程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++ 基礎知識回顧(string基礎、智能指針、迭代器、容器類)

C++ 基礎知識回顧(string基礎、智能指針、迭代器、容器類)

編輯:關於C++
[1.1] string 的構造      
 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

 

 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved