一、前言
“合抱之木,生於毫末;九層之台,起於壘土;千里之行,始於足下”,上一章我們知道了如何使用Qt創建簡單的示例程序,了解了最基本的Qt框架,在進一步學習Qt框架和消息機制前,我們應該對Qt本身有一個更細致的了解,這個了解就是Qt的模板庫、工具類和控件。
二、Qt的模板庫、工具類和控件
2.1 字符串類
1、字符串的操作
我們了解字符串的操作就是要了解字符串主要有哪些操作符,Qt基於C++繼承和強化了string的功能,結構類型為QString,QString提供了一個二元的“+”和“+=”操作符,其中“+=”操作符功能和append函數方法具有同樣的功能,是現在一個字符串末尾追加另一個字符串,學習時可基於C++中String類進行比較。
1 QString str1="nihao"; 2 QString str2="Qt"; 3 4 str1+=str2; //str1="nihaoQt" 5 str1=str1+str2; //str1="nihaoQtQt" 6 str1.append(str2); //str1="nihaoQtQtQt" 7 str1.append("yes"); //str1="nihaoQtQtQtyes"
Qt組合字符串的另一個函數楇 QString::sprintf(),此函數支持的格式定義符和C++庫中的函數sprintf定義的一樣。Qt還提供了另外一種方便的字符串組合方式,使用QString::arg()函數,此函數的重載可以處理多種數據類型,一些重載具有額外的參數對字段的寬度、數字基數或者浮點數精度進行控制。相對於sprintf來說,srg是一個比較好的解決方案,因為它類型安全,完全支持Unicode,並且允許改變"%n"參數的順序。
1 QString str; 2 str=QString("%1 was born in %2 .").arg("Rimond").arg(1990); 3 //str="Rimond was born in 1990".
此外,QString也提供了一些其他組合字符串的方法
| 函數名稱 | 函數功能 |
| insert() | 在原字符串特定的位置插入另一個字符串 |
| prepend() | 在原字符串的開頭插入另一個字符串 |
| replace() | 用指定的字符串代替原字符串中的某些字符 |
為了解決特定場景比如去除一個字符串兩端的空白(空白字符包括回車字符“\n”,換行字符“\r”,制表符“\t”和空格字符等),QString提供了特定的函數。
| 函數名稱 | 函數功能 |
| trimmed() | 移出字符串兩端的空白字符 |
| simplified() | 移除字符串兩端的空白字符,使用單個空格字符“ ”代替字符串中出現的空白字符 |
1 QString str=" Hello \t QT \n ! "; 2 str=str.trimmed(); 3 4 //str=" Hello \t to \n you! " 5 //如果使用str=str.simplified(),str的結果是“Hello Qt !”
2、查詢字符串數據
查詢字符串數據有多種樣式。
(1) QString::startsWith()判斷一個字符串是否以某個字符串開頭。此函數具有兩個參數,第一個參數指定了一個字符串,第二個參數指定是否大小寫敏感(默認大小寫敏感)。
1 QString str="Hello Qt!"; 2 str.startsWith("Hello",Qt::CaseSensitive); //返回真 3 str.startsWith("Qt",Qt::caseSenstive); //返回假
(2) QString::endwith()類似於QString::startswith(),它用來判斷一個字符串是否以某個字符串結尾。
(3) QString::contains()判斷一個指定的字符串是否出現過。
1 QString str ="Hello QT!"; 2 str.contains("QT",Qt::CaseSensitive); //返回真
(4) QString類還重載了多種用於比較的操作符,用法可參照C++ string類中重載的比較操作符。此外,QString類增加了兩個特殊函數。
localeAwareCompare(const QString&,const QString&):靜態函數,比較前后兩個字符串,如果前面字符串小於后面字符串,則返回值為負整數;如果等於則返回0;如果大於則返回值為正整數,該函數用於比較基於本地字符集,而且楇平台相關的,通常該函數用於向用戶顯示一個有序的字符串列表。
compare(const QString&,const QString&::CaseSensitivity):該函數可以指定是否進行大小寫的比較,而大小寫的比較楇完全基於字符的Unicode編碼值的,而且是非常快的,返回值類似於localeAwareCompare函數。
3、字符串的轉換
由於Qt的跨平台型,可移植性等特點反映了其在字符串上的靈活性,QString類提供了豐富的轉換函數,可以實現講一個字符串轉換為數值類型或者其他的字符編碼集。
(1) QString::toInt()函數實現了將字符串轉換為整型數值,類似的函數還有toDouble()、toFloat()、toLong()、toLongLong()等。
1 QString str="125"; 2 bool ok; 3 int hex=str.toInt(&ok,16); //ok=true,hex=293 4 int dec=str.toInt(&ok,10); //ok=true,dec=125
可以看到上面的16和10分別代表了進制,ok用於傳遞一個地址,表示轉換結果。
(2) QString提供的字符串編碼集的轉換函數將會返回一個const char*類型版本的QByteArry,即構造函數QByteArry(const char*)構造的QByteArry對象。QByteArry類具有一個字符數組,它既可以存儲原始字節(raw bytes),也可以存儲傳統的以“\0”結尾的8位的字符串。在Qt中,使用QByteArry比使用const char*更為方便,且QByteArry也支持隱式共享,轉換函數有以下幾種。
| 函數名稱 | 函數功能 |
| toAscii() | 返回一個ASCII編碼的8位字符串 |
| toLatin1() | 返回一個Latin-1編碼的8位字符串 |
| toUtf8() | 返回一個utf-8編碼的8位字符串(utf-8是ASCII碼的超集,它支持整個Unicode字符集) |
| toLocal8Bit() | 返回一個系統本地編碼的8位字符串 |
1 #include <QCoreApplication> 2 #include <QByteArray> 3 #include <QString> 4 #include <QtDebug> 5 6 int main(int argc, char *argv[]) 7 { 8 QCoreApplication a(argc, argv); 9 QString str="Hello Qt!"; 10 QByteArray ba=str.toLatin1(); 11 qDebug() << ba; 12 ba.append("Hello,world!"); 13 qDebug() << ba.data(); 14 return a.exec(); 15 }
運行結果如下

