《C++Primer》第三章-標準庫類型-學習筆記(1)

《C++Primer》第三章-標準庫類型-學習筆記(1)

日誌:
1,2020-02-28 筆者提交文章的初版V1.0

作者按:
最近在學習C++ primer,初步打算把所學的記錄下來。

傳送門/推廣
《C++Primer》第二章-變量和基本類型-學習筆記(1)
《C++Primer》第二章-變量和基本類型-學習筆記(2)
《C++Primer》第二章-變量和基本類型-學習筆記(3)

摘要

除了介紹的基本數據類型外,C++ 還定義了一個內容豐富的抽象數據類型標準庫。其中最重要的標準庫類型是stringvector,它們分別定義了大小可變的字符串和集合。string 和 vector 往往將迭代器用作配套類型(companion type),用於訪問 string 中的字符,或者 vector 中的元素。這些標準庫類型是語言組成部分中更基本的那些數據類型(如數組和指針)的抽象。
另一種標準庫類型 bitset,提供了一種抽象方法來操作位的集合。與整型值上的內置位操作符相比,bitset 類類型提供了一種更方便的處理位的方式。

命名空間的 using 聲明

使用 using 聲明可以在不需要加前綴 namespace_name:: 的情況下訪問命
名空間中的名字。using 聲明的形式如下:

using namespace::name;  //這個name可以是cin,string等
using std::string;  //使用樣例

一旦使用了 using 聲明,我們就可以直接引用名字,而不需要再引用該名字的命名空間。
沒有 using 聲明,而直接使用命名空間中名字的未限定版本是錯誤的,儘管有些編譯器也許無法檢測出這種錯誤。

一個 using 聲明一次只能作用於一個命名空間成員。using 聲明可用來明確指定在程序中用到的命名空間中的名字,如果希望使用 std(或其他的命名空間)中的幾個名字,則必須爲要用到的每個名字都提供一個 using 聲明。例如,

#include <iostream>
// using declarations for names from the standard library
using std::cin;
using std::cout;
using std::endl;
int main()
{
...
}

當然最通常還是使用

using namespace std;

標準庫 string 類型

string 類型支持長度可變的字符串,C++ 標準庫將負責管理與存儲字符相關的內存,以及提供各種有用的操作

string 對象的定義和初始化

string 標準庫支持幾個構造函數。下面列出了幾個 string 類型常用的構造函數。

構造函數 作用
string s1; 默認構造函數 s1 爲空串
string s2(s1); 將 s2初始化爲 s1 的一個副本
string s3(“value”); 將 s3 初始化爲一個字符串字面值副本
string s4(n, ‘c’); 將 s4 初始化爲字符 ‘c’ 的 n 個副本
表1.幾種初始化string 對象的方式

注意!因爲歷史原因以及爲了與 C 語言兼容,字符串字面值與標準庫 string 類型不是同一種類型。這一點很容易引起混亂,編程時一定要注意區分字符串字面值和 string 數據類型的使用,這很重要。

string 對象的讀寫

// Note: #include and using declarations must be added to compile
this code
int main()
{
	string s; // empty string
	cin >> s; // read whitespace-separated string into s
	cout << s << endl; // write s to the output
	return 0;
}

對於string類型變量s的輸入:

cin >> s;

從標準輸入讀取 string 並將讀入的串存儲在 s 中。string 類型的輸入操作符:

  • 讀取並忽略開頭所有的空白字符(如空格,換行符,製表符)。
  • 讀取字符直至再次遇到空白字符,讀取終止。

輸入和輸出操作的行爲與內置類型操作符基本類似。尤其是,這些操作符返回左操作數作爲運算結果。因此,我們可以把多個讀操作或多個寫操作放在一起:

string s1, s2;
cin >> s1 >> s2; // read first input into s1, second into s2
cout << s1 << s2 << endl; // write both strings

讀入未知數目的string 對象

和內置類型的輸入操作一樣,string 的輸入操作符也會返回所讀的數據流。因此,可以把輸入操作作爲判斷條件。
下面的程序將從標準輸入讀取一組 string 對象,然後在標準輸出上逐行輸出:

int main()
{
	string word;
	// read until end-of-file, writing each word to a new line
	while (cin >> word)
		cout << word << endl;
	return 0;
}

上例中,用輸入操作符來讀取 string 對象。該操作符返回所讀的istream 對象,並在讀取結束後,作爲 while 的判斷條件。如果輸入流是有效的,即還未到達文件尾且未遇到無效輸入,則執行 while 循環體,並將讀取到的字符串輸出到標準輸出。如果到達了文件尾,則跳出 while 循環。

使用getline 讀取整行文本

getline函數接受兩個參數:一個輸入流對象和一個 string 對象<>。getline 函數從輸入流的下一行讀取,並保存讀取的內容到不包括換行符。和輸入操作符不一樣的是,getline 並不忽略行開頭的換行符。只要 getline 遇到換行符,即便它是輸入的第一個字符,getline 也將停止讀入並返回。如果第一個字符就是換行符,則 string 參數將被置爲空 string。
getline 函數將 istream 參數作爲返回值,和輸入操作符一樣也把它用作判斷條件。例如,重寫前面那段程序,把每行輸出一個單詞改爲每次輸出一行文本:

int main()
{
string line;
// read line at time until end-of-file
while (getline(cin, line))  //循環讀取輸入流
cout << line << endl;
return 0;
}

由於 line 不含換行符,若要逐行輸出需要自行添加。照常,我們用 endl 來輸出一個換行符並刷新輸出緩衝區。
由於 getline 函數返回時丟棄換行符,換行符將不會存儲在 string 對象中。

string 對象的操作

操作 功能描述
s.empty() 如果 s 爲空串,則返回 true,否則返回 false。
s.size() 返回 s 中字符的個數
s[n] 返回 s 中位置爲 n 的字符,位置從 0 開始計數
s1 + s2 把 s1 和s2 連接成一個新字符串,返回新生成的字符串
s1 = s2 把 s1 內容替換爲 s2 的副本
v1 == v2 比較 v1 與 v2 的內容,相等則返回 true,否則返回 false
!=, <, <=, >, >= 保持這些操作符慣有的含義
表 2. 列出了常用的 string 操作。

瞭解 string 對象是否空是有用的。一種方法是將 size 與 0 進行比較:

if (s.size() == 0)
// ok: empty

用 string 的成員函數 empty() 可以更直接地回答這個問題:

if (st.empty())
// ok: empty

string::size_type 類型與配套類型

從邏輯上來講,size() 成員函數似乎應該返回整型數值。但事實上,size 操作返回的是string::size_type 類型的值。

string 類類型和許多其他庫類型都定義了一些配套類型(companion type)。通過這些配套類型,庫類型的使用就能與機器無關(machine-independent)

size_type 就是這些配套類型中的一種。它定義爲與 unsigned 型(unsignedint 或 unsigned long)具有相同的含義,而且可以保證足夠大能夠存儲任意 string 對象的長度。爲了使用由 string 類型定義的 size_type 類型是由 string 類定義。任何存儲 string 的 size 操作結果的變量必須爲 string::size_type 類型。特別重要的是,還要把 size 的返回值賦給一個 int 變量。

string 關係操作符

string 對象比較操作是區分大小寫的,即同一個字符的大小寫形式被認爲是兩個不同的字符。在多數計算機上,大寫的字母位於小寫之前:任何一個大寫之母都小於任意的小寫字母。
關係操作符比較兩個 string 對象時採用了和(大小寫敏感的)字典排序相同的策略:

  • 如果兩個 string 對象長度不同,且短的 string 對象與長的 string 對象的前面部分相匹配,則短的 string 對象小於長的 string 對象。
  • 如果 string 對象的字符不同,則比較第一個不匹配的字符。

string 對象的賦值

總體上說,標準庫類型儘量設計得和基本數據類型一樣方便易用。因此,大多數庫類型支持賦值操作。
對 string 對象來說,可以把一個 string 對象賦值給另一個 string 對象;

// st1 is an empty string, st2 is a copy of the literal
string st1, st2 = "The expense of spirit";
st1 = st2; // replace st1 by a copy of st2

賦值操作後,st1 就包含了 st2 串所有字符的一個副本。大多數 string 庫類型的賦值等操作的實現都會遇到一些效率上的問題,但值得注意的是,從概念上講,賦值操作確實需要做一些工作。它必須先把 st1 佔用的相關內存釋放掉,然後再分配給 st2 足夠存放 st2 副本的內存空間,最後把 st2 中的所有字符複製到新分配的內存空間。

和字符串字面值的連接

將 string 對象和字符串字面值混合連接:

string s1("hello");
string s2("world");
string s3 = s1 + ", " + s2 + "\n";

當進行 string 對象和字符串字面值混合連接操作時,+ 操作符的左右操作數必須至少有一個是 string 類型的

string s1 = "hello"; // no punctuation
string s2 = "world";
string s3 = s1 + ", "; // ok: adding a string and a literal
string s4 = "hello" + ", "; // error: no string operand s4 的初始化試圖將兩個字符串字面值相加,因此是非法的
string s5 = s1 + ", " + "world"; // ok: each + has string operand
string s6 = "hello" + ", " + s2; // error: can't add string literals
// s6 的初始化是非法的。依次來看每個子表達式,則第一個子表達式試圖把兩個字符串字面值連接起來。這是不允許的,因此這個語句是錯誤的。

從string 對象獲取字符

string 類型通過下標操作符([ ])來訪問 string 對象中的單個字符。下標操作符需要取一個 size_type 類型的值,來標明要訪問字符的位置。這個下標中的值通常被稱爲“下標”或“索引”(index)
string 對象的下標從 0 開始。如果 s 是一個 string 對象且 s 不空,則 s[0] 就是字符串的第一個字符, s[1] 就表示第二個字符(如果有的話),而 s[s.size() - 1] 則表示 s 的最後一個字符。
引用下標時如果超出下標作用範圍就會引起溢出錯誤
可用下標操作符分別取出 string 對象的每個字符,分行輸出:

string str("some string");
for (string::size_type ix = 0; ix != str.size(); ++ix)
cout << str[ix] << endl;

每次通過循環,就從 str 對象中讀取下一個字符,輸出該字符並換行。

計算下標值

任何可產生整型值的表達式可用作下標操作符的索引。例如,假設 someval 和 someotherval 是兩個整型對象,可以這樣寫:

str[someotherval * someval] = someval;

雖然任何整型數值都可作爲索引,但索引的實際數據類型卻是類型 unsigned 類型 string::size_type。

string 對象中字符的處理

我們經常要對 string 對象中的單個字符進行處理,例如,通常需要知道某個特殊字符是否爲空白字符、字母或數字。表 3 列出了各種字符操作函數,適用於 string 對象的字符(或其他任何 char 值)。這些函數都在 cctype 頭文件中定義。

操作 功能描述
isalnum( c ) 如果 c 是字母或數字,則爲 True。
isalpha( c ) 如果 c 是字母,則爲 true。
iscntrl( c ) 如果 c 是控制字符,則爲 true
isdigit( c ) 如果 c 是數字,則爲 true。
isgraph( c ) 如果 c 不是空格,但可打印,則爲 true。
islower( c ) 如果 c 是小寫字母,則爲 true。
isprint( c ) 如果 c 是可打印的字符,則爲 true。
ispunct( c ) 如果 c 是標點符號,則 true。
isspace( c ) 如果 c 是空白字符,則爲 true。
isupper( c ) 如果 c 是大寫字母,則 true。
isxdigit( c ) 如果是 c 十六進制數,則爲 true。
tolower( c ) 如果 c 大寫字母,返回其小寫字母形式,否則直接返回 c。
toupper( c ) 如果 c 是小寫字母,則返回其大寫字母形式,否則直接返回 c。
表 3 列出了各種字符操作函數
表中的大部分函數是測試一個給定的字符是否符合條件,並返回一個 int 作爲真值。如果測試失敗,則該函數返回 0 ,否則返回一個(無意義的)非 0 ,表示被測字符符合條件

