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

[1] string基礎

[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 }
View Code

 

[1.2] string 的輸入

對於 C 風格字符串,有3種輸入方式ios

char info[100];算法

cin >> info;             // 從流中讀一個單詞存放到info中數組

cin.getline(info, 100);  // 從流中讀入一行存放到info,刪除流中的\n安全

cin.get(info, 100);      // 從流中讀入一行存放到info,保留流中的\nide

對於 string 對象,有2種輸入方式函數

string stuff;spa

cin >> stuff;            // 從流中讀取一個單詞操作系統

getline(cin, stuff);     // 從流中讀取一行,刪除流中的\n指針

getline能夠指定用於分割單詞的分隔符,將其放到第三個參數中,如:code

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
相關文章
相關標籤/搜索