提示:Qt5中去除了toAscii()函數,改用toLatin1()函數就可了,再就是debug環境下別忘了添加qDebug的頭文件。
qDebug類似於cout格式輸出。
附加:NULL字符串和空(empty)字符串的區別
一個NULL字符串就是使用QString的默認構造函數或者使用(const char*)0作為參數的構造函數創建的QString字符串對象;而一個空字符串是一個大小為0的字符串。一個NULL字符串一定是一個空字符串,而一個空字符串未必是一個NULL字符串。
驗證方式
1 QString().isNull(); //true 2 QString().isEmpty(); //true 3 QString("").isNull(); //false 4 QString("").isEmpty(); //true
2.2 容器類
同C++的標准模板庫中的容器類作比較,Qt提供了一組通用的基於模板的容器類。這些容器更輕量、更安全並且更容易使用,同時還在速度、內存消耗和內聯等方面進行了優化。
對於Qt容器中的存儲數據類型也有要求,這些數據必須是可以賦值的數據類型,換句話說就是該數據類型必須有一個默認的構造函數(無參數構造函數)、一個復制構造函數(拷貝構造)和一個賦值操作符函數。
其實這樣的數據類型包含了通常我們使用的大多數數據類型,比如基本的數據類型(int和double等)和Qt的一些數據類型(如QString、QDate、QTime等)。但是,Qt的QObject及其他的子類(如QWidget和Qdialog等)是不可以存儲在容器中的。
1 QList<QToolBar> list; //error 2 QList<QToolBar*>list; //ok
上述代碼中,第一種是錯誤的。因為這些類(QObject及其他的子類)沒有復制構造函數和賦值構造函數。解決的辦法就是使用指向這些類的指針來作為存儲類型。
另外的一點就是Qt的容器類是可以嵌套的,這一點C++的STL也是可以做到的。
1 QHase<QString,QList<double> > ;
Qt的容器類為遍歷其中的內容提供了以下兩種方法:
(1)Java風格的迭代器
(2)STL風格的迭代器,能夠同Qt和STL的通用算法一起使用,並且在效率上也略勝一籌。
下面我們將通過具體的容器類來具體的了解Qt容器類的功能和使用方法。
2.2.1 QList類、QLinkedList類和QVector類
首先我們來看一下這三種容器類的操作時間復雜度。
| 容器類 | 查找(訪問) | 插入 | 頭部添加 | 尾部添加 |
| QList | O(1) | O(n) | Amort.O(1) | Amort.O(1) |
| QLinkedList | O(n) | O(1) | O(1) | O(1) |
| QVector | O(1) | O(n) | O(n) | Amort.O(1) |
注:其中Amort.O(1)表示,如果僅完成一次操作,可能會有O(n)行為;但是如果完成多次操作(比如n次),平均結果將會是O(1)。
1、QList類
QList<T>是我們會經常使用的容器類,它存儲給定數據類型T的一列數值。繼承自QList類的子類有QItemSelection、QQueue、QSignalSpy及QStringList和QTestEvenList。QList不僅提供了可以在列表進行追加的QList::append()和QList::prepend()函數,還提供了在列表中間完成插入操作的函數QList::insert()。相對於任何其他的Qt容器類,為了使可執行代碼盡可能的少,QList被高度優化。
QList<T>維護了一個指針數組,該數組存儲的指針指向QList<T>存儲的列表項的內容。因此,QList<T>提供了基於下標的快速訪問。對於不同的數據類型,QList<T>采用不同的存儲策略,主要有以下幾種。
(1)如果T是一個指針類型或者指針大小的基本類型(就是說該基本類型占有的字節數和指針類型占有的字節數相同),QList<T>會將數值直接存儲在它的數組中。
(2)如果QList<T>存儲對象的指針,則該指針指向實際存儲的對象。
1 #include <QCoreApplication> 2 #include <QString> 3 #include <QtDebug> 4 5 int main(int argc, char *argv[]) 6 { 7 QCoreApplication a(argc, argv); 8 QList<QString> list; 9 //{}用來表示作用域 10 { 11 QString str("this is a test string"); 12 list<<str; 13 } 14 qDebug()<<list[0]<<"Hello Qt!"; 15 return a.exec(); 16 }

