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<<" \t 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 \t 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<<" \t 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 \t 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<<"\t"<<bok;       //成功
    //Hex
    QString strHex = QObject::tr("FFFF");
    nDec = strHex.toInt(&bok, 10);  //基數錯誤,轉換失敗
    qDebug()<<nDec<<"\t"<<bok;

    short nHexShort = strHex.toShort(&bok, 16);
    qDebug()<<nHexShort<<"\t"<<bok; //FFFF正整數太大,超出范圍,轉換失敗,沒有負號 - 的都算正數。

    ushort nHexUShort = strHex.toUShort(&bok, 16);
    qDebug()<<nHexUShort<<"\t"<<bok;//成功

    //自動轉換
    QString strOct = QObject::tr("0077");
    int nOct = strOct.toInt(&bok, 0);
    qDebug()<<nOct<<"\t"<<bok;  //字符 0 打頭自動按八進制轉

    QString strHexWithPre = QObject::tr("0xFFFF");
    int nHexWithPre = strHexWithPre.toInt(&bok, 0);
    qDebug()<<nHexWithPre<<"\t"<<bok;   //字符 0x 打頭自動按十六進制轉

    int nDecAuto = strDec.toInt(&bok, 0);   //"800" ,自動按十進制
    qDebug()<<nDecAuto<<"\t"<<bok;

    //浮點數轉換
    QString strFixed = QObject::tr("123.78999");
    double dblFixed = strFixed.toDouble(&bok);
    qDebug()<<fixed<<dblFixed<<"\t"<<bok;
    //科學計數法
    QString strScientific = QObject::tr("1.238e-5");
    double dblScientific = strScientific.toDouble(&bok);
    qDebug()<<scientific<<dblScientific<<"\t"<<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()<<"\t"<<strE2.data_ptr()<<"\t"<<strE3.data_ptr();
    //改變字符串,追加
    strE2.append( QObject::tr("1234") );
    //再次打印數據指針,誰修改了數據,誰的數據指針就變
    qDebug()<<strE1.data_ptr()<<"\t"<<strE2.data_ptr()<<"\t"<<strE3.data_ptr();


    // += 和 append 函數類似
    strE3 += QObject::tr("1234");
    qDebug()<<strE2<<"\t"<<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 剔除字符串頭部和尾部的空白字符,包括 '\t', '\n', '\v', '\f', '\r', ' '  。字符串中間的空白不處理。

QString simplified() const

simplified 剔除字符串里出現的所有空白字符,包括 '\t', '\n', '\v', '\f', '\r', ' '  。兩端和中間的都剔除。

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()<<"\t"<<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  \t\t ");
    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<<"\t"<<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:\QtProjects\ch03\testqstring 文件夾里。下面主要把包含頭文件和 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 個函數逐個測試一下,並注意觀察輸出面板的信息,加深一下對本節知識的印象。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM