Excel数据对比分析小项目总结

这几天做了一个对Excel进行对比操作的一个小项目,凑了点学费:)。主要涉及到的功能就是先用QT做一个简单的界面,可供选择输入为文件或文件夹,选择完成之后对输入文件的格式进行检查,然后对比已有的数据库中(很多个文件,大概一共十几万行数据)的数据,分析输入文件中数据的错误,最后将分析后的结果写到Excel文件中,在这个项目主要涉及到以下几个知识点:

1. QT中文乱码的解决

QString 是不存在中文支持问题的,很多人遇到问题,并不是本身 QString 的问题,而是没有将自己希望的字符串正确赋给QString。
“我是中文”这样写的时候,它是传统的 char 类型的窄字符串,我们需要的只不过是通过某种方式告诉QString 这四个汉字采用的那种编码。而问题一般都出在很多用户对自己当前的编码没太多概念。

在这里介绍一篇对字符编码讲解的很透彻的一篇文章:ASCII、Unicode、GBK和UTF-8字符编码的区别联系

QString的内部是使用的unicode编码,所以本质就是将所有字符转换成unicode编码即可正常显示。

所以当使用这样的代码:

1
QString a = "我是中文"

其实等价于

1
2
const char* s = "我是中文"
QString a = s;

那么当需要从窄字符串char转成Unicode的QString字符串时,你需要告诉Qt你的这个char 是什么编码?GBK、BIG5、Latin-1.

所以关于Qt的中文乱码,Qt4和Qt5有不同的解决方案。
在Qt4中,常用的解决方案是在main.cpp加这几行代码

1
2
3
4
QTextCodec *codec = QTextCodec::codecForName("system");//获取系统中文编码
QTextCodec::setCodecForLocale(codec);
QTextCodec::setCodecForCStrings(codec);
QTextCodec::setCodecForTr(codec);

这几行代码就是告诉程序你的char* 中到底使用的是什么编码。

而在Qt5中取消了这几个函数,取而代之的是另外的解决方案。
用QTextCodec类中的转换函数

1
2
3
4
5
6
7
8
9
10
11
std::string utf82gbk(const QString &inStr)
{

QTextCodec *gbk = QTextCodec::codecForName("GB18030");
return gbk->fromUnicode(inStr).data();
}

QString gbk2utf8(const std::string &inStr)
{

QTextCodec *gbk = QTextCodec::codecForName("GB18030");
return gbk->toUnicode(inStr.c_str());
}

2. QString,string,wstring三者中文字符的比较

QString是unicode串,对应QChar为2个字节;
string一般如果不包含中文则是ascii串,包含中文会自动转换成gbk串,对应字符char 1个字节;
wstring是宽字符,也是unicode串,对应wchar_t 2字节(或4字节,linux);

1
2
3
4
5
6
7
QString q1 = QObject::tr("abc哈哈");
string s1 = "abc哈哈";
wstring w1 = L"abc哈哈";

int len1 = q1.length(); //5
int len2 = s1.length(); //7
int len3 = w1.length(); //5

在上面代码中,len1和len3都为5,返回的是字符数,而len2为7返回的是字节数。

下面是三者之间的互相转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//QString - string
string str = qstr.toStdString();
QString qstr(str.c_str());

//QString - wstring
wstring wstr = qstr.toStdWString();
QString qstr = QString::fromStdWString(wstr);

//string - wstring

//不用C++11的方案
// 需包含locale、string头文件、使用setlocale函数。
wstring StringToWstring(const string str)
{// string转wstring

unsigned len = str.size() * 2;// 预留字节数
setlocale(LC_CTYPE, ""); //必须调用此函数
wchar_t *p = new wchar_t[len];// 申请一段内存存放转换后的字符串
mbstowcs(p,str.c_str(),len);// 转换
wstring str1(p);
delete[] p;// 释放申请的内存
return str1;
}

string WstringToString(const wstring str)
{// wstring转string

unsigned len = str.size() * 4;
setlocale(LC_CTYPE, "");
char *p = new char[len];
wcstombs(p,str.c_str(),len);
string str1(p);
delete[] p;
return str1;
}

//C++11 现在提供了专门的转换函数
wstring s2ws(const std::string& str)
{

typedef std::codecvt_utf8<wchar_t> convert_typeX;
std::wstring_convert<convert_typeX, wchar_t> converterX;

return converterX.from_bytes(str);
}

string ws2s(const std::wstring& wstr)
{

typedef std::codecvt_utf8<wchar_t> convert_typeX;
std::wstring_convert<convert_typeX, wchar_t> converterX;

return converterX.to_bytes(wstr);
}

3. C++获取文件夹及其子文件夹下所有文件和特定后缀文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//检查文件的后缀名
bool checkSuffix(const string& filename,string suffix){

size_t length = filename.length();
size_t pointindex = filename.find_last_of(".");
if(pointindex<length){
string nowsuffix = filename.substr(pointindex);
if(iequal(nowsuffix,suffix)){
return true;
}
}
return false;
}

//获取特定后缀的文件
void getAllFiles(const string& folderpath, vector<string>& files,string suffix)
{

long hFile = 0;
struct _finddata_t fileinfo;
string p;
if((hFile = _findfirst(p.assign(folderpath).append("/*").c_str(),&fileinfo)) != -1)
{
do
{
if(fileflag){
if((fileinfo.attrib & _A_SUBDIR))
{
if(strcmp(fileinfo.name,".") != 0 && strcmp(fileinfo.name,"..") != 0)
{
getAllFiles( p.assign(folderpath).append("/").append(fileinfo.name), files,suffix);
}
}
else
{
if(checkSuffix(string(fileinfo.name),suffix)){
files.push_back(p.assign(folderpath).append("/").append(fileinfo.name) );
}else{
fileflag = false;
return;
}
}
}
}while(_findnext(hFile, &fileinfo) == 0);

_findclose(hFile);
}

}

4. xls和xlsx读取方式的不同

现有的C++ Excel处理库中,开源的解决方案中,没有可以支持.xls和.xlsx这两种文件的,所以我选取了两个库都分别处理,先判断他的后缀名,最后分析处理。
.xls的处理库我选择的是BasicExcel,但是不支持中文,需要进去源码里面去修改编码。
.xlsx的处理库选择的是Qt Xlsx,这个对中文支持较好,对xlsx文档的读写都比较友好,文档也很详细。

5.其他的小功能

比较两个字符串,忽略大小写

1
2
3
4
5
6
7
8
9
10
11
bool iequal(const string& str1, const string& str2) {
if (str1.size() != str2.size()) {
return false;
}
for (string::const_iterator c1 = str1.begin(), c2 = str2.begin(); c1 != str1.end(); ++c1, ++c2) {
if (tolower(*c1) != tolower(*c2)) {
return false;
}
}
return true;
}

6.总结

这个小工具本身比较简单,几天就写完了,但是又很多小地方需要特别注意,如规定好输入Excel文件的格式,对中文的处理,Excel文件中每个单元格中是否有空格或其他的一些违法字符,还有对输入路径合法性的检查,文件后缀的判定等等。尽管比较简单,但花了几天时间,凑了点学费还是不错的,O(∩_∩)O哈哈哈~