其中list<<str;中的<<是通過<<操作符將一個QString字符串存儲在該列表中。
(2)QLinkedList類
QLinkedList<T>是一個鏈式列表,它以非連續的內存塊保存數據。QLinkedList<T>不能使用下標,只能使用迭代器訪問他的數據項。與QList相比,當對一個很大的列表進行插入操作時,QLinkedList具有更高的效率。
(3)QVector類
QVector<T>在相鄰的內存中存儲給定數據類型T的一組數值。在一個QVector的前部或者中間位置進行插入操作的速度是很慢的,這是因為這樣的操作將導致內存中大量的數據被移動,這事有QVector存儲數據的方式決定的。
QVector<T>既可以使用下標訪問數據項,也可以使用迭代器訪問數據項。繼承自QVector類的子類有QPolygon、QPolygonF、QStack。
注:其實平常我們用QList就可以,它既可以是存儲指向存儲類型的指針,也可以對小的數據進行直接存儲。
附:STL風格迭代器遍歷容器
對於每一個容器類,Qt都提供了兩種類型的STL風格迭代器數據類型:一種提供只讀訪問;另一種提供讀寫訪問。由於只讀類型的迭代器的運行速度要比讀寫迭代器的運行速度快,建議盡可能使用只讀類型的迭代器。
| 容器類 | 只讀迭代器類 | 讀寫迭代器類 |
| QList<T>,QQueue<T> | QList<T>::const_itertor | QList<T>::itertor |
| QLinkedList<T> | QLinkedList<T>::const_itertor | QLinkedList<T>::itertor |
| QVector<T>,QStack<T> | QVector<T>::const_itertor | QVector<T>::itertor |
1 #include <QCoreApplication> 2 #include <QString> 3 #include <QtDebug> 4 5 int main(int argc, char *argv[]) 6 { 7 QCoreApplication a(argc, argv); 8 QList<int> list; 9 for(int j=0;j<10;j++) 10 list.insert(list.end(),j); 11 QList<int>::iterator i; 12 for(i=list.begin();i!=list.end();i++) 13 { 14 printf("%d ",*i); 15 *i=(*i)*10; 16 } 17 QList<int>::const_iterator ci; 18 for(ci=list.constBegin();ci!=list.constEnd();ci++) 19 printf("%d ",*ci); 20 return a.exec(); 21 }

注:如果使用qDebug輸出的話,我認為每次迭代都會生成一個qDebug的臨時對象,並且自動添加換行,所以我改成了printf(偷懶),另外關於容器類使用方法可以參考C++ STL的使用方法。
2.2.2 QMap類和QHash類
QMap類和QHash類具有非常類似的功能,他們的差別僅在於:
1、QHash具有比QMap更快的查找速度
2、QHash以任意順序存儲數據項,而QMap總是按照Key順序存儲數據;
3、QHash的鍵類型Key必須提供operator==()和一個全局的qHash(Key)函數,而QMap的鍵類型Key必須提供operator<()函數。
QMap和QHash的時間復雜度比較

1、QMap類
QMap<key,T>提供了一個從類型Key的鍵到類型為T的值的映射。通常,QMap存儲的數據類型是一個鍵對應一個值,並且按照鍵Key的次序存儲數據。為了能夠支持一鍵多值的情況,QMap提供了QMap<key,T>::insertMulti()和QMap<key,T>::values()函數。存儲一鍵多值的數據時,也可以使用QMultiMap<key,T>容器,他繼承自QMap。
2、QHash類
QHash<key,T>具有與QMap幾乎完全相同的API。QHash維護一張哈希表,哈希表的大小與QHash的數據項的數目相適應。QHash作為存放數據的容器。QHash也可以存儲一鍵多值形式的數據,他的子類QMultiHash<key,T>實現了一鍵多值的語義。
附:STL風格迭代器遍歷容器
和上面說到的容器相同,Qt也提供了兩種類型的STL風格迭代器數據類型。
| 容器類 | 只讀迭代器 | 讀寫迭代器 |
| QMap<key,T>,QMultiMap<key,T> | QMap<key,T>::const_itertor | QMap<key,T>::itertor |
| QHase<key,T>,QMultiHase<key,T> | QHash<key,T>::const_itertor | QHash<key,T>::itertor |
1 #include <QCoreApplication> 2 #include <QString> 3 #include <QtDebug> 4 int main(int argc, char *argv[]) 5 { 6 QCoreApplication a(argc, argv); 7 QMap<QString,QString>map; 8 map.insert("lili","1990"); 9 map.insert("wangli","1992"); 10 map.insert("zhangli","1989"); 11 QMap<QString,QString>::const_iterator i; 12 for(i=map.constBegin();i!=map.constEnd();i++) 13 qDebug()<<" "<<i.key()<<" "<<i.value(); 14 QMap<QString,QString>::iterator mi; 15 mi=map.find("lili"); 16 if(mi!=map.end()) 17 mi.value()="1995"; 18 QMap<QString,QString>::const_iterator modi; 19 for(modi=map.constBegin();modi!=map.constEnd();modi++) 20 qDebug()<<" "<<modi.key()<<" "<<modi.value(); 21 return a.exec(); 22 }

好了,關於字符串類和容器類先說這么多,如有錯誤,歡迎指正。![]()
