C++ Primer 读书笔记(1)字符串、向量和数组

3.1 命名空间的using声明

头文件不应该包含using声明,因为头文件的内容会拷贝到所有引用它的文件中去,如果头文件中里有某个using声明,那么每个使用了该头文件的文件就都会有这个声明。对于某些程序来说,由于不经意间包含了一些名字,反而可能产生始料未及的名字冲突。

3.2 标准库类型string

标准库类型string表示可变长的字符序列

1
2
3
4
5
6
string s1;							// 默认初始化,s1是一个空串
string s2(s1); // s2是s1的副本
string s2 = s1; // 等价于上条
string s3("value"); // s3是字面值“value”的副本,除了字面值最后的那个空字符外,直接初始化
string s3 = "value"; // 等价于上条,拷贝初始化
string s4(n,'c'); // 把s4初始化为由连续n个字符c组成的串

当把string对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符(+)两侧的运算对象至少有一个是string:

1
2
3
4
5
6
7
8
string s4 = s1 + ",";						// 正确:把一个string对象和一个字面值相加
string s5 = "hello" + ","; // 错误,两个运算对象都不是string
string s6 = s1 + "," + "world"; // 正确,每个加法运算符都有一个运算对象是string
// 等价于
string s6 = (s1 + ",") + "world";
string tmp = s1 + ",";
s6 = tmp + "world";
string s7 = "hello" + "," + s2; // 错误,不能把字面值直接相加

C++语言中的字符串字面值并不是标准库类型string的对象,字符串字面值与string是不同的类型。

3.3 标准库类型vector

标准库类型vector表示对象的集合,其中所有对象的类型都相同

C++既有类模版,也有函数模版,其中vector是一个类模版。模版本身不是类或函数,相反可以将模版看作为编译器生成类或函数编写的一份说明。编译器根据模版创建类或函数的过程称为实例化,当使用模版时,需要指出编译器应把类或函数实例化成何种类型。

对于类模版来说,我们通过提供一些额外信息来指定模版到底实例化成什么样的类,需要提供哪些信息由模版决定。vector能容纳绝大多数类型的对象作为其元素,但是因为引用不是对象,所以不存在包含引用的vector。

两个vector对象相等当且仅当它们所含的元素个数相同,而且对应位置的元素值也相同。关系运算符依照字典顺序进行比较:如果两个vector对象的容量不同,但是在相同位置上的元素值都一样,则元素较少的vector对象小于元素较多的vector对象;若元素的值有区别,则vector对象的大小关系由第一对相异的元素值的大小关系决定。

vector对象(以及string对象)的下标运算符可用于访问已存在的元素,而不能用于添加元素。

3.4 迭代器介绍

C++程序员习惯性的使用!=,其原因和他们更愿意使用迭代器而非下标的原因一样:因为这种编程风格在标准库提供的所有容器上都有效。

只有string和vector等一些标准库类型有下标运算符,并非全都如此。与之类似,所有标准库容器的迭代器都定义了==和!=,但是它们中的大多数都没有定义<运算符。

一般来说我们不知道迭代器的精确类型,而实际上,那些拥有迭代器的标准库类型使用iterator和const_iterator来表示迭代器的类型:

1
2
3
4
5
vector<int>::iterator it;			// it能读写vector<int>的元素
string::iterator it2; // it2能读写string对象中的元素

vector<int>::const_iterator it3; // it3只能读元素,不能写元素
string::const_iterator it4; // it4只能读字符,不能写字符

某些对vector对象的操作会使迭代器失效

不能在范围for循环中向vector对象添加元素。

任何一种可能改变vector对象容量的操作,比如push_back,都会使该vector对象的迭代器失效。

3.5 数组

不允许拷贝和赋值

不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值:

1
2
3
int a[] = {0,1,2};			// 含有3个整数的数组
int a2[] = a; // 错误,不允许使用一个数组初始化另一个数组
a2 = a; // 错误,不能把一个数组直接赋值给另一个数组

要想理解数组声明的含义,最好的办法就是从数组的名字开始按照由内向外的顺序阅读。

在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。由上可知,在一些情况下数组的操作实际上是指针的操作,当使用一个数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组:

1
2
3
4
5
6
7
8
int ia[] = {0,1,2,3,4,5,6};
auto ia2(ia); // ia2是一个整型指针,指向ia的第一个元素
ia2 = 42; // 错误,ia2是一个指针,不能用int值给指针赋值
auto ia2(&ia[0]); // 使用ia作为初始值时,编译器实际执行的初始化过程类似
// 使用decltype关键字时上述转换不会发生
decltype(ia) ia3 = {0,1,2,3,4,5,6}; // ia3是一个数组
ia3 = p; // 错误,不能用整型指针给数组赋值
ia3[4] = i; // 正确,把i的值赋给ia3的元素