• Qt学习(13)


    Qt学习(13)——使用 QString

            本节介绍 QString 的常见使用,包含 C++ 基本类型和 QString 的互相转换、QString 涉及的运算符、QString 子串查询和操作、利用 QTextStream 对 QString 做输入输出操作等,最后通过一个示例集成测试函数,展示 QString 用法。本节内容较多,可分几次尝试代码,凡是原理性质的内容需要理解,而罗列性质的内容不用死记的,可以到用的时候查看文档。

    1、QString和QChar简介

          QString是由一系列16bit字符QChar组成的字符串,以NULL字符结尾(末尾的NULL不计入字符串长度)。QChar是一个Unicode 4.0标准的字符,对于超过16bit范围的国际码字符,QString里采用相邻的一对QChar来表示。QString使用的其实是UTF-16的双字节编码,tr函数就是将UTF-8变长编码的字符串转成QString运行时的内码。UTF-8编码是属于通用的存储交换格式,但这种编码的缺点就是一个字符的长度不固定,这对字符串操作效率是有影响的,因为要先确定每个字符的长度。因此QString采用固定长度字符单元的UTF-16编码,这对程序运行时字符串比较、查询操作效率更高。上一节 中“运行时QString与多种编码格式转换” 表格中 utf16() 和 unicode() 函数都没有用 to 前缀,因为这两个函数没有做转换,它们返回的就是 QString  运行时的内码,同 data() 函数。tr 函数不仅可以用于支持国际化翻译,并且能自动将字符串的存储交换格式 UTF-8 转换成运行时的 UTF-16 内码,返回转换过后得到的QString 对象。

            字符串之间经常有手动复制或者通过函数参数、函数返回值等复制操作,QString为了优化内存使用效率,避免出现大量相同内容的字符串副本,QString对复制过程采用隐式共享机制(implicit sharing),比如执行字符串对象str1 = str2时,如果这两个对象字符串内容都没有后续的改变,那么它们会指向同一块字符串数据,而如果其中之一发生改变,字符串数据块的复制过程才会发生,这样能最大程度地节省内存,而且在传QString类型参数或返回值时,避免了大量数据块的复制过程,优化了程序运行效率。

           QString内码是UTF-16,而标准C++的字符串是UTF-8编码的,Qt针对标准C++字符串也提供了QByteArray类,用于操作UTF-8编码以及其他本地化字符串(如GBK、Big5)、字节数组(不以NULL结尾的纯数据)等,QByteArray类下一节会介绍。

    2、基本类型与字符串互相转换

            在编程时经常会出现把数值如 800 转成字符串 "800",或者反过来把字符串转成数值等情况,本小节罗列 C++ 基本的数值类型和 Qt 对这些类型的别称,然后展示这些基本类型和 QString 对象的互相转换,并编写一些测试函数来示范效果。

    基本类型 Qt别称 转入函数 转出函数 描述
    short qint16 arg或setNum toShort 2 字节长度,有符号短整型。
    unsigned short ushort、quint16 arg或setNum toUShort 2 字节长度,无符号短整型。
    int qint32 arg或setNum toInt 4 字节长度,有符号整型。
    unsigned int uint、quint32 arg或setNum toUInt 4 字节长度,无符号整型。
    long arg或setNum toLong 有符号长整型,对于 32 位编程 long 是 4 字节长度,对于 64 位编程是 8 字节长度。
    unsigned long ulong arg或setNum toULong 无符号长整型,对于 32 位编程 unsigned long 是 4 字节长度,对于 64 位编程是 8 字节长度。
    long long qlonglong、qint64 arg或setNum toLongLong 8 字节长度,有符号长长整型。
    unsigned long long qulonglong、quint64 arg或setNum toULongLong 8 字节长度,无符号长长整型。
    float 默认情况下无 arg或setNum toFloat 4 字节长度,单精度浮点数。
    double 默认情况对应 qreal arg或setNum toDouble 8 字节长度,双精度浮点数。

     这些基本的数值类型转为 QString 对象都是使用重载的 arg 或 setNum 函数,而 QString  对象转出为其他类型使用单独命名的函数。Qt 对这些类型的别称都定义在头文件 <QtGlobal> 里面,由于其他绝大多数 Qt 头文件都包含了该全局头文件,所以通常不需要自己手动去包含它的。对于上表需要说明的两点:一是 long 和 ulong 长度是根据操作系统和编译器确定的,32 位编程就是 32 位,64 位编程就是 64 位;二是实数 qreal 默认情况下都是对应 double ,例外情况是在编译 Qt 类库本身时配置了 -qreal float 选项参数,这种例外情况极少,通常都不用管的
            首先来介绍一下转入函数,对于整数类型,setNum 函数声明是完全类似的,以 int 为例:

    QString & setNum(int n, int base = 10)

    第一个参数就是需要转换的整数,第二个参数是转换之后的目标字符串进制基数,比如转成十六进制字符串、八进制字符串等,默认是转成十进制的字符串。setNum 函数设置好字符串内容后返回 QString 对象自身的引用。

    对于浮点数类型,setNum 函数声明有些区别,以 double 为例:

    QString & QString::​setNum(double n, char format = 'g', int precision = 6)

    第一个参数是需要转换的浮点数,第二个是转换之后的目标字符串格式('e', 'E', 'f', 'g' ,  'G'),第三个是目标字符串显示的浮点数精度,默认是 6 。浮点数的格式是与 C 语言类似的,如下所述:

    • 'e':科学计数法,小写 e,如 [-]9.9e[±]999。
    • 'E':科学计数法,大写 E,如 [-]9.9E[±]999。
    • 'f':定点数显示,[-]9.9。
    • 'g': 自动选择用科学计数法或定点数显示,哪种方式最简洁就用哪个,科学计数法的 e 小写。
    • 'G':自动选择用科学计数法或定点数显示,哪种方式最简洁就用哪个,科学计数法的 E 大写。

    setNum 函数示范代码:

    void Test_setNum() {
        QString strTest;
        //to Hex string
        short numHex = 127;
        strTest.setNum(numHex, 16);
        qDebug()<<"Hex: "<<strTest;
    
        //to Oct string
        int numOct = 63;
        strTest.setNum(numOct, 8);
        qDebug()<<"Oct: "<<strTest;
    
        //to normal Dec string
        long numDec = 800;
        strTest.setNum(numDec);
        qDebug()<<"Normal: "<<strTest;
    
        //to float string
        float numFixed = 123.78999;
        strTest.setNum(numFixed, 'f', 3);
        qDebug()<<"Fixed: "<<strTest;
    
        //to scientific double string
        double numScientific = 456.78999;
        strTest.setNum(numScientific, 'e', 6);
        qDebug()<<"Scientific: "<<strTest;
    }

    这个测试函数运行结果就不贴出来了,读者自己手动去试试看。

            接下来重点介绍 arg 函数,这是最常用也是最具特色的。arg 函数无所不包,它的参数可以是数值类型、字符串类型,并且可以串联,格式化参数里还可以指定顺序、重复使用参数等等。对于数值类型,它的声明与 setNum 比较类似,以 int 和 double 为例:

    QString arg(int a, int fieldWidth = 0, int base = 10, QChar fillChar = QLatin1Char( ' ' )) const 
    QString arg(double a, int fieldWidth = 0, char format = 'g', int precision = -1, QChar fillChar = QLatin1Char( ' ' )) const

            注意 arg 函数声明末尾的 const,这个函数不会改变字符串对象本身的内容,而是会返回一个全新的 QString对象,所以使用这个函数时,必须用它的返回值。
            对于整数类型,它的声明多出来两个:fieldWidth 是指生成的目标字符串宽度,0 表示自动设置长度,最后的 fillChar 是填充字符,如果设置的域宽比较大,多余的空位就会使用这个填充字符填满。
            对于浮点数类型,多出来的 fieldWidth 也是生成的目标字符串宽度,fillChar 也是填充字符。默认的填充字符是空格,QLatin1Char 代表一个字节长度的拉丁字符,与 ASCII 码字符差不多。QLatin1Char 有对应的类 QLatin1String,因为仅支持单字节拉丁字符,不支持国际化,它应用的比较少。 
            arg 函数比 setNum 函数功能更强大,可以设置目标字符串宽度和填充字符。arg 函数还可以用字符串作为参数,可以将一个字符串填充到另一个里面,比如下面这个函数声明:

    QString arg(const QString & a, int fieldWidth = 0, QChar fillChar = QLatin1Char( ' ' )) const

    这个声明和数值类型声明差不多,也可以设置目标字符串宽度和填充字符。

    函数声明介绍到这,下面看看这个函数该怎么用。arg 函数的使用方式很特别,它的串联方式也很灵活,来看看示例代码:

    void Test_arg() {
        //使用 strResult 存储 arg 返回的新对象
        QString strResult;
    
        //Dec
        long numDec = 800;
        QString strMod = QObject::tr("Normal: %1");
        strResult = strMod.arg(numDec);  //%1是占位符,第一个arg函数参数变量转后的字符串填充到 %1 位置
        qDebug()<<"Mod: "<<strMod<<" 	 Result: "<<strResult;
    
        //Oct
        int numOct = 63;
        strResult = QObject::tr("Oct: %1").arg(numOct, 4, 8, QChar('0'));  //numOct转换后为4字符域宽,8进制,填充0
        qDebug()<<strResult;
    
        //Hex
        short numHex = 127;
        QString strPrefix = QObject::tr("0x");
        //占位符里可填充数值转的字符串,也可以直接填充原有的字符串
        strResult = QObject::tr("Hex: %1%2").arg(strPrefix).arg(numHex, 0, 16);  //串联:第一个arg函数参数填充到%1,第二个arg填充到%2
        qDebug()<<strResult;
    
        //double
        double numReal = 123.78999;
        strResult = QObject::tr("Fixed: %1 	 Scientific: %2").arg(numReal, 0, 'f').arg(numReal, 0, 'e', 3);
        qDebug()<<strResult;
    
        //占位符可重复,也可乱序
        int one = 1;
        int two = 2;
        int three = 3;
        strResult = QObject::tr("%1 小于 %2,%1 小于 %3,%3 大于 %2 。").arg(one).arg(two).arg(three);
        qDebug()<<strResult;
    }

    上面都是通过 tr 函数封装了一个临时的 QString 对象,然后调用该临时对象的 arg 函数实现数值类型转成格式化字符串,填充到占位符里面。这个工作原理与 sprintf 等 C 语言函数类似,sprintf 函数使用 %n 、%s  之类的格式占位符,QString 的实现方式不一样,它使用 % 加数字的占位方式,%1 对应后面串联的第一个 arg 函数,%2 对应后面串联的第二个 arg 函数,以此类推。具体的 %1 或 %2 等替换后的格式,由对应的 arg 函数来决定,QString 里有非常多的重载 arg 函数,每个 arg 函数对应一个类型,因此 %1 既可以填充数值类型转化后的格式化字符串,也可以填充其他原有的字符串。下面逐个解释一下各个 arg 函数意义:

    long numDec = 800;     
    QString strMod = QObject::tr("Normal: %1"); strResult = strMod.arg(numDec); //%1是占位符,第一个arg函数参数变量转后的字符串填充到 %1 位置 qDebug()<<"Mod: "<<strMod<<" Result: "<<strResult;

    这是最简单的形式,tr函数生成的 strMod 对象里面只有一个占位符 %1 , arg 函数会将整数 numDec 转成十进制数字符串,然后根据  strMod 构造一个新的字符串对象,并将十进制数字符串填充到占位符 %1 位置。原本的 strMod 不会改变,arg 函数会返回全新的字符串对象,然后复制给了 strResult。qDebug 打印的结果就是:
    Mod:      "Normal: %1"         Result:    "Normal: 800"

    int numOct = 63;     
    strResult = QObject::tr("Oct: %1").arg(numOct, 4, 8, QChar('0'));  //numOct转换后为4字符域宽,8进制,填充0
    qDebug()<<strResult;

    这里 arg 函数是将普通数字 63 用八进制数来显示,要转换的数值是 numOct,设置 numOct 转换后的子串至少 4 字符宽度,用八进制显示,空位用字符 '0' 填充。qDebug 打印的结果就是:
    "Oct:  0077"

    short numHex = 127;     
    QString strPrefix = QObject::tr("0x");
    //占位符里可填充数值转的字符串,也可以直接填充原有的字符串
    strResult = QObject::tr("Hex: %1%2").arg(strPrefix).arg(numHex, 0, 16);  //串联:第一个arg函数参数填充到%1,第二个arg填充到%2
    qDebug()<<strResult;

    这里使用了串联的两个 arg 函数,第一个 arg 函数是填充原有字符串 strPrefix 到 %1 位置,第二个 arg 函数填充 numHex 转换后的十六进制字符串到 %2 位置。第二个 arg 函数参数里的 0 是指不限制域宽,转换后的十六进制字符串该多长就多长,参数 16 是十六进制的意思。占位符本身是没有格式信息的,填充的具体内容由后面串联的 arg 函数决定,想填充原有字符串就填充原有的字符串,想填充转换后的数字字符串,那就填充数字字符串,非常方便。qDebug 打印的结果为:
    "Hex:  0x7f"

    double numReal = 123.78999;    
    strResult = QObject::tr("Fixed: %1 	 Scientific: %2").arg(numReal, 0, 'f').arg(numReal, 0, 'e', 3);
    qDebug()<<strResult;

    这里展示的是浮点数转成字符串,第一个 arg 函数将 numReal 以定点数形式('f ')转成字符串,0 代表不限制宽度,并填充到 %1 位置,没有设置显示精度(默认为 6 位)。第二个 arg 函数将 numReal 以科学计数法形式('e ')转成字符串,0 代表不限制宽度,3 代表显示精度为 3位。qDebug 打印的结果为:
    "Fixed: 123.789990  Scientific: 1.238e+02"

    int one = 1;     
    int two = 2;
    int three = 3;
    strResult = QObject::tr("%1 小于 %2,%1 小于 %3,%3 大于 %2 。").arg(one).arg(two).arg(three);
    qDebug()<<strResult;

    最后一段示例比较有意思,如果是 C 语言的 sprintf 要填充 6 个整型数,那必须用 6 个 %n ,不管有没有重复的。这里仅仅用了 %1、%2、%3,后面对应三个 arg 函数,每个 arg 函数都将参数里的变量转成数字字符串,并填充到正确的位置,而且可以重复填充。占位符的顺序也可以是乱的,规律就是第一个 arg 函数填充所有的 %1 ,第二个 arg 函数填充所有的 %2 ,第三个 arg 函数填充所有的 %3 ,以此类推。因此 qDebug 打印的结果就是:
    "1 小于 2,1 小于 3,3 大于 2 。"
    这正是我们希望看到的结果,可见 arg 函数的灵活性是传统 C 语言 sprintf 等无法比拟的,而且也更安全。学会 arg 函数用法,可应对各种复杂的格式化字符串转换。

    接下来简单看看 QString 的转出函数,可以将数字字符串转成各种类型的数值变量。对于整数类型,它们的函数声明都是类似的,以 int 为例:

    int QString::​toInt(bool * ok = 0, int base = 10) const

    toInt 函数第一个参数 ok 接收一个 bool 型的指针,用于反馈转换过程是否成功,第二个参数 base 是字符串对象里数字的进制基数,默认的 10 代表十进制,也可以设置二进制、八进制和十六进制等等。如果将 base 设置为 0,那么 toInt 函数将自动识别字符串对象里面的进制标识,对于 "0" 打头的自动按八进制转换,对于 "0x" 打头的自动按十六进制转换,其他情况都按十进制转换。如果转换出错,ok 指向的变量会设置为 false,返回值为 0 。

    对于浮点数字符串的转换,函数声明有些差异:

    double QString::​toDouble(bool * ok = 0) const

    这个不能指定进制基数,都是十进制的,支持定点数字符串和浮点数字符串转成数值。参数 ok 接收一个 bool 型变量的指针,用于反馈转换过程是否成功。如果转换失败,ok 指向的变量会设置为 false,返回值为 0。
            下面示范 QString 对象的转出函数:

    void Test_toValue() {
        bool bok = false;
        //dec
        QString strDec = QObject::tr("800");
        int nDec = strDec.toInt(&bok, 10);
        qDebug()<<nDec<<"	"<<bok;       //成功
        //Hex
        QString strHex = QObject::tr("FFFF");
        nDec = strHex.toInt(&bok, 10);  //基数错误,转换失败
        qDebug()<<nDec<<"	"<<bok;
    
        short nHexShort = strHex.toShort(&bok, 16);
        qDebug()<<nHexShort<<"	"<<bok; //FFFF正整数太大,超出范围,转换失败,没有负号 - 的都算正数。
    
        ushort nHexUShort = strHex.toUShort(&bok, 16);
        qDebug()<<nHexUShort<<"	"<<bok;//成功
    
        //自动转换
        QString strOct = QObject::tr("0077");
        int nOct = strOct.toInt(&bok, 0);
        qDebug()<<nOct<<"	"<<bok;  //字符 0 打头自动按八进制转
    
        QString strHexWithPre = QObject::tr("0xFFFF");
        int nHexWithPre = strHexWithPre.toInt(&bok, 0);
        qDebug()<<nHexWithPre<<"	"<<bok;   //字符 0x 打头自动按十六进制转
    
        int nDecAuto = strDec.toInt(&bok, 0);   //"800" ,自动按十进制
        qDebug()<<nDecAuto<<"	"<<bok;
    
        //浮点数转换
        QString strFixed = QObject::tr("123.78999");
        double dblFixed = strFixed.toDouble(&bok);
        qDebug()<<fixed<<dblFixed<<"	"<<bok;
        //科学计数法
        QString strScientific = QObject::tr("1.238e-5");
        double dblScientific = strScientific.toDouble(&bok);
        qDebug()<<scientific<<dblScientific<<"	"<<bok;
    }

    上面代码的运行结果这里不贴出来了,读者自己动手去试试。对于两个浮点数打印的行,里面带有流操作子,类似标准 C++ 控制台输出对象 cout 的操作子,fixed 是指按定点数显示,scientific 是指按科学计数法显示。

    3、字符串运算符

          QString 重载了多个对字符串有清晰意义的运算符,之前见过赋值运算符,可以将一个 QString 对象赋值给另一个 QString 对象。还有其他的比较运算符和中括号运符,先将其罗列如下:

    operator 描述
    = 赋值运算符,遵循隐式共享规则,在赋值的两个对象有变化时才真正复制数据块。
    += 追加。将运算符左边和右边字符串拼接后,赋值给左边对象。
    < 小于号。左边字符串字典序比右边的靠前时,表达式为真。
    <= 小于等于。左边字符串字典序比右边的靠前或相同时,表达式为真。
    == 等于。二者字典序是一致的时候为真。
    != 不等于。二者字典序不一样的时候为真。
    > 大于。左边字符串字典序比右边的靠后时,表达式为真。
    >= 大于等于。左边字符串字典序比右边的靠后或相同时,表达式为真。
    [] 类似数组取数的中括号,从指定位置取出 QChar 字符,另外还可以修改指定位置的 QChar 字符。
    + 拼接。这是个友元函数,将两个字符串拼接后返回全新的字符串对象。

           上面运算符的意义是一目了然的,主要解释一下赋值运算符 ( = ) 的隐式共享(Implicit  Sharing),在执行赋值时,真正的字符串数据拷贝没有发生,这是为了优化运行效率,避免大量数据的拷贝。隐式共享实现方式就是对数据块做引用计数,多一个对象赋值或参数、返回值拷贝时,引用次数加 1,这个赋值过程只需要设置一下数据指针和增加引用计数,不会真的拷贝大量数据,这种拷贝称为浅拷贝(shallow  copy)。
            在赋值的一个字符串发生变化,要做写入修改时,这个要发生变化的字符串会重新分配一块内存,将旧的数据拷贝到新的内存空间,并对其做相应的写入修改,这个过程叫深拷贝(deep copy),也可称为 copy-on-write(写时拷贝)。深拷贝会将旧的数据块引用计数减 1,然后将变化的字符串数据指向新空间,新空间引用计数加 1。
            如果发生字符串超出生命期销毁或清空,那么对应的数据引用计数减 1,当引用计数减到 0 时,数据块空间才会真的被释放。
           Qt 对象能够赋值或传参数、返回值的,一般都是采用隐式共享机制,所以 Qt 的参数和返回值传递运行效率是很高的。这也将信号和槽机制传递参数的效率大大提升了。面向对象的高级编程语言一般都支持类似的功能,比如 Java 和 Python 的垃圾回收机制,也是类似的。

            下面通过简单示例展示运算符的使用:

    void Test_operator()
    {
        // =
        QString strE1, strE2, strE3;
        strE1 = QObject::tr("abcd");
        strE2 = strE1;
        strE3 = strE2;
        //打印数据指针
        qDebug()<<strE1.data_ptr()<<"	"<<strE2.data_ptr()<<"	"<<strE3.data_ptr();
        //改变字符串,追加
        strE2.append( QObject::tr("1234") );
        //再次打印数据指针,谁修改了数据,谁的数据指针就变
        qDebug()<<strE1.data_ptr()<<"	"<<strE2.data_ptr()<<"	"<<strE3.data_ptr();
    
    
        // += 和 append 函数类似
        strE3 += QObject::tr("1234");
        qDebug()<<strE2<<"	"<<strE3;
    
    
        //比较 1 vs 2
        qDebug()<<"strE1 < strE2: "<<(strE1 < strE2);
        qDebug()<<"strE1 <= strE2: "<<(strE1 <= strE2);
        qDebug()<<"strE1 == strE2: "<<(strE1 == strE2);
        qDebug()<<"strE1 != strE2: "<<(strE1 != strE2);
        //2 vs 3
        qDebug()<<"strE2 > strE3"<<(strE2 > strE3);
        qDebug()<<"strE2 >= strE3"<<(strE2 >= strE3);
        qDebug()<<"strE2 == strE3"<<(strE2 == strE3);
    
    
        //类似数组取数
        qDebug()<<strE1[0];
        strE1[0] = QChar('?');  //修改
        qDebug()<<strE1;
    
    
        //拼接
        QString strPlus;
        strPlus = strE1 + strE2 + strE3;
        qDebug()<<strPlus;
    }

    我们来看看开头赋值运算符一段代码的运行结果,其他的比较简单就不贴了:
    0x13725b98          0x13725b98     0x13725b98
    0x13725b98          0x13741958     0x13725b98

            可以看到三个字符串对象都没有修改时,它们的数据指针是一样的,这就是浅拷贝的过程。在 strE2 发生改变时,strE2 会重新分配一块内存空间,并将旧的数据拷贝到新空间然后做对应的追加字符串操作,修改之后,strE2 的数据指针变了,由于 strE1 和 strE3这时没变化,它们数据指针还是一样的。在有写操作的时候,才会发生深拷贝,发生变化的字符串对象就会完全独立。后续的比较操作、中括号操作、拼接操作等代码读者可以自己去 试试,看看结果如何。

    4、子串查询与操作

            在面对文本处理时经常会遇到子串查询和操作,QString 类拥有大量这方面的函数,并且重载的也比较多,详细的函数可以通过查阅帮助文档获知,索引栏输入     QString 就能找到该类说明。下面简略讲解一些比较实用的函数(一般每个函数名只列一个声明,还有其他同名重载的请查帮助文档),这些函数不用死记硬背的,等到需要用的时候查一下文档就行了。

    QString & append(const QString & str)

    append追加子串到字符串尾部

    QString & prepend(const QString & str)

    prepend将子串加到字符串头部

    bool startsWith(const QString & s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const

    startsWith判断字符串(如“abcd”)是否以某个子串(如s是“ab”)打头,cs指判断时大小写是否敏感,返回bool。

    bool endsWith(const QString & s, Qt::CaseSensitivity cs = Qt::CaseSensitive) const

    endsWith 判断字符串(如 "abcd")是否以某个子串(如 s 是 "cd")结尾,cs 指判断时大小写是否敏感,返回 bool。

    bool contains(const QString & str, Qt::CaseSensitivity cs = Qt::CaseSensitive) const

    contains 判断字符串对象里是否包含子串 str ,参数 cs 指判断时大小写是否敏感,后面函数的 cs 都是一个意思,不重复说了。

    int count(const QString & str, Qt::CaseSensitivity cs = Qt::CaseSensitive) const

    count 对字符串对象里子串 str 出现的次数做统计,返回出现次数,如果没出现就返回 0。

    int indexOf(const QString & str, int from = 0, Qt::CaseSensitivity cs = Qt::CaseSensitive) const

    indexOf 从 from 指定的序号开始查询子串 str,返回查到的第一个 str 子串起始位置序号。查不到就返回 -1 。

    int lastIndexOf(const QString & str, int from = -1, Qt::CaseSensitivity cs = Qt::CaseSensitive) const

    lastIndexOf 默认从字符串尾部开始向前查询,设置 from 之后,从 from 开始的位置向前查询子串 str,返回最先匹配的 str 子串起始位置序号(搜索区间 0 ~ from ,子串起始序号最接近 from)。查不到就返回 -1 。

    QString & insert(int position, const QString & str)

    insert 是将子串 str 插入到 position 序号位置,子串 str 插入后的起始序号就是 position 。

    QString & remove(int position, int n)

    remove 从 position 开始的位置移除掉 n 个字符,如果 n 比 position 位置开始的子串长度大,后面的就会被全部移除。

    QString & remove(const QString & str, Qt::CaseSensitivity cs = Qt::CaseSensitive)

    这个重载的 remove 函数将匹配的所有子串 str 都从字符串里面移除掉,拿来消除空格之类的字符比较好使。

    QString & replace(int position, int n, const QString & after)

    replace 将从 position 序号开始的 n 个字符的子串替换成 after 字符串。

    QString & replace(const QString & before, const QString & after, Qt::CaseSensitivity cs = Qt::CaseSensitive)

    这个重载的 replace 将字符串里出现的所有子串 before 全部替换为新的 after。

    QStringList split(QChar sep, SplitBehavior behavior = KeepEmptyParts, Qt::CaseSensitivity cs = Qt::CaseSensitive) const
    QStringList split(const QString & sep, SplitBehavior behavior = KeepEmptyParts, Qt::CaseSensitivity cs = Qt::CaseSensitive) const

    split 用字符或子串 sep 切分当前字符串内容,然后将切分的所有子串以 QStringList 列表形式返回,可以从返回的列表提取各个子串。behavior 是分隔模式,是否保留空白字符区域等。

    QString section(QChar sep, int start, int end = -1, SectionFlags flags = SectionDefault) const
    QString section(const QString & sep, int start, int end = -1, SectionFlags flags = SectionDefault) const

    section 函数首先将字符串按照字符或子串 sep 分成段落,类似 split 划分,但 section 只返回第 start 段到第 end 段之间的内容。如果没指定 end 就一直包含到最后。flags 参数影响划分行为,如大小写敏感、是否忽略空白区域等。

    QString left(int n) const

    left 返回字符串左边 n 个字符构成的子串。

    QString right(int n) const

    right 返回字符串右边 n 个字符构成的子串。

    QString mid(int position, int n = -1) const

    mid 返回从 position 位置开始的 n 个字符构成的子串。不设置 n 的话就包含到末尾。

    QString & fill(QChar ch, int size = -1)

    fill 用字符 ch 填充当前字符串,如果不指定 size ,就把所有的字符都填成 ch 字符。如果指定正数 size,字符串长度被重置为 size 大小,里面依然全是 ch 字符。

    QString repeated(int times) const

    将当前字符串重复拼接 times 次数,返回新的重复串。

    QString trimmed() const

    trimmed 剔除字符串头部和尾部的空白字符,包括 ' ', ' ', 'v', 'f', ' ', ' '  。字符串中间的空白不处理。

    QString simplified() const

    simplified 剔除字符串里出现的所有空白字符,包括 ' ', ' ', 'v', 'f', ' ', ' '  。两端和中间的都剔除。

    void truncate(int position)

    truncate 是从 position 序号开始截断字符串,只保留 0 ~ position-1 位置的字符串,position 位置被设为  NULL,后面的全移除。

           关于子串处理的函数就罗列上面这些,等到用的时候再查文档就行了。下面示范一个测试函数,挑几个函数出来用用,看看效果如何。

    void Test_substring() {
        QString strOne = QObject::tr("abcd");
        QString strThree = strOne.repeated(3); //abcd 重复三次
        qDebug()<<strThree.isEmpty();   //是否为空
        qDebug()<<strThree.length()<<"	"<<strThree.size(); //都是长度
        qDebug()<<strThree;
    
    
        //子串查询
        qDebug()<<strThree.contains(strOne);    //是否包含
        qDebug()<<strThree.count(strOne);       //包含几个
        qDebug()<<strThree.startsWith(strOne);  //打头的子串
        qDebug()<<strThree.indexOf(strOne);     //左边开始的子串位置
        qDebug()<<strThree.lastIndexOf(strOne); //右边开始的子串位置
    
    
        //剔除两端的空白
        QString strComplexFileName = QObject::tr("   /home/user/somefile.txt  		 ");
        QString strFileName = strComplexFileName.trimmed();
        qDebug()<<strFileName;
        if(strFileName.endsWith( QObject::tr(".txt") ))
        {
            qDebug()<<"This is a .txt file";
        }
        //分隔子串
        QStringList subsList = strFileName.split(QChar('/'));
        for(int i=0; i<subsList.length(); i++)  //打印各个子串
        {
            qDebug()<<i<<"	"<<subsList[i];
        }
        //获取段落
        QString subsections = strFileName.section(QChar('/'), 2, 3);
        qDebug()<<subsections;
    }

    测试函数前半截的代码意义是比较简单的,就不多说了,看看后面文件名部分的输出结果,strComplexFileName 剔除两端的空白区域之后,得到 strFileName:
    "/home/user/somefile.txt"
    这个新的 strFileName 以 ".txt" 结尾,所以判断为文本文件:This is a .txt file
    然后以字符 '/' 分隔文件名为子串:

             0        ""
             1        "home"
             2        "user"
             3        "somefile.txt"
            注意文件名被拆成了四个子串,而不是三个,第一个 '/' 左边没有东西,也会被切分为一个独立的子串 "" ,就是空串。
            切分后的子串保存在 QStringList 里面,这就像存储多个 QString 对象的数组,可以直接用 [] 操作各个子串。

         最后的取段落函数,它分隔段落的方法和 split 函数类似的,取出序号从 2 到 3 的段落内容,这些段落内部之间的分隔符是保留的,段落函数返回的是一个 QString 对象,而不是列表:"user/somefile.txt"
         QString 还有其他的如字母大小写转换函数,toUpper() 函数是转成全大写,toLower() 函数是全部转成小写。clear() 函数是清空字符串,这些函数作用一目了然,就不多作介绍了。

    5、QTextStream配合字符串使用

            对于熟悉 C++ 里面 iostream 控制台输入输出流、fstream 文件数据流和 sstream 内存数据流的程序员,如何在 Qt 里面使用类似的流操作呢?之前示范过 qDebug() 有流操作子 fixed 和 scientific,这是调试输出流。对于控制台输入输出流、文件流、内存流,Qt 统一用强大的 QTextStream 来支持,本小节简单介绍利用 QTextStream 对 QString 做内存流的输入输出处理,以后的“文件和数据流”章节还会更多地介绍 QTextStream 。

          QTextStream 配合 QString 使用的过程非常简单,QTextStream 构造函数接受 QString 对象的指针,并且支持类似 cout 流的操作子,我们直接来看示例代码:

    void Test_QTextStream() {
        //内存输出流
        QString strOut;
        QTextStream streamOut(&strOut);
        //打印多种进制数字
        streamOut<<800<<endl;
        streamOut<<hex<<127<<endl;
        streamOut<<oct<<63<<endl;
        //还原为十进制
        streamOut<<dec;
    
        //设置域宽和填充字符
        streamOut<<qSetFieldWidth(8)<<qSetPadChar('0')<<800;
        //还原默认域宽和填充
        streamOut<<qSetFieldWidth(0)<<qSetPadChar(' ')<<endl;
    
        //设置精度
        streamOut<<qSetRealNumberPrecision(3)<<fixed<<123.789999<<endl;
        streamOut<<qSetRealNumberPrecision(6)<<scientific<<123.789999<<endl;
    
        //打印字符串和数字混搭
        streamOut<<QObject::tr("7*7 == ")<<7*7<<endl;
        //显示现在的字符串对象
        qDebug()<<strOut;
    
        //内存输入流
        QString strIn = QObject::tr("800  abcd  123.789999");
        QTextStream streamIn(&strIn);
        int numDec = 0;
        QString strSub;
        double dblReal = 0.0;
        //输入到变量里
        streamIn>>numDec>>strSub>>dblReal;
        //显示
        qDebug()<<numDec;
        qDebug()<<strSub;
        qDebug()<<fixed<<dblReal;   //定点数显示
    }

    第一块代码是打印多种进制的数值,流操作子和 cout 是一样的,hex 是十六进制,oct 是八进制,dec 是十进制。需要注意的是用完各种进制之后,要记得把流的进制还原为 dec  十进制,否则之前设置的进制会一直持续生效,直到被重新设置为止。这部分显示的结果(双引号是字符串对象 strOut 起始标志):

    "800

    7f

    77

    第二块代码是设置显示域宽 qSetFieldWidth 、填充字符 qSetPadChar,用完之后要还原,否则域宽和填充字符会持续生效。这部分显示结果为:

    00000800
    第三块是设置精度 qSetRealNumberPrecision 并打印浮点数到字符串对象 strOut 里面,定点数操作子为 fixed,科学计数法操 作子为 scientific,这部分结果显示为:

    123.790    

    1.237900e+02

    第四块是打印字符串和数值混搭,都输出到字符串对象 strOut ,然后将 strOut 显示到调试输出面板里(双引号是字符串对象结尾):

    7*7 == 49    

    "

    该测试函数最后是将字符串 "800  abcd  123.789999" 作为输入源,然后把数据输入到整型 numDec、字符串对象 strSub 和浮点数 dblReal 里,得到的结果显示为:

    800

    "abcd"

    123.789999
    将字符串对象作为输入源使用也很方便,灵活运用 QTextStream 对于字符串操作也是很有益处的。

            对于输出流,除了三个 q 打头的操作子 qSetFieldWidth 、qSetPadChar、qSetRealNumberPrecision  ,其他的操作子名字和 cout 的操作子名字是基本一样的。

    6、QString示例代码

            QString 示例代码就是上面 6 个测试函数的合集,然后融入到一个简单 Qt 程序里。6 个测试函数分别为 Test_setNum()、Test_arg()、Test_toValue()、Test_operator()、Test_substring()、Test_QTextStream(),这些代码不重复贴了,上面都有,测试这些函数时,每次只启用一个,其他的注释掉,慢慢试,不用全记住的,可以分几次学。示例代码下载: https://lug.ustc.edu.cn/sites/qtguide/QtProjects/ch03/testqstring/testqstring.7z      
            照旧把代码解压到如 E:QtProjectsch03 estqstring 文件夹里。下面主要把包含头文件和 main 函数贴出来:

    //testqstring.cpp
    #include <QApplication>
    #include <QTextBrowser>
    #include <QDebug>
    #include <QTextStream>
    
    void Test_setNum()
    {
    ...
    }
    
    void Test_arg()
    {
    ...
    }
    
    void Test_toValue()
    {
    ...
    }
    
    void Test_operator()
    {
    ...
    }
    
    void Test_substring()
    {
    ...
    }
    
    void Test_QTextStream()
    {
    ...
    }
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        QString strText = QObject::tr("测试字符串类 QString");
        QTextBrowser tb;
        tb.setText(strText);
        tb.setGeometry(40, 40, 400, 300);
        tb.show();
    
    
        //setNum
        Test_setNum();
    
    
        //arg
        //Test_arg();
    
    
        //toValue
        //Test_toValue();
    
    
        //operator
        //Test_operator();
    
    
        //substring
        //Test_substring();
    
    
        //QTextStream
        //Test_QTextStream();
    
    
        return a.exec();
    }

     这个 main 代码和上一节的类似,主界面是文本显示框,测试函数的结果打印到 Qt Creator 输出面板,将 6 个函数逐个测试一下,并注意观察输出面板的信息,加深一下对本节知识的印象。

  • 相关阅读:
    整理 修改功能测试点
    centos 修改yum镜像源
    修改Docker容器的时间和宿主机一致
    Postgre Invalid command l;. Try ? for help.
    RGB渐变算法(JavaScript)
    docker postgre&postgis
    activiti+spring boot 报错: java.lang.NoClassDefFoundError: org/springframework/core/log/LogMessage
    Docker 创建 Postgre
    Jenkins+Gitlab+Maven 远程部署
    docker安装centos并ssh连接
  • 原文地址:https://www.cnblogs.com/wyxsq/p/5082206.html
Copyright © 2020-2023  润新知