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();