本案例对应的源代码目录:src/chapter04/ks04_03。
Qt提供了大量的类来支撑跨平台软件研发工作,这给软件开发人员带来了极大的便利。但在进行服务端开发时,不建议使用Qt类库,因为引入Qt类库将使运行程序的体积变得较大。在进行服务端开发时最好使用软件开发组织自己写的类库或者使用一些比较轻便小巧的第三方跨平台类库,而仅仅把Qt用来制作服务端项目的pro文件。从本节开始,将选择Qt的几个常用类进行讲解。字符串处理是跨平台界面编程时经常碰到的场景,所以本案例介绍Qt的字符串处理类QString。
在使用Qt提供的类时,首先需要引入Qt的类定义文件。Qt的类定义文件采用跟类名同样的拼写方式,比如包含QString的头文件的代码:
#include <QString> |
QString提供的接口非常多,不可能对它们一一进行介绍,开发人员可以用assistant查看Qt的文档来寻求帮助。本节的案例列举了几种常见的字符串处理场景。
1.从输入目录中获取目录名、文件名
从输入目录中获取目录名、文件名这个场景对应example01()。在这个案例中首先给出一个绝对路径名,要求从这个路径中获取目录名和文件名。比如,输入:
QString strInput = "d:\\gui\\src\\chapter01\\ks01_01\\ks01_01.pro"; |
得到的输出应该是:目录名为d:/gui/src/chapter01/ks01_01,文件名为ks01_01.pro。本场景对应的实现见代码清单4-14。
// main()函数 if (false) { ① QString strInput = "d:\\project\\gui\\src\\chapter01\\ks01_01\\ks01_01.pro"; QString strOutput; QString strDir; QString strFileName; strOutput = example01(strInput, strDir, strFileName); } |
在代码清单4-14中,在main()函数中调用example01()。标号①处的代码体现了一种编程习惯。这样写是为了调试时可以把注意力只放在所关注的代码上。比如,当希望调试example01()时,那么就可以把此处的代码改为:
if (true) { ... } // 或者 if (1) { ... } |
现在来看example01()的实现代码,见代码清单4-15。
代码清单4-15
|
在代码清单4-15中,在example01()的函数体前提供了注释。软件研发人员应该从一开始就养成编写注释的习惯,这对日后的软件开发工作将产生深远的影响。此处将注释写在了实现代码中,但是推荐将注释写在头文件中,以便类的使用者进行查阅。此处把注释写在类的实现代码中仅仅是为了方便说明。
example01()提供一个输入参数strInput用来传入目录的全路径,函数注释中的in表示输入参数,即在本接口中只会使用该值,对调用该函数的代码中的原始对象没有任何影响。函数还定义了两个输出参数strDir、strFileName分别用来存放解析后的目录名和文件名,这两个都是out参数。out参数表示它们被用作函数的返回值,其内容会被该接口重写。函数入口对参数进行有效性判断。为了统一处理,利用replace()接口将字符串中的斜杠替换为Linux风格的“/”(见标号①处),“/”这种写法在Windows和Linux系统中都支持。替换后的文本变为:d:/project/gui/src/chapter01/ks01_01/ks01_01.pro。然后找到文本中最后一个斜杠,这是目录名与文件名的分割点(见标号②处)。然后,获取目录名(见标号③处)。此时strDir的值为d:/gui/src/chapter01/ks01_01。获取文件名时使用idx-1是为了剔除文件名前面的斜杠(见标号④处)。此时,strFileName的值为ks01_01.pro。这样就成功将传入的文件全路径拆分成了目录名与文件名。
2.字符串拼接
字符串拼接这个场景对应example02()。这个场景比较简单,见代码清单4-16。
代码清单4-16
// example02() /** * @brief 组织日志信息。 * @param[in] level 日志等级 * @param[out] strPerson 人员信息 * @param[out] strComputer 机器信息 * @param[out] strInfo 日志详情 * @return 拼接后的日志 */ QString example02(int level, const QString& strPerson, const QString& strComputer, const QString& strInfo){ QString str; str.sprintf("---- level=%02d, Person:", level); // str = "---- level=01, Person:" str += strPerson; // str = "---- level=01, Person:Lisa" str += ", Computer:"; // str = "---- level=01, Person:Lisa, Computer:" str += strComputer; // str = "---- level=01, Person:Lisa, Computer:adm01" str += ", info="; // str = "---- level=01, Person:Lisa, Computer:adm01, info=" str += strInfo; // str = "---- level=01, Person:Lisa, Computer:adm01, info=xxxxxx"; return str; } |
QString的sprintf()同标准库的printf()用法类似,都是先传入格式化字符串,再传入变量值。字符串拼接采用QString重载的+=操作符。代码清单4-16中每行代码后面的注释中都备注了拼接后的字符串内容。
3.格式化字符串
格式化字符串的场景对应example03()。如果希望QString中的字符串按照一定的格式保存,除了sprintf()之外还可以使用arg()接口。比如将矩形的坐标、宽高按照一定格式(x,y,w,h)存储在QString中,如代码清单4-17所示。
代码清单4-17
// example03() /** * @brief 使用arg()接口对信息格式化,输出矩形的坐标、宽高。 * @param[in] rect 矩形 * @return 矩形的坐标、宽高 */ QString example03(const QRectF& rect){ QString strRect = QString("%1,%2,%3,%4").arg(rect.x()).arg(rect.y()).arg(rect.width()).arg(rect.height()); return strRect; } |
在代码清单4-17中,用arg()接龙完成了矩形坐标尺寸信息的格式化。其中,%1、%2、%3、%4分别对应QString()后面的4个arg()中的参数。把需要显示的数据放到arg()中。函数arg()比起sprintf()来是类型安全的,而且它能接受多种数据类型作为参数,因此建议使用arg()函数而不是sprintf()。
格式化字符串的翻译这个场景对应example04()。如果字符串带有可变参数,并且需要进行翻译,那么使用arg()接口是个不错的选择,见代码清单4-18。
代码清单4-18
// example04() /** * @brief 使用arg()接口对信息格式化,接口内部支持翻译。 * @param[in] strTemplateName 模板名称;@param[in] nTerminalCount 端子个数 * @return 格式化后的信息 */ QString example04(const QString& strTemplateName, int nTerminalCount){ QString strInfo = QObject::tr("template name:%1 already has terminal count=%2.").arg(strTemplateName).arg(nTerminalCount); cout << strInfo.toLocal8Bit().data() << endl; return strInfo; } |
在代码清单4-18中,待翻译的源文中含有可变参数%1、%2,可以使用arg()配合实现翻译功能。在ts文件中,源文的内容为"template name:%1 already has terminal count=%2.",翻译后的文本可以写成:“模板名:%1 已经包含%2个端子。”。
5.解析带有特定分隔符的字符串
解析带有特定分隔符的字符串这个场景对应example05()。在进行软件开发时,有时候需要解析这样一些字符串,这些字符串通过特定的分隔符进行隔离(比如“,”、“:”、“|”等)。解析代码见代码清单4-19。
// example05() void example05(){ QString str = QString::fromLocal8Bit("软件特攻队,女儿叫老白,C++|Qt"); ① str = str.trimmed(); // 删除多余的空格 QStringList strList = str.split(","); // strList[0] : "软件特攻队" ② // strList[1] : "女儿叫老白" // strList[2] : "C++|Qt" cout << "机构:" << strList[0].toLocal8Bit().data() << endl; cout << "姓名:" << strList[1].toLocal8Bit().data() << endl; QString strCategory = strList[2]; // strCategory : "C++|Qt" strCategory.replace("|", ","); // strCategory : "C++,Qt" ③ cout << "领域:" << strCategory.toLocal8Bit().data() << endl; } |
首先请注意标号①处的QString::fromLocal8Bit()接口,该接口可以接受中文字符并将其正确显示在Qt界面。如果源代码文件的编码格式为UTF-8,那么也可以用QString::fromUtf8()接口。尝试直接为str赋值为中文,然后运行程序看看会得到什么结果?
QString str = "软件特攻队, 女儿叫老白, C++|Qt "; |
在代码清单4-19中,源文为“软件特攻队,女儿叫老白, C++|Qt”。标号②处代码先通过QString的split()接口根据“,”分隔符将该源文拆分得到一个QStringList。QStringList中存放着拆分后的3个字符串:“软件特攻队”、“女儿叫老白”、“C++|Qt”。其中“C++|Qt”又被“|”分隔符分成两部分。
然后,将拆分后的字符串列表输出到终端,得到:
机构:软件特攻队
姓名:女儿叫老白
在标号③处,用replace()接口把第三部分字符串“C++|Qt”中的“|”用逗号做了替换,并输出到终端。最终显示的信息为:
机构:软件特攻队
姓名:女儿叫老白
领域:C++,Qt
6.字符串与数值相互转换
字符串与数值相互转换这个场景对应example06()。字符串与数值相互转换是在软件开发过程中经常碰到的场景,具体见代码清单4-20。
代码清单4-20
void example06(){ // 字符串转整数 QString str1 = "2147483648"; // int32最大值:2147483647 int nInt32 = str1.toInt(); // 带符号整数 uint uInt32 = str1.toUInt(); // 无符号整数 cout << "int32 data = " << nInt32 << endl; cout << "uInt32 data = " << uInt32 << endl; // 整数转字符串 QString str2; nInt32 = 2147483647; str1.sprintf("%d", nInt32); // 带符号整数 cout << "new int32 data = " << str1.toLocal8Bit().data() << endl; str2.sprintf("%d", uInt32); // %d,无符号整数 cout << "new uint32 data = " << str2.toLocal8Bit().data() << endl; str2.sprintf("%u", uInt32); // %u,无符号整数 cout << "new uint32 data = " << str2.toLocal8Bit().data() << endl; str1 = QString("%1").arg(nInt32); cout << "another new int32 data = " << str1.toLocal8Bit().data() << endl; // 字符串转单精度浮点数 QString str3 = "200.f"; float f = str3.toFloat(); cout << "float data = " << f << endl; str3 = "200"; f = str3.toFloat(); cout << "float data = " << f << endl; // 字符串转双精度浮点数 QString str4 = "3.1415926535897932"; qreal d = str4.toDouble(); QString strData; strData = QString::number(d); cout << "double data = " << strData.toLocal8Bit().data() << endl; // 需要明确指示精度,否则默认只有6位小数。 strData = QString::number(d, 'g', 16); ① cout << "double data = " << strData.toLocal8Bit().data() << endl; } |
在代码清单4-20中,先介绍了QString的toInt()、toDouble()等将字符串转换为数值的接口,也介绍了sprintf()、number()等将数值转换为字符串的接口。需要注意的是标号①处的QString::number()可以指定数据格式和精度,该接口原型见代码清单4-21。format取值见表4-1。
代码清单4-21
[static] QString QString::number(double n, // 数值 char format = 'g', // 格式 int precision = 6) // 精度,小数点后的位数 |
表4-1 format的取值
值 | 含义 | 值 | 含义 |
e | 科学计数法,格式化为[-]9.9e[+|-]999 | g | 使用e或f格式,哪个更简练就用哪个 |
E | 科学计数法,格式化为[-]9.9E[+|-]999 | G | 使用E或f格式,哪个更简练就用哪个 |
f | 格式化为[-]9.9 |
|
|
从表4-1看出,其实format用来表示各种不同的数值表示方法,这个参数影响的是将数据转换成QString后的字符串的内容。如果仍然不理解,可以尝试修改一下这个参数,然后查看QString的内容来看看效果。precision用来表示精度,最高精度是小数点后15位(个人经验,仅供参考)。
----------------------------------------------------------------------------------------------------------------------------------------------