1 #include <iostream> 2 #include <string> 3 4 int main() 5 { 6 using namespace std; 7 8 cout << "1 --- string(const char* s):將string對象初始化為s指向的C風格字符串" << endl; 9 string one("benxintuzi_1"); 10 cout << "one = " << one << endl; // 重載了 << 11 12 cout << "2 --- string(size_type n, char c):以字符c初始化包含n個字符的string對象" << endl; 13 string two(20, '$'); 14 cout << "two = " << two << endl; 15 16 cout << "3 --- string(const string& str):復制構造函數初始化" << endl; 17 string three(one); 18 cout << "three = " << three << endl; 19 20 cout << "4 --- string():默認構造函數" << endl; 21 string four; 22 four += one; // 重載了 += 23 four = two + three; // 重載了 + 24 cout << "four = " << four << endl; 25 26 cout << "5 --- string(const char* s, size_type n):用s指向的C風格字符串的前n個字符初始化string對象" << endl; 27 char s[] = "benxintuzi"; 28 string five(s, 5); 29 cout << "five = " << five << endl; 30 31 cout << "6 --- string(Iter begin, Iter end):用[begin, end)區間內的字符初始化string對象" << endl; 32 string six(s + 2, s + 5); 33 cout << "six = " << six << endl; 34 35 cout << "7 --- string(const string& str, string size_type pos = 0, size_type n = npos):將string對象初始化為str中從pos開始的n個字符" << endl; 36 string seven(four, 5, 2); 37 cout << "seven = " << seven << endl; 38 39 /* 40 cout << "8 --- string(string&& str) noexcept:將string對象初始化為str,並可能修改str,移動構造函數[C++11新特性]" << endl; 41 42 cout << "9 --- string(initializer_list<char>il:將string對象初始化為初始化列表il中的字符[C++11新特性]" << endl; 43 string nine = {'t', 'u', 'z', 'i'}; // or string nine{'t', 'u', 'z', 'i'}; 44 cout << "nine = " << nine << endl; 45 */ 46 }
[1.2] string 的輸入 對於 C 風格字符串,有3種輸入方式 char info[100]; cin >> info; // 從流中讀一個單詞存放到info中 cin.getline(info, 100); // 從流中讀入一行存放到info,刪除流中的\n cin.get(info, 100); // 從流中讀入一行存放到info,保留流中的\n 對於 string 對象,有2種輸入方式 string stuff; cin >> stuff; // 從流中讀取一個單詞 getline(cin, stuff); // 從流中讀取一行,刪除流中的\n getline可以指定用於分割單詞的分隔符,將其放到第三個參數中,如: cin.getline(info, 100, ‘:’); // C 風格字符串使用這種形式 getline(stuff, ‘:’); // string 類使用這種形式 string與傳統 C 風格字符串的比較: 使用string類輸入時,不需要具體指定輸入的字符個數,其可以自動匹配字符串大小,使用C風格字符串必須顯示指定,因為C風格字符串使用的是istream類的方法,而string版本使用的是獨立的函數,因此形式上有所區別,這種區別在其他運算符上也有體現,如>>操作符: C 風格字符串使用:cin.operator>>(fname),而string風格為:operator(cin, fname); 說明: string版本的getline()在如下三種情況下結束讀取: 1 到達文件末尾:此時輸入流中的eofbit置位; 2 遇到分割符:默認為\n,此時,刪除流中的\n; 3 讀取的字符個數達到最大值[min(string::npos, 空閒內存)],此時將輸入流的failbit置位。 在輸入流中有一個統計系統,用於跟蹤流的狀態: 1 檢測到文件末尾,設置eofbit寄存器; 2 檢測到錯誤,設置failbit寄存器; 3 檢測到無法識別的故障,設置badbit寄存器; 4 檢測到一切順利,設置goodbit寄存器。 string 類對全部 6 個關系運算符進行了重載,對於每個關系運算符,又進行了3種重載,分別是: string 對象與 string 對象; string 對象 C 風格字符串; C 風格字符串與 string 對象。 [2] 智能指針(smart pointer) 智能指針是行為類似於指針,但卻是類。其可以幫助管理動態分配的內存,有三種可選:分別為auto_ptr\unique_ptr\shared_ptr。auto_ptr是C++ 98提供的,C++ 11已經拋棄了(雖然已經被拋棄,但是仍然被大量使用)。 小知識: 為何新標准要放棄auto_ptr?理由如下: 假設如下賦值: auto_ptr<string> ps1(new string(“benxintuzi”)); auto_ptr<string> ps2; ps2 = ps1; 如果ps2和ps1是普通指針,那麼他們將同時指向一塊堆內存,那麼auto_ptr類型的指針若同時指向一塊堆內存,那麼就會調用兩次delete,這個太可怕了,因此,auto_ptr采用的策略是如果發生賦值,那麼就會令ps1為0,同樣unique_ptr也是采用這種策略,但是更加嚴格。 如下,當ps2 = ps1,即ps1置空時,如果發生調用*ps1,那麼相當於調用了一個空指針指向的對象,在auto_ptr情況下,可以編譯通過,在運行時會出錯;但在unique_ptr情況下,不會讓你通過編譯的。 結論就是unique_ptr比auto_ptr更安全一些。 shared_ptr對於每個堆對象,其維護一個指向同一對象的引用計數,只有當引用計數為0時才調用delete,因此可以很好地支持智能指針賦值操作。 要創建智能指針,必須包含頭文件memory,然後使用模板語法實例化所需類型的指針,例如auto_ptr包含如下構造函數: template<class X> class auto_ptr { public: // throw()意味著不引發異常【C++ 11也將throw()拋棄了】 explicit auto_ptr(X* p = 0) throw(); ... } 因此,智能指針的使用非常簡單,只需要用特定類型的指針初始化即可,如: auto_ptr<double> pd(new double); 說明: 智能指針都放在名稱空間std中(注意shared_ptr和unique_ptr都是C++ 11新增的,舊時的編譯器可能不支持)。
1 #include <iostream> 2 #include <string> 3 #include <memory> 4 5 class Report 6 { 7 public: 8 Report(const std::string s) : str(s) 9 { 10 std::cout << "Object created!" << std::endl; 11 } 12 ~Report() 13 { 14 std::cout << "Object deleted!" << std::endl; 15 } 16 void comment() const 17 { 18 std::cout << "str = " << str << std::endl; 19 } 20 21 private: 22 std::string str; 23 }; 24 25 int main() 26 { 27 std::auto_ptr<Report> pa(new Report("using auto_ptr")); 28 pa->comment(); 29 30 std::shared_ptr<Report> ps(new Report("using shared_ptr")); 31 ps->comment(); 32 33 std::unique_ptr<Report> pu(new Report("using unique_ptr")); 34 pu->comment(); 35 36 return 0; 37 } 38 39 /** output */ 40 Object created! 41 str = using auto_ptr 42 Object created! 43 str = using shared_ptr 44 Object created! 45 str = using unique_ptr 46 Object deleted! 47 Object deleted! 48 Object deleted!
注意: 絕對不要將非堆內存指針賦予智能指針,否則雖然可以通過編譯,但是情況並非總是樂觀的,這就相當於delete了非堆內存,容易引發難以發現的問題。 關於如何選擇合適的智能指針,如下給出參考建議: 如果需要多個指向同一堆對象的有效指針,那麼選擇使用shared_ptr。這種情況包括: 1 有一個指針數組,並且使用一些輔助指針標識特殊元素,如標識最值元素; 2 兩個對象中都包含指向第三個對象的指針; 3 STL容器中的指針等,很多STL算法都支持復制和賦值操作。 如果程序不需要多個有效指針同時指向同一堆對象,那麼使用unique_ptr。如果需要將unique_ptr作為右值時,可將其賦值給shared_ptr;在滿足unique_ptr條件時,也可使用auto_ptr,但unique_ptr似乎是更好的選擇(如果編譯器不提供unique_ptr,可以使用boost庫提供的scoped_ptr,其功能與unique_ptr類似)。 [3] 迭代器 [3.1] 迭代器基礎 迭代器就是廣義的指針,可對其進行遞增操作和解引用操作等的對象。每個容器類都定義了一個與之相關的迭代器,該迭代器是一個名為iterator的typedef定義,作用域為整個類。模板使算法獨立於數據類型,而迭代器使算法獨立於容器類型。 迭代器主要用於遍歷容器中的元素,其應該具有如下基本功能: 1 支持解引用操作 2 賦值操作 3 比較操作,如 ==、!= 4 自增操作 具備以上能力就差不多了。其實STL按照迭代器功能強弱定義了多種級別(5種)的迭代器,說明如下: 迭代器類型 說明 輸入迭代器 【讀容器】可來讀取容器中的元素,但可能不會修改容器中的元素。輸入迭代器必須可以訪問容器中的所有元素,因此,支持++操作。 輸出迭代器 【寫容器】程序可以修改容器中的元素值,但不能讀取容器中的元素。 正向迭代器 【讀或者寫容器】單向讀寫。 雙向迭代器 【雙向讀或者寫容器】雙向讀寫。支持自減運算符。 隨機訪問迭代器 【雙向讀或者寫容器】提供了附加的“跳步”訪問能力。 迭代器支持的操作含義: 表達式 說明 a + n、n + a 指向a所指元素後的第n個元素 a - n 指向a所指元素前的第n個元素 r += n、r -= n r = r + n、r = r – n a[n] *(a + n) b - a a ~ b區間的元素個數 a < b、a > b、a >= b、a <= b 邏輯判斷 STL迭代器功能匯總: 總結: 迭代器支持的通常操作為解引用讀或者寫;所有類型的迭代器都支持自增操作;自減操作只有雙向迭代器和隨機訪問迭代器支持;隨機訪問迭代器為“跳步”訪問提供了可能。因此迭代器能力大小可以表示如下: 隨機訪問迭代器 > 雙向迭代器 > 正向迭代器 > 輸入迭代器 == 輸出迭代器 [3.2] 迭代器進階 迭代器是廣義的指針,而指針滿足所有迭代器的要求。因此STL算法可以使用指針來對基於指針的非STL容器進行操作。例如,可將STL算法用於數組: 如果要將一個double Receipts[100]進行排序,可以使用STL的算法sort,但傳入的確是指針,如: sort(Receipts, Receipts + 100); STL提供了一些預定義的迭代器:copy()、ostream_iterator、istream_iterator。 假設有如下定義: int casts[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; vector<int> dice(10); copy(casts, casts + 10, dice.begin()); // 將[casts, casts + 10)范圍內的數據復制到dice.begin()開始的目標空間中 輸出流迭代器: 假設現在需要將數據復制到顯示器上,那麼需要一個表示輸出流的迭代器,STL為我們提供了ostream_iterator模板。該迭代器是一個適配器,可將所有的接口轉換為STL使用的接口。我們使用時必須包含頭文件iterator,並且做出如下聲明來創建該迭代器: #include <iterator> ... ostream_iterator<int, char> out_iter(cout, “ ”); 說明: int: 表示發送到輸出流中的數據類型; char: 表示輸出流使用的字符類型; 構造函數中,cout: 表示要使用的輸出流;“ ”:表示所發送數據的分隔符; 可以這樣使用迭代器: *out_iter++ = 15; // 等價於cout << 15 << “ ”; 這條語句表明將”15 ”賦予指針指向的位置,然後指針向前移動一個單位。 實現如上的copy動作為: copy(dice.begin(), dice.end(), out_iter)即可將dice容器的整個區間復制到顯示器中顯示。 當然也可以創建匿名的迭代器,如下: copy(dice.begin(), dice.end(), ostream_iterator<int, char>(cout, “ ”)); 輸入流迭代器: 對於的輸入流迭代器為istream_iter模板: copy(istream_iterator<int, char>(cin), istream_iterator<int, char>(), ostream_iterator<int, char>(cout, “ ”)); 說明: int:要讀取的數據類型; char:輸入流使用的數據類型; 構造函數中,第一個參數cin表示讀取由cin管理的輸入流;省略構造函數意味著輸入失敗,因此上述代碼從輸入流中讀取int型數據,直到文件末尾、類型不匹配或者出現其他輸入故障為止才輸出到屏幕上。
1 #include <iostream> 2 #include <iterator> 3 using namespace std; 4 5 int main() 6 { 7 copy(istream_iterator<int, char>(cin), istream_iterator<int, char>(), 8 ostream_iterator<int, char>(cout, " ")); 9 return 0; 10 } 11 12 /** output */ 13 1 2 3 4 5 14 1 2 3 4 5 __
除了istream_iterator和ostream_iterator外,頭文件iterator中還提供了一些專用的迭代器,如:reverse_iterator、back_insert_iterator、front_insert_iterator和insert_iterator。 reverse_iterator執行遞增時,指針實際上發生遞減操作。例如:vector類中的rbegin()和rend()分別返回指向最後一個元素下一位置的指針、指向第一個元素的指針。 強調: 雖然rbegin()和end()返回的指針雖然指向同一個位置,但是類型不同,前者類型是reverse_iterator,後者類型是iterator。 同樣要注意的是:對於反向迭代的解引用的具體實現實則是先遞減,再解引用。否則會出錯。試想一下,如果一個rp是rbegin()返回的,直接解引用將導致操作一個空指針。
1 #include <iostream> 2 #include <iterator> 3 #include <vector> 4 using namespace std; 5 6 /* 7 int main() 8 { 9 copy(istream_iterator<int, char>(cin), istream_iterator<int, char>(), 10 ostream_iterator<int, char>(cout, " ")); 11 return 0; 12 } 13 */ 14 15 int main() 16 { 17 int casts[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; 18 vector<int> dice(10); 19 copy(casts, casts + 5, dice.begin()); // 將casts的前5個數據復制到dice中 20 21 ostream_iterator<int, char> out_iter(cout, " "); // 創建輸出流迭代器 22 copy(dice.begin(), dice.end(), out_iter); // 將dice中的全部元素復制到屏幕上輸出 23 24 cout << endl; 25 26 /* 隱式使用reverse_iterator */ 27 copy(dice.rbegin(), dice.rend(), out_iter); // 將dice中的全部元素反向輸出到屏幕上 28 29 cout << endl; 30 31 /* 顯式使用reverse_iterator */ 32 vector<int>::reverse_iterator ri; 33 for(ri = dice.rbegin(); ri != dice.rend(); ++ri) 34 cout << *ri << " : "; 35 cout << endl; 36 37 return 0; 38 } 39 40 /** output */ 41 1 2 3 4 5 0 0 0 0 0 42 0 0 0 0 0 5 4 3 2 1 43 0 : 0 : 0 : 0 : 0 : 5 : 4 : 3 : 2 : 1 :
在形式上,STL中的很多算法都和copy函數類似,如下為三種插入迭代器操作: back_insert_iterator將元素插入容器尾部; front_insert_iterator將元素插入容器頭部; insert_iterator將元素插入指定位置。 插入操作相比於copy操作而言,可以自動增加空間,而copy確是靜態分配好的空間,即使不夠了也不會向操作系統去申請,默認情況下會截斷後邊的數據,只復制容量允許的數據。使用插入迭代器時,使用容器類型作為模板參數,使用容器變量作為構造參數,如下所述: back_insert_iterator<vector<int>> back_iter(dice); 對於insert_iterator,還需要一個指定插入位置的構造參數: insert_iterator<vector<int>> insert_iter(dice, dice.begin()); [4]容器類 容器用於存儲對象,其要求所存儲的對象必須具有相同的類型,而且該類型必須是可復制構造和可賦值的。一般基本類型和類類型都滿足要求(當然除非你把這兩種函數聲明為私有或保護類型的)。C++ 11中又添加了可復制插入和可移動插入要求。 容器類型是用於創建具體容器的模板。之前共有11個容器類型:deque/list/queue/priority_queue/stack/vector/map/multimap/set/multiset/biset,C++ 11新增了5個:forward_list/unordered_map/unordered_multimap/unordered_set/unordered_multiset。 何為序列? 序列保證了元素將按特定的順序排列,不會在兩次迭代之間發生變化。序列還要求其元素嚴格按線性順序排列,即存在第一個/第二個/.../等。比如數組和鏈表都是序列,但分支結構就不是序列。 由於序列中的元素具有特定的順序,因此可以執行像插入元素到特定位置、刪除特定區間等操作。如下容器都為序列容器: vector vector是數組的一種類的表示,它提供了自動管理內存的功能,可以動態改變vector的長度,增大或減小。vector是可以反轉的,主要是通過rbegin()和rend()實現的,並且其返回的迭代器類型都是reverse_iterator。 deque 雙端隊列。其實現類似於vector,支持隨機訪問。主要區別在於可以從deque中開頭和結尾處插入和刪除元素,而且其時間復雜度固定。 list 雙向鏈表。與vector類似,list也可以反轉。主要區別是:list不支持隨機訪問。除此之外,list模板類還包括了鏈表專用的成員函數,如下所示: void merge(list<T, Alloc>& x): 將鏈表x與調用鏈表合並,並且已然有序。合並後的鏈表保存在調用鏈表中,x為空。 void remove(const T& val): 從鏈表中刪除val的所有實例。 void sort(): 使用<運算符對鏈表進行排序,時間復雜度為nlgn。 void splice(iterator pos, list<T, Alloc>x): 將鏈表x的內容插入到pos前邊,插入後x為空。 void unique(): 將鏈表中連續的相同元素壓縮為單個元素。 說明: insert()和splice()之間的主要區別在於:insert是插入原始區間的副本到目標地址,而splice是將原始區間轉移到目標地址。 C++ 11新增了容器類forward_list,實現了單鏈表,與之關聯的迭代器是正向迭代器而非雙向迭代器。 queue queue模板類是一個適配器類,如前所述,ostream_iterator模板也是一個適配器,可以讓輸出流使用迭代器接口,同樣,queue模板類讓底層類(默認為deque)展示出隊列接口。 queue模板的限制比deque更多。他不僅不允許隨機訪問,而且不允許遍歷操作。其把使用限制在隊列基本操作上,如入隊、出隊、取隊首、判隊空等,具體如下: bool empty() const: 如果隊列為空則返回true;否則返回false。 size_type size() const: 返回隊列中元素的數目。 T& front(): 返回指向隊首元素的引用。 T& back(): 返回指向隊尾元素的引用。 void push(const T& x): 在隊尾插入x。 void pop(): 刪除隊首元素。 說明: priority_queue與queue的主要區別在於:最大元素被移到隊首。其內部實現為vector,可以指定內部元素排序規則。 stack 適配器類,使用vector實現,支持操作如下: bool empty() const: 如果棧為空則返回true;否則返回false。 size_type size() const: 返回棧中元素的數目。 T& top(): 返回指向棧頂元素的引用。 void push(const T& x): 在棧頂插入x。 void pop(): 刪除棧頂元素。 array(C++ 11 新增) 模板類array並非STL容器,因為其長度是固定的。因此不能使用動態調整容器大小的函數如push_back()和insert()等,但是其定義了很有用的成員函數,如operator[]()和at(),正如之前所述,許多標准的STL算法也可用於array對象,如:copy()和for_each()。 操作示例:
1 #include <iostream> 2 #include <list> 3 #include <iterator> 4 #include <algorithm> 5 using namespace std; 6 7 void PrintList(int n){ cout << n << " "; } 8 9 int main() 10 { 11 list<int> one(5, 2); // 5個2 12 cout << "list one: "; 13 for_each(one.begin(), one.end(), PrintList); 14 cout << endl; 15 16 list<int> two; 17 int stuff[3] = {2, 5, 9}; 18 two.insert(two.begin(), stuff, stuff + 3); 19 cout << "list two: "; 20 for_each(two.begin(), two.end(), PrintList); 21 cout << endl; 22 23 list<int> three(two); 24 three.insert(three.end(), one.begin(), one.end()); 25 cout << "list three: "; 26 for_each(three.begin(), three.end(), PrintList); 27 cout << endl; 28 29 cout << "merge one and three: "; 30 three.splice(three.begin(), one); 31 for_each(three.begin(), three.end(), PrintList); 32 cout << endl; 33 34 cout << "delete the duplicate elements: "; 35 three.unique(); 36 for_each(three.begin(), three.end(), PrintList); 37 cout << endl; 38 39 cout << "merge with another: four = "; 40 list<int> four(3, 8); 41 four.merge(three); 42 for_each(four.begin(), four.end(), PrintList); 43 cout << endl; 44 45 cout << "remove the value 8 in four: "; 46 four.remove(8); 47 for_each(four.begin(), four.end(), PrintList); 48 cout << endl; 49 50 return 0; 51 } 52 53 /** output */ 54 list one: 2 2 2 2 2 55 list two: 2 5 9 56 list three: 2 5 9 2 2 2 2 2 57 merge one and three: 2 2 2 2 2 2 5 9 2 2 2 2 2 58 delete the duplicate elements: 2 5 9 2 59 merge with another: four = 2 5 8 8 8 9 2 60 remove the value 8 in four: 2 5 9 2