建議:採用 C 標準庫頭文件的 C++ 版本

C++ 標準庫除了定義了一些選定於 C++ 的設施外,還包括 C 標準庫。C++ 中的頭文件 cctype 其實就是利用了 C 標準庫函數,這些庫函數就定義在 C 標準庫的 ctype.h 頭文件中。
C 標準庫頭文件命名形式爲 name 而 C++ 版本則命名爲 cname ,少了後綴,.h 而在頭文件名前加了 c 表示這個頭文件源自 C 標準庫。因此,cctype 與 ctype.h 文件的內容是一樣的,只是採用了更適合 C++程序的形式。特別地,cname 頭文件中定義的名字都定義在命名空間 std 內,而 .h 版本中的名字卻不是這樣。
通常,C++ 程序中應採用 cname 這種頭文件的版本,而不採用 name.h 版本,這樣,標準庫中的名字在命名空間 std 中保持一致。使用 .h 版本會給程序員帶來負擔,因爲他們必須記得哪些標準庫名字
是從 C 繼承來的,而哪些是 C++ 所特有的。

標準庫 vector 類型

vector是同一種類型的對象的集合,每個對象都有一個對應的整數索引值。和 string 對象一樣,標準庫將負責管理與存儲元素相關的內存。我們把 vector稱爲容器是因爲它可以包含其他對象一個容器中的所有對象都必須是同一種類型的
vector 是一個類模板(class template)。使用模板可以編寫一個類定義或函數定義,而用於多個不同的數據類型。因此,我們可以定義保存 string 對象的 vector,或保存 int 值的 vector,又或是保存自定義的類類型對象(如Sales_items 對象)的 vector。
在使用下標索引 string 對象時,必須保證索引值“在上下界範圍內”。“在上下界範圍內”就是指索引值是一個賦值爲 size_type 類型的值,其取值範圍在 0 到 string 對象長度減 1 之間。使用 string::size_type 類型或其他 unsigned 類型,就只需要檢測它是否小於 string 對象的長度。
標準庫不要求檢查索引值,所用索引的下標越界是沒有定義的,這樣往往會導致嚴重的運行時錯誤。

vector 對象的定義和初始化

vector 類定義了好幾種構造函數(2.3.3 節),用來定義和初始化 vector對象.

構造函數 功能描述
vector< T > v1; vector 保存類型爲 T 對象。默認構造函數 v1 爲空。
vector< T > v2(v1); v2 是 v1 的一個副本。
vector< T > v3(n, i); v3 包含 n 個值爲 i 的元素。
vector< T > v4(n); v4 含有值初始化的元素的 n 個副本。

若要創建非空的 vector 對象,必須給出初始化元素的值。當把一個 vector對象複製到另一個 vector 對象時,新複製的 vector 中每一個元素都初始化爲原 vectors 中相應元素的副本。但這兩個 vector 對象必須保存同一種元素類型:

vector<int> ivec1; // ivec1 holds objects of type int
vector<int> ivec2(ivec1); // ok: copy elements of ivec1 into ivec2
vector<string> svec(ivec1); //錯誤!類型不一致: svec holds strings, not ints

值初始化

如果沒有指定元素的初始化式,那麼標準庫將自行提供一個元素初始值進行值初始化(value initializationd)。這個由庫生成的初始值將用來初始化容器中的每個元素,具體值爲何,取決於存儲在 vector 中元素的數據類型。
如果 vector 保存內置類型(如 int 類型)的元素,那麼標準庫將用 0 值創建元素初始化式:

