3.4 字符串类QString
之所以把QString类单独拿出来,是因为字符串是很常用的一种数据类型,在很多语言中(比如JavaScript)都是把string作为一种与int等一样的基本数据类型来实现的。
每一个GUI程序都需要字符串,这些字符串可以用作界面上的提示语,也可以用作一般的数据类型。C++语言提供了两种字符串的实现:C风格的字符串,以'\0'结尾;C++中的std::string,即标准模板库中的字符串类。Qt提供了自己的字符串类:QString。QString类以16位Unicode进行编码。我们平常用的ASCII等一些编码集都作为Unicode编码的子集。关于Unicode编码的问题,我们会在稍后章节详细说明。
在使用QString类对象的时候,我们不需要担心内存分配以及关于'\0'结尾的这些注意事项。QString类会解决这些问题。通常,我们可以把QString类对象看作是一个QChar的向量。另外,与C风格的字符串不同,QString类对象中间可以包含'\0'符号,它的length()函数会返回整个字符串的长度,而不仅仅是从开始字符到'\0'字符为止的字符串长度。
QString类在Qt的各种数据转换中可谓举足轻重,熟悉QString类的用法对于Qt编程而言真的是如虎添翼。QString是Qt编程中常用的类,除了用作数字量的输入输出之外,QString类还有很多其他功能,熟悉这些常见的功能有助于灵活地实现字符串的处理。
QString类存储字符串釆用的是Unicode码,每一个字符都是一个16位的QChar类对象,而不是8位的char类型字符,所以QString类用于处理中文字符没有问题,而且一个汉字算作一个字符。
3.4.1 Qstring类的特点
作为后起之秀,QString类有如下特点:
(1)采用Unicode编码,所以一个QChar类对象占用两个字节。
(2)使用隐式共享技术来节省内存和减少不必要的数据备份。
(3)跨平台使用,不用考虑字符串的平台兼容性。
(4)QString类直接支持字符串和数字之间的相互转换。
(5)QString类直接支持字符串之间的大小比较(按照字典顺序)。
(6)QString类直接支持不同编码下的字符串转换。
(7)QString类直接支持std::string和std::wstring之间的相互转换。
(8)QString类直接支持正则表达式的使用。
3.4.2 Qstring类的常用操作
QString类的常用操作包括字符串类对象的构造、字符串的追加、字符串的组合、字符串的插入及替换、查找字符获取索引、字符串的提取、把字符串转换为其他类型、字符串的比较、判断字符串是否存在、字符串的分隔、空白字符串的过滤、字符串中字母大小写的切换、判断字符串中是否以某个子字符串开始或结束、获取字符串的长度等。这些常用操作都有常用接口相对应。下面简单了解一下常用的10类接口。
1. 初始化
通常有两种方法可以初始化QString类的对象。
第一种方法是通过构造函数。QString类的构造函数较多,通常有如下几种用法:
例如,以下代码创建一个长度为5的字符串,内容为“Hello”:
QString str("Hello");
效果等同于直接赋值:
QString str = "Hello";
又比如:
static const QChar data[4] = { 0x0055, 0x006e, 0x10e3, 0x03a3 }; // 使用了构造函数QString ( const QChar * unicode, int size ); QString str(data, 4);
第二种方法是使用resize()设置字符串的大小,并初始化字符数组中的每个元素。比如:
QString str; str.resize(4); str[0] = QChar('U'); str[1] = QChar('n'); str[2] = QChar(0x10e3); str[3] = QChar(0x03a3);
QString类的索引值从0开始,和C++的数组一样。调用resize()函数后,新分配的字节具有未定义的值。要将所有字节设置为同一个特定值,可以调用fill()函数,该函数的原型声明如下:
QString::fill ( QChar ch, int size = -1 );
其中,ch是要填充的字符;size是要填充的字符个数,即填充size个ch字符。比如:
QString str = "Berlin"; str.fill('z'); // str == "zzzzzz" str.fill('A', 2); // str == "AA"
关于QString类有几点需要了解清楚:
(1)QString类存储的字符串中的字符默认采用的是Unicode编码
比如有如下代码:
QString str = "你好";
str变量中存储的数据采用的是Unicode编码格式,接收方如果解析成乱码,就要想想两方的编码格式采用的是否都是Unicode。如果不是,就需要用QTextCodec类来执行转码操作。
(2)来自char*的数据,默认被当作UTF-8编码格式
最常用的就是传入一个 const char*(字符串常量),例如:
QString str = "hello";
Qt默认将来自char*的字符串视为UTF-8编码格式,因此在转换过程中会在内部自动调用fromUtf8()函数进行char*→QString的转换。
(3)用QChar数组构建的QString类对象会进行深度复制
因为Unicode编码格式是用双字节存储一个字符,所以QString类中存储着一个个的16位QChar字符(16位即为2个字节,16 bits =2 bytes),每个QChar字符对应着一个Unicode 4.0字符。如果字符的编码大于65536,就要用两个QChar存储这个字符。例如:
static const QChar data[4] = {0x0055, 0x006e, 0x10e3, 0x03a3}; QString str(data,4);
用QChar数组来构建QString类对象是采用深度复制(Deep Copy)的方式,意思就是说QString类对象会完整复制一份QChar数组的数据。
QString类对象复制QChar的数据时采用深度复制,意味着增加了系统开销。如果不想如此,则可以使用fromRawData()函数,该函数的原型声明如下:
QString QString::fromRawData(const QChar *unicode, int size);
参数unicode用于构造字符串的QChar数组,并不会进行复制;size表示在unicode中从左开始截取的长度。下列代码演示fromRawData()函数的使用:
2. 访问某个元素
和3.2.2节讲述的访问QByteArray类对象中某个元素的方式类似,访问QString类对象中的某个元素采用类似的4种主要方式,分别为[]、at()、data[]和constData[]。其中,[]和data[]方式为可读可写,at()和constData[]方式仅为可读。如果只是进行读操作,则采用at()和constData[]方式的访问速度最快,因为避免了复制操作。
at()方式比operator []()方式快,因为前者不会进行深度复制操作。总之,如果仅仅是读取QString类对象中的字符,那么调用at()函数更快。函数at()的原型声明如下:
QChar QString::at(int position);
返回position索引处的字符,如果position的值超过字符串的长度就返回0。比如:
更直观的方法是用操作符[],它们的声明形式如下:
const QChar operator[] (int position) const; const QChar operator[] (uint position) const;
事实上,通过[]操作符得到的字符可以被修改,要用到另外两个重载的[]操作符:
QCharRef operator[] (int position); QCharRef operator[] (uint position);
返回的QCharRef类是一个辅助类,对这个类的对象进行修改会修改到原字符串。
下列代码演示了data[]的使用:
3. 赋值运算
通过赋值运算符(=)可以给QString类对象赋值,比如:
QString str = "abc";
4. 获取长度
QString类的成员函数count()、size()和length()都会返回字符串中的字符个数,这3个函数是相同的,但是要注意,字符串中如果有汉字,那么一个汉字只算一个字符。下列代码演示了这3个函数的使用:
5. 字母大小写的转换
QString类的成员函数toUpper()会将字符串内的字母全部转换为大写形式,toLower()则会将字符串内的字母全部转换为小写形式,比如:
QString str1="Hello, World", str2; str2=str1.toUpper(); //str1="HELLO,WORLD" str2=str1.toLower(); //str1="hello, world"
6. 移除字符
成员函数remove()可以移除字符串中一个或多个字符,该函数的原型声明如下:
QString &remove(int position, int n);
其中,参数position表示要被移除字符的起始索引位置;n表示要移除字符的个数。该函数返回的是移除字符后字符串的引用。
又比如:
QString s = "Montreal"; s.remove(1, 4); // s == "Meal"
7. 添加字符串
QString类的成员函数append()在字符串的后面添加字符串,而成员函数prepend()在字符串的前面添加字符串,比如:
QString str1="卖",str2="拐"; QString str3=str1; str1.append (str2) ; //str1="卖拐" str3.prepend (str2) ; //str3="拐卖"
与Java语言中的String类类似,QString类也重载了+和+=运算符。这两个运算符可以把两个字符串连接到一起,这和Java语言中String类的操作一样。QString类可以自动对占用内存空间进行扩充,因而这种连接操作非常迅速。这两个运算符的使用方法如下:
QString str = "User: " ; str+=userName+ "/n" ;
8. 去掉空格
QString类的成员函数trimmed()会去掉字符串首尾的空格,而成员函数simplified()不仅会去掉字符串首尾的空格,中间连续的空格也用一个空格符来替换。比如:
QString str1=" Are you OK? ", str2; str2=str1.trimmed () ; //str1="Are you OK? " str2=str1.simplified(); //str1="Are you OK?"
又比如:
9. 查找子字符串
QString类的成员函数indexOf()在自身字符串内查找参数str指定的字符串所出现的位置。indexOf()函数的原型声明如下:
int indexOf (const QString &str, int from = 0 , Qt::CaseSensitivity cs = Qt::CaseSensitive);
在自身字符串内查找参数str指定的字符串所出现的位置,参数from指定开始查找的位置;参数cs指定是否区分字母大小写,默认是区分字母大小写的(Qt::CaseSensitive)。如果找到str指定的字符串,则返回该字符串在所查找字符串中第一次出现的位置,即索引值,如果没有找到,则返回-1。注意,所谓向前查找,就是朝着索引值增大的方向查找,即从左到右进行查找。
另外,函数lastIndexOf()用于查找某个字符串最后出现的位置,相当于从字符串末尾开始朝字符串头部方向查找,即从右到左进行查找。比如:
QString str1="G:\Qt5Book\QT5.9Study\qw.cpp"; N=str1.indexOf("5.9"); // N=13 N=str1.lastIndexOf("\\"); //N=21
"\"是转义字符,如果要查找 "\",则需要输入 "\\"。
又比如:
更强大的查找函数是find(),该函数的原型声明如下:
int find(const QRegExp & rx, int index = 0);
从位置index开始,找到常量正则表达式rx第一次出现的位置。如果index为-1,则从最后一个字符开始查找,如果是-2,则从倒数第二个字符开始查找,以此类推。函数返回rx第一次出现的位置,如果没有被找到rx,则返回-1。比如:
QString string( "bananas" ); int i = string.find( QRegExp("an"), 0 ); // i == 1
10. 判读字符串是否为空
成员函数isNull()和isEmpty()都是用于判读字符串是否为空,但是稍有差别。如果是一个空字符串,只有"\0",isNull()则返回false,而isEmpty()返回的是true;只有未赋值的字符串,isNull()才返回true。比如:
QString类对象只要赋值,就会在字符串的末尾自动加上"\0"。如果只是要判断字符串内容是否为空,常用isEmpty()函数。
11. 判断是否包含某个字符串
函数contains()用于判断字符串内是否包含某个字符串,可指定是否要区分字母大小写。比如:
12. 判断是否以某个字符串开头或结尾
函数startsWith()用于判断是否以某个字符串幵头,函数endsWith()用于判断是否以某个字符串结束。比如:
又比如:
if (url.startsWith("http:" ) && url.endsWith(".png" )) { }
等价于:
if (url.left(5) == "http:" && url.right(4) == ".png" ) { }
不过,前者要比后者更加清楚简洁,并且性能也更快一些。
13. 截取子字符串
函数left()表示从字符串中截取左边多少个字符。函数right()表示从字符串中截取右边多少个字符。注意,一个汉字被当作一个字符。比如:
函数mid()也可以用来截取子字符串,该函数的原型声明如下:
QString mid(int position, int n = -1);
mid()函数接收两个参数,第一个是起始位置,第二个是截取子字符串的长度。如果省略第二个参数,则会从字符串起始位置截取到末尾。比如:
注意
left()、right()和mid()三个函数并不会去修改QString类对象自身,而是返回一个临时对象供调用者使用。
另外,函数section()也可以用来截取子字符串,截取功能更为强大,它的功能是从字符串中提取以sep作为分隔符、从start开始到end结束的子字符串。该函数的原型声明如下:
QString section (const QString &sep, int start, int end = -1, SectionFlags flags = SectionDefault);
比如:
14. 格式化打印
C语言中用printf()函数进行格式化输出,QString类提供了一个sprintf()成员函数来实现相同的功能,比如:
str.sprintf("%s %.1f%%" , "perfect competition" , 100.0);
这句代码将输出“perfect competition 100.0%”,同C语言的printf()一样。又比如:
Qt还提供了另一种方便的字符串组合方式,即QString::arg()函数,此函数的重载形式可以用于处理很多数据类型。此外,一些重载具有额外的参数,用于对字段的宽度、数字基数或者浮点精度进行控制。相对于QString::sprintf(),QString::arg()是一个比较好的解决方案,因为它的类型安全,完全支持Unicode,并且允许改变"\n"参数的顺序。例如:
QString str; str = QString("%1 was born in %2.").arg("Joy").arg(1993); //str = "Joy was born in 1993."
其中:"%1"被替换为"Joy","%2"被替换为"1993"。又比如:
str = QString("%1 %2 (%3s-%4s)").arg("permissive").arg("society").arg(1950). arg(1970);
在这句程序代码中,%1、%2、%3、%4作为占位符,将被后面的arg()函数中的内容依次替换,比如%1将被替换成permissive,%2将被替换成society,%3将被替换成1950,%4将被替换成1970。最后,这句程序代码的输出为“permissive society(1950s-1970s)”,arg()函数与sprintf()函数相比,前者是类型安全的,同时它也接收多种数据类型作为参数,因此建议使用arg()函数而不是传统的sprintf()函数。
15. 将字符串类型转换成其他基本数据类型
与QByteArray类类似,一系列的to函数可以将字符串转换成其他的基本数据类型的数据,例如toInt()、toDouble()、toLong()等。这些函数都接收一个bool指针作为参数,函数结束之后将根据是否转换成功设置为true或者false。比如:
16. 字符串的比较
静态成员函数compare()可以用来比较两个字符串。函数的原型声明如下:
int compare(const QString & s1, const QString & s2);
对s1和s2进行词典比较,如果s1小于、等于或者大于s2,则返回小于、等于或者大于0的整数。比如:
int a = QString::compare( "def", "abc" ); // a > 0 int b = QString::compare( "abc", "def" ); // b < 0 int c = QString::compare(" abc", "abc" ); // c == 0
这个比较是基于字符Unicode值大小的,并且非常快。如果要对用户界面的字符串进行比较,则请考虑使用QString::localeAwareCompare()成员函数。
除了用函数进行比较外,还可以使用operator<()、operator<=()、operator==()、operator>()、operator>=()和operator!=这6个运算符进行比较。它们的原型声明如下:
bool operator<(const char *s1, const QString &s2); bool operator<=(const char *s1, const QString &s2); bool operator==(const QString &s1, const QString &s2); bool operator>(const QString &s1, const QString &s2); bool operator>=(const QString &s1, const QString &s2); bool operator!=(const QString &s1, const QString &s2);
17. 清空
成员函数clear()可用于清空一个QString类对象的内容(即字符串),使之成为空字符串。该函数的原型声明如下:
void clear();
18. 截断字符串
成员函数truncate()可用于截断QString类对象的内容,也就是去掉指定位置后的所有内容,函数的原型声明如下:
void truncate(int position);
从位置position处截断。注意,位置是从索引值0开始的。
成员函数chop()可用于截掉QString类对象最后的若干个字符,该函数的原型声明如下:
void chop(int n);
该函数截掉最后的n个字符。
19. char *和QString互转
将char *类型的C语言风格的字符串转换成QString类的对象也是很常见的需求,我们可以调用函数QLatin1String()来进行转换:
char *c_str = "123456789"; QString string = QString(QLatin1String(c_str));
或者使用构造函数法:
char * c_str ="hello!"; QString str(c_str); // Qt5 QString str = QString::fromUtf8(ch)); // 针对Qt4
另外,还可以调用函数fromAscii()等。
为了将QString类的对象转成char *字符串,需要进行两步操作,首先调用函数toAscii()获得一个QByteArray类的对象,然后调用它的data()或者constData()函数,例如:
printf("User: %s/n" , str.toAscii().data());
为了方便使用,Qt提供了一个宏qPrintable(),等价于toAscii().constData(),例如:
printf("User: %s/n" , qPrintable(str));
再比如:
也可以不先转换为QByteArray类的对象,而是通过复制函数来转换,比如:
QString str("hello world!"); const char* std_str = str.toStdString().data(); char buf[4096] = {0}; strcpy(buf, std_str);
20. std::string和QString互转
QString类的toStdString函数使用中文时会乱码,需要调用函数tolocal8Bit()进行转化。
(1)std::string转为QString(全英文字符)
std::string s = "hello world"; QString qs = QString::fromStdString(s);
(2)std::string转为QString(中文字符)
std::string s = "hello 世界"; QString qs = QString::fromLocal8Bit(s.data());
(3)QString转为std::string(全英文字符)
QString qs = "coder"; std::string s = qs.toStdString();
(4)QString转为std::string(中文字符)
QString qs = "你好,world"; QByteArray cdata = qs.toLocal8Bit(); std::string s = std::string(cdata);
21. 数字和QString互转
使用static的函数number()可以把数字转换成字符串。例如:
QString str = QString::number(54.3);
也可以使用非static函数setNum()来实现相同的目的:
QString str; str.setNum(54.3);
上面是把浮点数转为字符串,下面是将整数类型(int)转为QString类的字符串对象:
int d = 18; QString qs = QString::number(d);
QString转为int:
QString qs = "123"; int d = qs.toInt();