《C++ Primer》读书笔记 第三章 字符串、向量和数组

第三章 字符串、向量和数组

3.1 命名空间的using声明

  • 头文件不应包含using声明,因为头文件的内容会拷贝到引用它的文件中。

3.2 标准库类型string

3.2.1 定义和初始化string对象

1
2
3
4
5
6
7
string s1;
string s2(s1);
string s2 = s1; // 拷贝初始化
string s3("value"); // 直接初始化
string s3 = "value";
string s4(n, 'c'); // 直接初始化
string s4 = string(n, 'c'); // 非要用拷贝初始化,没有任何好处

3.2.2 string对象上的操作

  • cin >> s会忽略掉开始和结束的空白符,返回cin
  • getline()读取到换行符并将它抛弃、不存储。
  • getline()若一开始就读取到换行符则会得到空字符串。
  • size()的返回类型是string::size_type。和其它标准库一样,这种配套类型体现了标准库类型与机器无关的特性,我们虽然不清楚这个类型的细节,但它一定是无符号的,且足够存下任何string类型的大小。(所以千万不要和负数比较。)
  • 书写代码时,表示下标的整数类型可以声明为auto或者decltype(str.size())
  • 使用+时,可以把string和字符串字面值相加,但一定要保证每个+旁边至少有一个string对象。
  • 切记字符串字面值和string是不同的类型。
  • 无论何时用到下标,都要检查合法性。

3.2.3 处理string对象中的字符

1
2
3
4
5
string str("xxxxx");
for (auto c : str)
cout << c;
for (auto &c : str)
c = toupper(c);

3.3 标准库类型vector

  • vector是模板而非类型,它能容纳绝大多数类型的对象作为其元素,但因引用不是对象,不存在包含引用的vector。
  • 如果vector的元素还是vector,C++11之前要求在>>之间添加一个空格。

3.3.1 定义和初始化vector对象

  • 初始化的两个vector对象类型必须相同。
  • 使用拷贝初始化时,只能提供一个初始值。
  • 如果提供的是一个类内初始值,则只能使用拷贝初始化或使用花括号的形式。
  • 值初始化:要么是内置类型,如int默认为0;要么类支持默认初始化,如string默认为空字符串。
  • 注意区分使用()(表示构造)和{}(表示列表初始化)。

3.3.2 向vector对象中添加元素

  • 使用push_back()
  • C++标准要求vector应该能在运行时高效快速地添加元素。
  • 如果循环体内部含有向vector对象添加元素的语句,则不应使用for循环。

3.3.3 其它vector操作

3.4 迭代器介绍

3.4.1 使用迭代器

  • 使用begin()end()获取首迭代器和尾后迭代器。
  • 二者相等则容器为空。
  • 二者返回的迭代器类型是否带const取决于该对象是否是常量。
  • C++引入两个新函数cbegin()cend()方便获得const_iterator的迭代器。

  • 并非所有容器都有下标运算符,与之类似,大多数迭代器没有定义>等符号。(我的理解:定义了下标运算符的容器,其相应的迭代器才能使用>等作比较。)
  • (*iter).mem中的括号不能省。

迭代器类型:

1
2
vector<int>::iterator it1;          
vector<int>::const_iterator it2; //只能读,不能写。

3.4.2 迭代器运算

  • 其中,两个迭代器相减得到的类型是difference_type,vector和string中均有定义,它是有符号的。

3.5 数组

3.5.1 定义和初始化内置数组

  • 默认情况下,数组中的元素执行默认初始化。所以函数内部定义的内置类型的数组会初始化为未定义值。
  • 不能使用auto去推断数组类型,不能建立引用的数组。
  • 使用列表初始化可以忽略掉数组的维度。
  • 字符数组和字符串的关系。
  • 不允许拷贝和赋值。

复杂的数组声明:

1
2
3
4
5
int *ptrs[10];                  // 含有10个int的数组
int &refs[10]; // 错误
int (*Parray)[10]; // 指向一个含有10个int的数组
int (&arrRef)[10] = arr; // 引用一个含有10个int的数组
int *(&arry)[10] = ptrs;
  • 默认情况下,类型修饰符从右向左依次绑定。有括号的由内而外。

3.5.2 访问数组元素

  • 数组下标是<cstddef>中定义的size_t类型,该类型是一种机器相关的无符号类型,被设计得足够大以便能表示内存中任意对象的大小。

3.5.3 指针和数组

  • 大多数表达式中,数组名相当于指向首元素的指针。
  • 指针相当于数组的迭代器。
  • C++11引入begin()end()用于获得数组的首元素指针和尾后指针。(<iterator>
1
2
3
4
5
6
7
8
9
int ia[] = {0,1,2};
auto ia2(ia); // int*类型
decltype(ia) ia3 = {4,5,6}; // 含三个元素的数组类型

int *pbeg = begin(ia), *pend = end(ia);
while(pbeg != pend){
...
++pbeg;
}
  • 两个指针相减的结果类型是ptrdiff_t,定义在<cstddef>,机器相关,有符号。
  • 内置的下标运算符使用的索引值不是无符号类型,即可以为负数,这一点与vector、string等不一样。

3.5.4 C风格字符串

  • 不安全,不推荐。

3.5.5 与旧代码的接口

  • 需要用string的地方往往可以用C风格字符串替代。
  • 需要用C风格字符串的地方不能直接用string替代,而是需要str.c_str(),返回类型是const char*,且在改变str后,这个值可能失去作用。
  • 允许使用数组来初始化对象:
1
2
int int_arr[] = {1, 2, 3};
vector<int> ivec(begin(int_arr), end(int_arr));

3.6 多维数组

  • 多维数组的初始化:
1
2
3
4
5
6
7
int a[3][4] = {
{0, 1, 2, 3},
{1, 2},
{1}
};

int b[2][3] = {1, 2, 3, 4, 5, 6};
  • 多维数组的遍历:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for(size_t i = 0; i != rowCnd; ++i)
for(size_t j = 0; j != colCnt; ++j)
...

for(auto &row : ia) // 一定是引用,否则auto是指针类型,内层就不能再用for了。
for(auto &col : row)
...

for(auto p = ia; p != ia + rowCnt; ++p)
for(auto q = *p; q != *p + colCnt; ++q)
...

for(auto p = begin(ia); p != end(ia); ++p)
for(auto q = begin(*p); q != end(*p); ++q)
...
  • 指针和多维数组:
1
2
3
int ia[3][4];
int (*p)[4] = ia; // p指向含有4个整数的数组
p = &ia[2]; // p指向ia的尾元素
  • 使用类型别名简化多维数组的指针:
1
2
3
4
5
using int_array = int[4];
typedef int int_array[4];
for(int_array *p = ia; p != ia + 3; ++p)
for(int *q = *p; q != *p + 4; ++q)
...

练习

  • 3.1 using的使用。
  • 3.2 string按行和按单词读入。
  • 3.3 string读入时对空白字符的处理。
  • For code like is >> s, input is separated by whitespaces while reading into string s.
  • For code like getline(is, s) input is separated by newline \n while reading into string s. Other whitespaces are ignored.
  • For code like getline(is, s, delim)input is separated by delim while reading into string s. All whitespaces are ignored.
  • 3.4 string的比较。
  • 3.5 string的连接。
  • 3.6 string的遍历。
  • 3.7 string的遍历。
  • 3.8 string的遍历。
  • 3.9 空string的理解。
  • 3.10 string删除指定字符。
  • 3.11 stringfor(auto &:)语句。
  • 3.12 vector的定义。
  • 3.13 vector的定义。注意vector<string> v7{10, "hi"};有10个"hi"
  • 3.14 vector的输入。
  • 3.15 vector的输入。
  • 3.16 vector的容量和遍历。
  • 3.17 vector的输入和遍历。
  • 3.18 空vector的理解。
  • 3.19 vector的定义。
  • 3.20 vector的输入和访问。
  • 3.21 迭代器的使用。
  • 3.22 迭代器的使用。
  • 3.23 迭代器的使用。
  • 3.24 迭代器的使用。
  • 3.25 迭代器的使用。
  • 3.26 迭代器不能和迭代器相加。
  • 3.27 数组的定义。
  • 3.28 数组的默认初始化。
  • 3.29 数组的缺点。
  • 3.30 数组的索引范围。
  • 3.31 数组的定义。
  • 3.32 数组的拷贝。
  • 3.33 数组的默认初始化。
  • 3.34 数组指针的运算。
  • 3.35 数组指针的使用。
  • 3.36 比较两个数组/两个vector。
  • 3.37 字符数组的遍历。
  • 3.38 两个指针相加没有意义。
  • 3.39 比较两个string/两个C风格字符串。
  • 3.40 字符数组的定义、拷贝、连接。
  • 3.41 用整型数组初始化vector<int>对象。vector<int> v(begin(arr), end(arr));
  • 3.42 将vector<int>拷贝给整型数组。
  • 3.43 综合练习。
  • 3.44 综合练习(使用类型别名)。
  • 3.45 综合练习(使用auto)。