《C++ Primer》第三章 - 字符串、向量和数组
1. 命名空间和 using
声明
1.1 命名空间的 using
声明
std::cin
编译器应从标准输入std
中寻找名字cin
1.2 using
声明
-
using namespace::name;
-
位于头文件的代码一般来说不应该使用
using
声明
2. 标准库类型 string
2.1 定义和初始化 string
对象
#include <string>
using std::string;
string s1; // 默认初始化,s1 是一个空串
string s2(s1); // s2 是 s1 的副本,direct initialization
string s2 = s1; // 等价于上一条语句,copy initialization
string s3("value"); // s3 是字面值 “value” 的副本,除了字面值最后的哪个空字符外
string s3 = "value"; // 等价于上一条语句
string s4(n, 'c'); // 把 s4 初始化为连续 n 个字符 ’c' 组成的串,direct initialization
string s5 = string(10, 'c') // copy initialization,可读性较差
2.2 string
对象上的操作
-
读写
string
对象#include <string> using std::cin; std::cout; std::endl; using std::string; int main() { string s; cin >> s; // 将 string 对象读入 s,遇到空白停止,忽略开头空白(空格、回车、制表符等) cout << s << endl; return 0; }
-
读取未知数量的
string
对象#include <string> using std::cin; std::cout; std::endl; using std::string; int main() { string word; while (cin >> word) // 反复堆区,直至流无效,遇到文件结束标记或者非法输入后循环结束 cout << word << endl; // 诸葛输出单词,每个单词后面紧跟一个换行 return 0; }
-
getline
从输入流中读入内容,直到遇到换行符为止(换行符也被读进),然后把所读的内容存入到string
对象中去(不包括换行符)。getline
只要一遇到换行符就结束读取操作并返回结果,因此最输入换行符的话会返回空的string
int main() { string line; while (getline(cin, line)) // 从 cin 中读入内容,直到遇到换行符为止(换行符也被读进) cout << line; // 每行过后加上换行符并刷新显示缓存区 return 0; }
-
empty
和size
// 改写上个语句块中的对应内容,只输出非空行 while (getline(cin, line)) if (!line.empty()) // empty 函数根据 string 对象是否为空返回布尔值,空 则返回 真 cout << line << endl; // 改写上个语句块中的对应内容,只输出长度超过80的行 while (geline(cin, line)) if (line.size() > 80) cout << line << endl;
size
函数返回值的类型是string::size_type
类型,是一个无符号的整型数,因此表达式中已经有了size()
函数时就不要再使用int
,以避免混用int
和unsigned
可能带来的问题
-
比较
string
对象- 对大小写敏感;
- 如果两个
string
对象的长度不同,而且较短的string
对象的每个字符都与较长string
对象对应位置上的字符相同,则较短的string
对象 小于 较长的string
对象; - 如果两个
string
对象爱某些对应的位置上不一致,则string
对象比较的结果其实是string
对象中第一对相异字符比较的结果(按字典排序);
-
string
对象相加:每个加法运算符的两侧的运算对象至少有一个是string
对象,字面值字符串也string
是不同的类型
string s1 = "hello", s2 = "world";
string s3 = s1 + "," + s2 + '\n'; // 正确:将一个 string 对象和一个字面值相加
string s4 = ”hello“ + "," + s2; // 错误:不能把字面值直接相加
-
处理
string
对象中的字符-
cctype
头文件中的函数函数名 c 为何种类型时为真 / 函数功能 isalnum(c)
字母或者数字 isalpha(c)
字母 iscntrl(c)
控制符 isdigit(c)
数字 isgraph(c)
不是空格但是可打印 iflower(c)
小写字母 isprint(c)
可打印字符(空格或者具有可视形式) ispunct(c)
标点符号(不是上面的各种类型) isspace(c)
空白(控制、横向制表符、纵向制表符、回车符、换行符、进纸符) isupper(c)
大写字母 isxdigit(c)
十六进制数字 tolower(c)
将 c 变为或保持为小写字母 toupper(c)
将 c 变为或保持为大写字母 -
处理每个字符
string str("some string"); for (auto c : str) // 赋值,字符串 str 不改变 cout << c << endl; for (auto &c : str) c = toupper(c); // c 是一个引用,字符串 str 被改变 cout << c << endl;
-
处理部分字符
string str("some string"); if (!str.empty()) str[0] = toupper(str[0]); // 下标运算符 [] 返回字符串在索引值相应位置的字符的引用,原字符串改变
-
3. 标准库类型 vector
vector
表示对象的集合,其中所有对象的类型都相同,常被称作容器 container
。- 不能在范围
for
循环中向vector
对象添加元素。
3.1 定义和初始化 vector
对象
#include <vector>
using std::vector;
vector<int> ivec; // 初始状态为空
vector<int> ivec2(ivec); // 把 ivec 的元素拷给 ivec2
vector<int> ievc3 = ievc; // 把 ivec 的元素拷给 ivec3
vector<string> sevc(ivec2); // 错误:把 ivec 的元素是 string 对象,不是 int
3.2 列表初始化
vector<string> v1{"a", "b", "c"}; // 列表初始化
vector<string> v2("a", "b", "c"); // 错误
// () 表示构建 vector 对象
vector<int> v3(10); // v3 中有10个元素,每个的值都是0
vector<int> v4{10}; // v4中有1个元素,其值为10
vector<int> v5(10, 1); // v5 中有10个元素,每个的值都是1
vector<int> v6{10, 1}; // v6 中有两个元素,分别是10和1
vector<string> v7("hi"); // 错误:不能用自负床字面值构建 vector 对象
vector<string> v8{10}; // v8 中有10个默认初始化的元素
vector<string> v9{10, "hi"}; // v9 中有10个值为 ”hi“ 的元素
3.3 向 vector
对象中添加元素:push_back
vector<int> v2;
for (int i = 0; i != 100; ++i)
v2.push_back(i);
string word;
vector<string> text;
while(cin >> word)
text.push_back(word);
vector<int>::size_type // 正确
vector::size_type // 错误,需要指定类型
3.4 索引
// 以10分为一个分数段统计成绩的数量:0~9,10~19,…,90~99,100
vector<unsigned> scores(11, 0); // 11个分数段,初始化为0
unsigned grade;
while (cin >> grade) {
if (grade <= 100)
++scores[grade/10];
}
4. 迭代器
4.1 begin
和 end
auto b = v.begin(), e = v.end(); // b 和 e 类型相同
begin
负责返回指向第一个元素的迭代器。end
尾后迭代器(off-the-end iterator),负责返回指向容器(或string
)”为元素的下一位置(one past the end)“的迭代器。- 如果容器为空,则
begin
和end
返回的是同一个迭代器,都是尾后迭代器。 begin
和end
返回的具体类型有对象是否是常量决定,如果对象是常量,返回const_interator
;否则,返回iterator
。cbegin
和cend
固定返回const_interator
。
4.2 迭代器运算符
符号 | 作用 |
---|---|
*iter | 返回迭代器 iter 所指元素的引用 |
iter->men | 解引用 iter 并获取该元素的名为 mem 的成员,等价于 (*iter).mem |
++iter | 令 iter 指示容器中的下一个元素 |
--iter | 令 iter 指示容器中的上一个元素 |
iter1 == iter2 | 判断两个迭代器是否相等,如果两个迭代器知识的是同一个元素 |
iter1 != iter2 | 或者它们是同一个容器的尾后迭代器,则相等;反之,不相等 |
- 不能对
end
返回的迭代器进行递增或者解引用的操作
4.3 迭代器类型
-
拥有迭代器的标准库类型使用
iterator
和const_interator
来表示迭代器的类型vector<int>::interator it; // it 能读写 vector<int> 元素 string::iterator it2; // it2 能读写 string 对象中的字符 vector<int>::const_iterator it3; // it3 只能读元素,不能写元素 string::const_interator it4; // it4 只能读字符,不能写字符
4.4 结合解引用和成员访问操作
(*it).empty() // 解引用 it,然后调用结果对象的 empty 成员
*it.empty() // 错误:试图访问 it 的名为 empty 成员
// 依次输出 text 的每一行直至遇到第一个空白行为止
for (auto it = text.cbegin(); it != text.cend() && !it->empty(); ++it)
cout << *it << endl;
4.5 迭代器使用的注意事项
- 但凡是使用了迭代器的循环体,都不要想迭代器所属的容器添加元素;
- 不要解引用非法迭代器或者尾后迭代器;
4.6 迭代器运算
auto mid = vi.begin() + vi.size() / 2; // 计算得到最接近 vi 中间元素的一个迭代器
if (it < mid)
// 处理 vi 的前半部分元素
// 使用迭代器实现有序序列的二分搜素,搜索目标是 sought
// text 必须是有序的
auto beg = text.begin(), end = text.end();
auto mid = beg + (end - beg) / 2;
while (*mid != sought) {
if (*mid < sought) beg = mid;
if (*mid > sought) end = mid;
mid = beg + (end - beg) / 2;
}
- 参与比较的两个迭代器必须合法并且指向的是同一个容器的元素;
- 两个迭代器相减得到的结果是两个迭代器的距离,其类型时名为
difference_type
的带符号整型数;
5. 数组
5.1 定义和初始化内置数组
unsigned cnt = 42; // 不是常量表达式
constexpr unsigned sz = 3; // 常量表达式
int arr[10]; // 含有10个整数的数组
int *parr[sz]; // 含有3个整数型指针的数组
string bad[cnt]; // 错误:cnt 不是常量表达式
int a1[] = {0, 1, 3}; // 维度是3的数组
int a2[sz] = {0, 1}; // 维度是3的数组,{0, 1, 0}
int a3[sz] = {0, 1, 2, 3}; // 错误:初始值过多
// 字符数组的特殊性
char a4[] = {'C', '+', '+'}; // 列表初始化,没有空字符
char a5[] = {'C', '+', '+', '\0'}; // 列表初始化,含有显式的空字符
char a6[] = "C++"; // 自动添加表示字符串结束的空字符
const char a4[4] = "Ulic"; // 错误:没有空间可以添加空字符
char a7[] = a6; // 错误:不允许使用一个数组初始化另一个数组
a8 = a6; // 错误:不能把一个数组直接赋值给另一个数组
// 一些比较复杂的数组声明
int *ptrs[10]; // ptrs 是含有10个整型指针的数组
int &refs[10] = /* ? */; // 错误:不存在引用的数组
int (*Parray)[10] = &arr; // Parray 是一个指针,指向维度为10的整型数组
int (arrRef)[10] = arr; // arrRef 是一个引用,引用的是维度为10的整型数组
int *(&arry)[10] = ptrs; // arry 是一个引用,引用的对象是维度为10的数组,数组的元素类型是整型指针
- 定义数组的时候必须指定数组的类型,不允许使用
auto
关键字有初始值的列表推断类型; - 和
vector
一样,数组的元素应为对象,因此不存在引用的数组; - 数组不允许拷贝和赋值;
- 理解数组声明的含义时最好从数组的名字开始由内向外阅读;
- 数组元素的个数的值类型为
size_t
;
5.2 访问数组元素
// 以10分为一个分数段统计成绩的数量:0~9,10~19,…,90~99,100
unsigned scores[11] = {}; // 11个分数段,初始化为0
unsigned grade;
while (cin >> grade) {
if (grade <= 100)
++scores[grade/10];
}
// 输出 scores 中的每个元素
for (auto i : scores)
cout << i << " ";
cout << endl;
5.3 数组和指针
int arr[] = {0, 1, 2, 3, 4, 5};
auto a1(arr); // a1 是一个指针
auto a1(&arr[0]); // 与上一条命令等价
decltype(arr) a2 = {0, 1, 2, 3, 4, 5}; // a2 是数组,而不是指针
// 用指针遍历数组中的元素
int *e = &arr[6]; // 指向数组 arr 尾元素的下一位置
for (int *p = arr; p != e; ++p)
cout << *p << endl;
- 在大多数表达式中,使用数组类型的对象其实时使用一个只想该数组首元素的指针;
- 当使用
decltype
关键字时,编译器不会将数组名字替换为指向数组首元素的指针; - 两个指向同一数组元素的指针相减得到的结果是他们之间的距离,其类型是名为
ptrdiff_t
的标准库类型,定义在cstddef
头文件中;
5.4 标准库函数 begin
和 end
#include <iterator>
int arr[] = {0, 1, 2, 3, 4, 5};
int *beg = begin(arr); // 指向 arr 首元素的指针
int *last = end(arr); // 指向 arr 尾后元素的指针
5.5 指针和下标
int arr[] = {0, 1, 2, 3, 4, 5};
int *p = &arr[2];
int a = p[1]; // 等价于 *(p + 1),也就是 arr[3]
int b = p[-2]; // 等价于 *(p - 2),也就是 arr[0]
- 内置的下标运算符所用的索引值不是无符号类型,这一点与
vector
和string
不一样;
5.6 多维数组
-
多维数组的初始化
int a[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; // 等价于 int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} } int ia[3][4] = {{0}, {4}, {8}}; // 初始化每一行的第一个元素
-
多维数组的下标引用
// 创建二维数组,以每个元素的索引值作为其元素值 constexpr size_t rowCnt = 3, colCnt = 4; int arr[rowCnt][colCnt]; for (size_t i = 0; i != rowCnt; ++i) { // 对于每一行 for (size_t j = 0; j != colCnt; ++j) { // 对于每一列 arr[i][j] = i * colCnt + j; } }
-
使用范围
for
语句处理多维数组// 功能同上一个代码块 constexpr size_t rowCnt = 3, colCnt = 4; int ia[rowCnt][colCnt]; size_t cnt = 0; for (auto &row : ia) // 对于外层数组的每一个元素 for (auto &col : row) { // 对于内层数组的每一个元素 col = cnt; ++cnt; }
-
ia
是一个维度为 3 的数组 的首个元素,这个元素是一个维度为 4 的数组; -
循环中,
row
分别是对ia[0]
、ia[1]
、ia[2]
的引用,ia[0]
、ia[1]
、ia[2]
均是是维度为 4 的数组; -
col
分别是对ia[0]
、ia[1]
、ia[2]
这三者中各个元素的引用; -
要使用范围 for 语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型;
for (const auto &row : ia) for (auto col : row) cout << col << endl; // 正确 for (auto row : ia) for (auto col : row) // 错误:编译器会自动将数组形式的元素转换成指针, /* ? */ // 因此 row 的类型是 int*,无法遍历
-
-
指针和多维数组
int arr[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}; for (auto p = begin(arr); p != end(arr); ++p) { for (auto q = begin(*p); q != end(*p); ++q) cout << *q << " "; cout << endl; }