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 個函數逐個測試一下,並注意觀察輸出面板的信息,加深一下對本節知識的印象。