vector< int > fvec(10); // 10 elements, each initialized to 0

如果 vector 保存的是含有構造函數的類類型(如 string)的元素,標準庫將用該類型的默認構造函數創建元素初始化式

vector< string > svec(10); // 10 elements, each an empty string

還需要注意,一些有自定義構造函數但沒有默認構造函數的類,在初始化這種類型的 vector 對象時,程序員就不能僅提供元素個數,還需要提供元素初始值。
還有一種可能:元素類型可能是沒有定義任何構造函數的類類型。這種情況下,標準庫仍產生一個帶初始值的對象,這個對象的每個成員進行了值初始化。

vector 對象動態增長

vector 對象(以及其他標準庫容器對象)的重要屬性就在於可以在運行時高效地添加元素。因爲 vector 增長的效率高,在元素值已知的情況下,最好是動態地添加元素。
雖然可以對給定元素個數的 vector 對象預先分配內存,但更有效的方法是先初始化一個空 vector 對象,然後再動態地增加元素。

vector 對象的操作

vector 標準庫提供了許多類似於 string 對象的操作:

操作函數 功能描述
v.empty() 如果 v 爲空,則返回 true,否則返回 false。
v.size() 返回 v 中元素的個數。
v.push_back(t) 在 v 的末尾增加一個值爲 t 的元素。
v[n] 返回 v 中位置爲 n 的元素。
v1 = v2 把 v1 的元素替換爲 v2 中元素的副本。
v1 == v2 如果 v1 與 v2 相等,則返回 true。
!=, <, <=,>, >= 保持這些操作符慣有的含義。

vector 對象的 size

vector 對象的empty 和 size 操作類似於 string 的相關操作。成員函數size 返回相應 vector 類定義的 size_type 的值。
使用 size_type 類型時,必須指出該類型是在哪裏定義的。vector 類型總是包括總是包括 vector 的元素類型:

vector<int>::size_type // ok
vector::size_type // error

向 vector 添加元素

push_back操作接受一個元素值,並將它作爲一個新的元素添加到 vector對象的後面,也就是“插入(push)”到 vector 對象的“後面(back)”:

// read words from the standard input and store them as elements ina vector
string word;
vector<string> text; // empty vector
133
while (cin >> word) {
text.push_back(word); // append word to text
}

vector 的下標操作

vector 中的對象是沒有命名的,可以按 vector 中對象的位置來訪問它們。通常使用下標操作符來獲取元素
vector 的下標操作符接受一個值,並返回 vector 中該對應位置的元素。vector 元素的位置從 0 開始。

下標操作不添加元素

初學 C++ 的程序員可能會認爲 vector 的下標操作可以添加元素,其實不然:

vector<int> ivec; // empty vector 空的ivec
for (vector<int>::size_type ix = 0; ix != 10; ++ix)
ivec[ix] = ix; // disaster: ivec has no elements 不能通過下標添加

這個循環的正確寫法應該是:

for (vector<int>::size_type ix = 0; ix != 10; ++ix)
ivec.push_back(ix); // ok: adds new element with value ix

必須是已存在的元素才能用下標操作符進行索引。通過下標操作進行賦值時,不會添加任何元素。
不幸的是,試圖對不存在的元素進行下標操作是程序設計過程中經常會犯的嚴重錯誤。所謂的“緩衝區溢出”錯誤就是對不存在的元素進行下標操作的結果。這樣的缺陷往往導致 PC 機和其他應用中最常見的安全問題。

安全的泛型編程

