第 3 章:字符串、向量和数组
本章重点:string 和 vector。
string 表示可变长的字符序列
vector 存放的是某种给定类型对象的可变长序列
3.1 命名空间的 using 声明
using namespace::name;
头文件不应包含 using 声明,因为头文件的内容会拷贝到所有引用它的文件中去
3.2 标准库类型 string
#include <string>
using std::string;
w定义和初始化 string 对象
string 对象上的操作
输入输出
输入将会忽略开头的空白(空格、换行),从第一个字符开始,直到遇到下一个空白。如输入 “ hello world! ”,输出 “hello”。
getline
getline 函数返回的那个换行符实际上被丢弃掉了
string::size_type
size 方法返回的不是 int 或 unsigned ,而是 string::size_type
。是一个无符号整数型,能够存放任何 string 的大小。需要注意的是 size() < n
当 n 是 负数时,结果大概率是 true,n 会被转为一个 无符号值。
字符串字面值
字符串字面值是为了兼容 C 的,字符串字面值和 string 是不同的类型。
string 可以与字符串字面值相加,得到新的 string。但是字符串字面值不能与字符串字面值相加。
处理 string 中的字符串
基于范围的 for 语句
for (declaration: expression)
statement
for (auto c : str) {
cout << c << endl;
}
通过引用改变值
for (auto &c : str) {
c = toupper(c);
}
下标运算符
s[0]
是第一个字符
s[s.size() - 1]
是最后一个字符
下标必须大于等于 0,而小于 size() 的值,可以设置下标类型为:string::size_type
。
其他
如何退出 while(cin >> str)
循环
键入 ctrl + d
跳出循环
3.3 标准库类型 vector
vector
表示对象的集合,每个对象的类型都相同,并有一一对应的索引。
#include <vector>
using std::vector;
vector 是模板而非类型,由 vector 生成的类型必须包含 vector 中元素的类型,例如 vector<int>
。
定义和初始化 vector 对象
列表初始化 vector 对象
vector<string> arcticles = { "a", "an", "the" };
值初始化
10 个元素,每个都是空 string 对象。
vector<string> ivec(10);
向 vector 对象添加元素
push_back
负责向 vector 末尾添加值。
vector 对象能高效增长。
注意:范围 for 语句中最好不要有改变遍历序列大小的操作。
其他 vector 操作
empty、size 跟 string 功能完全一致,size 返回 vector<T>::size_type
不能用下标形式添加元素
向一个空的 vector 直接 vec[idx] = xxx
是错误的,下标运算符用于访问已存在的元素。
确保下标合法的一种有效手段就是尽可能使用范围for语句。
3.4 迭代器
使用迭代器
v.begin(); // 指向第一个元素
v.end(); // 指向尾元素的下一个位置
end()
返回的迭代器称为尾后迭代器(off-the-end iterator, end iterator)。
如果容器为空,他们指向同一个位置,都是尾后迭代器。
迭代器运算符
迭代器的 for 循环,使用 !=
判断循环结束条件,有些迭代器不支持 <
。
for (auto is = s.begin(); is != s.end() && !isspace(*it); ++it) {
*it = toupper(*it);
}
迭代器类型
vector<int>::iterator it;
strinng::iterator it2;
vector<int>::const_iterator itConst;
strinng::const_iterator itconst2;
一般无需关系迭代器类型,直接 auto 即可。如果对象是常量,那么就会得到 xxx::const_iterator
。
有时候默认行为不是想要的,可以通过 cbegin()
cend()
得到 xxx::const_iterator
。
解引用、成员访问操作
(*it).empty()
必须要加括号。
it->empty()
使用箭头运算符,结合解引用和成员访问。
注意:凡是使用迭代器的循环体,都不要向容器添加元素。
迭代器运算
3.5 数组
数组类似于 vector,但是大小固定,不能随意增加。
定义和初始化数组
数组维度必须是常量表达式
int arr_1[10];
constexpr unsigned len = 42;
int arr_2[len];
数组的内部的某类型的元素,同内置类型的变量一样默认初始化为未定义的值。
显示的初始化数组
int arr_3[3] = {0, 1, 2}; // 维度是 3
int arr_4[] = {0, 1, 2}; // 维度是 3
int arr_5[5] = {0, 1, 2}; // 维度是 5,相当于 {0, 1, 2, 0, 0}
string arr_6[3] = {"hello"} // 维度是 3, 相当于 {"hello", "", ""}
初始值大于长度会报错。
字符数组的特殊性
需要注意的是:字符串字面值的结尾处还有一个空字符。
char a2[] = {'C', '+', '+'}; // 长度为 3
char a3[] = "C++"; // 长度为 4
char a4[3] = "C++"; // 报错,长度不足
不允许拷贝和赋值
不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值。
复杂的数组声明
int *ptrs[10]; // ptrs 是一个含有 10 个整形指针,int* ptrs[10] 这样写可能更好理解
int &refs[10] = /* ? */ // 错误:不存在引用的数组
int (*Parray)[10] = &arr; // Parray 指向一个含有 10 个整数的数组
int (&arrRef)[10] = arr; // arrRef 引用一个含有 10 个整数的数组
int *(&ptrsRef)[10] = ptrs; // ptrsRef 引用一个含有 10 个整形指针的数组
访问数组元素
数组的下标类型为 size_t
。
下标越界试图访问非法内存区域,也会产生错误。
指针和数组
大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。
int arr[] = {0, 1, 2};
// 下面三个操纵等价
int *p1 = &arr[0];
int *p2 = arr;
auto *p3 = arr;
指针也是迭代器
指向数组元素的执政还支持 迭代器的运算
int arr[] = {0, 1, 2};
int *p = arr; // 指向 arr[0]
++p; // 指向 arr[1]
得到尾后指针
int *lp = &arr[3]; // 注意:arr 实际的最后一个下标是 2, 而这种方式可以拿到尾后指针
使用标准库方法获得指针:begin(arr)
end(arr)
需要特别注意的,尾后指针不能执行解引用和递增操作。
下标
内置的下标运算符所用的索引值不是无符号类型。
C 风格字符串
字符串字面值是 C++ 由 C 继承而来的 C 风格字符串。存放在字符数组中,以空字符 \0
结束。
C 标准 string 函数
现代的C++程序应当尽量使用 vector 和迭代器,避免使用内置数组和指针;应该尽量使用 string,避免使用C风格的基于数组的字符串。
3.6 多维数组
多维数组初始化
int ia[2][3] = {
{1, 2, 3},
{4, 5, 6}
}