《C++Primer》第三章-标准库类型-学习笔记(1)
文章目录
日志:
1,2020-02-28 笔者提交文章的初版V1.0
作者按:
最近在学习C++ primer,初步打算把所学的记录下来。
传送门/推广
《C++Primer》第二章-变量和基本类型-学习笔记(1)
《C++Primer》第二章-变量和基本类型-学习笔记(2)
《C++Primer》第二章-变量和基本类型-学习笔记(3)
摘要
除了介绍的基本数据类型外,C++ 还定义了一个内容丰富的抽象数据类型标准库。其中最重要的标准库类型是string
和 vector
,它们分别定义了大小可变的字符串和集合。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 个副本 |
注意!因为历史原因以及为了与 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 |
!=, <, <=, >, >= | 保持这些操作符惯有的含义 |
了解 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。 |
建议:采用 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(署名 - 非商业性使用 - 相同方式共享) 协议,转载请注明出处,不得用于商业目的。