C++ 程序員習慣於優先選用!=而不是 <來編寫循環判斷條件。比如上面for 循環的判斷條件用!= 而不是用< 來測試 vector 下標值是否越界,這是一種合理的習慣。
調用 size 成員函數而不保存它返回的值,在這個例子中同樣不是必需的,但這反映了一種良好的編程習慣。在 C++ 中,有些數據結構(如vector)可以動態增長。上例中循環僅需要讀取元素,而不需要增加新的元素。但是,循環可以容易地增加新元素,如果確實增加了新元素的話,那麼測試已保存的 size 值作爲循環的結束條件就會有問題,因爲沒有將新加入的元素計算在內。所以我們傾向於在每次循環中測試 size的當前值,而不是在進入循環前,存儲 size 值的副本。
C++ 中有些函數可以聲明爲內聯(inline)函數。編譯器遇到內聯函數時就會直接擴展相應代碼,而不是進行實際的函數調用。像 size 這樣的小庫函數幾乎都定義爲內聯函數,所以每次循環過程中調用它的運行時代價是比較小的

迭代器

除了使用下標來訪問 vector 對象的元素外,標準庫還提供了另一種訪問元素的方法:使用迭代器(iterator)。迭代器(iterator)是一種檢查容器內元素並遍歷元素的數據類型
標準庫爲每一種標準容器(包括 vector)定義了一種迭代器類型迭代器類型提供了比下標操作更通用化的方法所有的標準庫容器都定義了相應的迭代器類型,而只有少數的容器支持下標操作因爲迭代器對所有的容器都適用,現代 C++ 程序更傾向於使用迭代器而不是下標操作訪問容器元素,即使對支持下標操作的 vector 類型也是這樣。

容器的迭代器(iterator) 類型

每種容器類型都定義了自己的迭代器類型,如 vector:

vector<int>::iterator iter;

每個標準庫容器類型都定義了一個名爲 iterator 的成員,這裏的 iterator 與迭代器實際類型的含義相同。

迭代器和迭代器類型

程序員首次遇到有關迭代器的術語時可能會困惑不解,原因之一是由於同一個術語 iterator 往往表示兩個不同的事物。

  • 一般意義上指的是迭代器的概念;
  • 而具體而言時指的則是由容器定義的具體的 iterator 類型,如 vector。

重點要理解的是,有許多用作迭代器的類型,這些類型在概念上是相關的。若一種類型支持一組確定的操作(這些操作可用來遍歷容器內的元素,並訪問這些元素的值),我們就稱這種類型爲迭代器。

各容器類都定義了自己的 iterator 類型,用於訪問容器內的元素。換句話說,每個容器都定義了一個名爲 iterator 的類型,而這種類型支持(概念上的)迭代器的各種操作。

begin 和 end 操作

每種容器都定義了一對命名爲 begin 和 end 的函數,用於返回迭代器
如果容器中有元素的話,由begin返回的迭代器指向第一個元素:

vector<int>::iterator iter = ivec.begin();
//上述語句把 iter 初始化爲由名爲 vector 操作返回的值。假設 vector 不
//空,初始化後,iter 即指該元素爲 ivec[0]。

end操作返回的迭代器指向 vector 的“末端元素的下一個”。“超出末端迭代器”(off-the-end iterator)。表明它指向了一個不存在的元素。如果 vector 爲空,begin 返回的迭代器與 end 返回的迭代器相同
由 end 操作返回的迭代器並不指向 vector 中任何實際的元素,相反,它只是起一個哨兵(sentinel)的作用,表示我們已處理完 vector 中所有元素。

vector 迭代器的自增和解引用運算

迭代器類型定義了一些操作來獲取迭代器所指向的元素,並允許程序員將迭代器從一個元素移動到另一個元素。迭代器類型可使用解引用操作符(dereference operator)(*)來訪問迭代器所指向的元素:

*iter = 0;

解引用操作符返回迭代器當前所指向的元素。假設 iter 指向 vector 對象 ivec 的第一元素,那麼 *iter 和 ivec[0] 就是指向同一個元素。上面這個語句的效果就是把這個元素的值賦爲 0。

迭代器使用自增操作符向前移動迭代器指向容器中下一個元素。從邏輯上說,迭代器的自增操作和 int 型對象的自增操作類似。對 int 對象來說,操作結果就是把 int 型值“加 1”,而對迭代器對象則是把容器中的迭代器“向前移動一個位置”。因此,如果 iter 指向第一個元素,則 ++iter指向第二個元素。
由於 end 操作返回的迭代器不指向任何元素,因此不能對它進行解引用或自增操作。

迭代器的其他操作

另一對可執行於迭代器的操作就是比較:用 == 或 != 操作符來比較兩個迭代器,如果兩個迭代器對象指向同一個元素,則它們相等,否則就不相等。

迭代器應用的程序示例

假設已聲明瞭一個 vector 型的 ivec 變量,要把它所有元素值重置爲 0,可以用下標操作來完成:

// reset all the elements in ivec to 0
for (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix)
	ivec[ix] = 0;

更典型的做法是用迭代器來編寫循環:

// equivalent loop using iterators to reset all the elements in ivec to 0
for (vector<int>::iterator iter = ivec.begin();iter != ivec.end(); ++iter)
	*iter = 0; // set element to which iter refers to 0

如果 ivec 爲空,則 begin 返回的迭代器不指向任何元素——由於沒有元素,所以它不能指向任何元素。在這種情況下,從 begin 操作返回的迭代器與從 end 操作返回的迭代器的值相同,因此 for 語句
中的測試條件立即失敗。

const_iterator

每種容器類型還定義了一種const_iterator 類型,該類型只能用於讀取容器內元素,但不能改變其值
如果我們對 const_iterator 類型解引用時,則可以得到一個指向 const 對象的引用,如同任何常量一樣,該對象不能進行重寫。[2]
例如,如果 text 是 vector 類型,程序員想要遍歷它,輸出每個元素,可以這樣編寫程序:

// use const_iterator because we won't change the elements
for (vector<string>::const_iterator iter = text.begin();
iter != text.end(); ++iter)    //iter值可以改變!
cout << *iter << endl; // print each element in text

除了是從迭代器讀取元素值而不是對它進行賦值之外,這個循環與前一個相似。由於這裏只需要藉助迭代器進行讀,不需要寫,這裏把 iter 定義爲 const_iterator 類型。**當對 const_iterator 類型解引用時,返回的是一個 const 值。**不允許用 const_iterator: 進行賦值。

不要把 const_iterator 對象與 const 的 iterator 對象混淆起來。聲明一個 const 迭代器時,必須初始化迭代器。一旦被初始化後,就不能改變它的值:[2]

vector<int> nums(10); // nums is nonconst
const vector<int>::iterator cit = nums.begin();
*cit = 1; // ok: cit can change its underlying element
++cit; // error: can't change the value of cit   值不能改變!

const_iterator 對象可以用於 const vector 或非 const vector,因爲不能改寫元素值。const 迭代器這種(比如const vector<int>::iterator)類型幾乎沒什麼用處:一旦它被初始化後,只能用它來改寫其指向的元素,但不能使它指向任何其他元素。

const vector<int> nines(10, 9); // cannot change elements in nines
// error: cit2 could change the element it refers to and nines is
const
const vector<int>::iterator cit2 = nines.begin();
// ok: it can't change an element value, so it can be used with a
const vector<int>
vector<int>::const_iterator it = nines.begin();
*it = 10; // error: *it is const
++it; // ok: it isn't const so we can change its value
// an iterator that cannot write elements
vector<int>::const_iterator
// an iterator whose value cannot change
const vector<int>::iterator

參考資料

【1】C++ Primer 中文版(第四版·特別版)
【2】《C++Primer》第二章-變量和基本類型-學習筆記(3)

註解

本文許可證

本文遵循 CC BY-NC-SA 4.0(署名 - 非商業性使用 - 相同方式共享) 協議,轉載請註明出處,不得用於商業目的。
CC BY-NC-SA 4.0

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章