Chapter 3 字符串、向量和数组
3.1 命名空间的 using 声明
我们在使用库函数,譬如 的时候,我们需要显示地将命名空间 std 添加上去。会显得比较繁琐。现在,我们将介绍一种更安全的方式,,通过使用 using 声明,如下:
#include <iostream>
using namespace std; // 使用命名空间 std
// 也可以使用独立的 using 声明
// using namespace std::cin;
// using namespace std::cout;
int main()
{
int i, j;
// i 的输入输出函数不需要添加 std
cin >> i >> j;
cout << i << " " << j << endl;
return 0;
}
头文件不应包含 using 声明
位于头文件的代码,一般不应该使用 using 声明。这是因为头文件的内容会拷贝到所有引用它的文件中去,如果头文件里有某个 using 声明,那么每个使用了该头文件的文件就都会有这个声明。
3.2 标准库类型 string
标准库类型 string 表示可变长的字符序列,使用 string 类型必须首先包含 string 头文件,string 定义在命名空间 std 中。
3.2.1 定义和初始化 string 对象
string s1; // 默认初始化,s1 是一个空字符串
string s2 = s1; // s2 是 s1 的副本 也相当于 string s2(s1);
string s3 = "cpp"; // s3 是该字符串字面值的副本 也相当于 string s3("cpp");
string s4(5, 'a'); // s4 = "aaaaa"
直接初始化和拷贝初始化
如果使用等号(=)进行初始化,则进行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去。与之相反,如果不使用等号,则执行的是直接初始化。
3.2.2 string 对象上的操作
| 操作 | 含义 |
|---|---|
| os << s | 将s写到输出流 os 中,返回 os |
| is >> s | 从 is 中读取字符串赋给s,字符串以空白分隔 |
| getline(is, s) | 从 is 中读取一行赋给 s,返回 is |
| s.empty() | s 为空返回 true,否则返回 false |
| s.size() | 返回 s 中字符的个数 |
| s[n] | 返回 s 中第n个字符的引用,从0开始计数 |
| s1 + s2 | 返回 s1 和 s2 连接后的结果 |
| s1 = s2 | 用 s2 的副本代替 s1 中原来的字符 |
| s1 == s2 | 如果 s1 和 s2 所含字符完全一样,则相等,区分大小写 |
| <, <=, >, >= | 字典序比较 |
读写 string 对象
在读取操作时, string 对象会自动忽略开头的空白(即空格符、换行符、制表符),并从第一个真正的字符开始读起,直到遇见下一处空白为止。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
cin >> s; // 将 string 对象读入 s,遇到空白停止
cout << s << endl;
return 0;
}
同样,string 对象也可以连读:
cin >> s1 >> s2;
cout << s2 << s2 << endl;
读取未知数量的 string 对象
int main()
{
string s;
while(cin >> s)
{
cout << s << endl;
}
return 0;
}
使用 getline 读取一整行
有时我们希望能在最终得到的字符串中保留输入时的空白字符,这时应该用 getline 函数与代替原来的 >> 运算符。
int main()
{
string s;
while(getline(cin, s))
{
cout << s << endl;
}
return 0;
}
string 的 empty 和 size 操作
empty 函数根据 string 对象是否为空返回一个对应的布尔值。
size 函数返回 string 对象的长度。
string::size_type 类型
size 函数返回的是一个 string::size_type 类型的值,其是一个无符号类型的值,而且足够存放下任何 string 对象的大小。
比较 string 对象
string 对象的比较运算是区分大小写的:
- 如果两个 string 对象的长度不同,而且较短 string 对象的每个字符都与较长 string 对象对应位置上的字符相同,那么较短 string 对象小于较长 string 对象。
- 如果两个 string 对象在某些对应的位置不一致,则 string 对象比较的结果其实是两个 string 对象中第一对相异字符比较的结果。
如:
string s1 = "cppprimer";
string s2 = "cppprimer5"; // s1 < s2
string s3 = "cppprimar"; // s1 > s3
string s4 = "caa"; // s1 > s4
字面值和 string 对象相加
当把 string 对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符的两侧的运算对象至少有一个是 string:
string s1 = "a", s2 = "b";
string s3 = s1 + "bc";
string s4 = s1 + 'b' + "c"; // 正确,因为是先进行左边+加法,返回 string 对象,再进行右边加法
string s5 = 'b' + "c" + s1; // 错误
3.2.3 处理 string 对象中的字符
我们经常需要单独处理 string 对象中的字符,比如检查一个 string 对象是否包含空白,或者把 string 对象中的字母改成小写,再或者查看某个特定的字符是否相等。在 ctype 头文件中定义了一组标准库函数处理这部分工作:
| 操作 | 含义 |
|---|---|
| isalnum© | 当 c 是字母或者数字时为真 |
| isalpha© | 当 c 是字母时为真 |
| iscntrl© | 当 c 是控制字符时为真 |
| isdigit© | 当 c 是数字时为真 |
| isgraph© | 当 c 不是空格但可打印时为真 |
| islower© | 当 c 是小写字母时为真 |
| isprint© | 当 c 是可打印字符时为真 |
| ispunct© | 当 c 是标点符号时为真 |
| isspace© | 当 c 是空白时为真 |
| isupper© | 当 c 是大写字母时为真 |
| isxdigit© | 当 c 是十六进制数字时为真 |
| tolower© | 如果 c 是大写字母,输出对应的小写字母,否则原样输出 c |
| toupper© | 如果 c 是小写字母,输出对应的大写字母,否则原样输出 c |
使用 for 语句处理 string 中的每个字符
string s = "hello, world";
for(auto c: s)
{
if(islower(c))
{
...;
}
if(isdigit(c))
{
...;
}
cout << c << endl;
}
如果想要改变 string 对象中字符的值,必须把循环变量定义成引用类型。
for(auto &c: s){
c = toupper(c);
}
cout << s << endl; // HELLO,WORLD
我们也可以通过 size 函数得到 string 对象的长度,以达到只处理前面一些字符,或者通过引用的方式只处理其中几个字符。
3.3 标准库类型 vector
标准库类型 vector 表示对象的集合,其中所有对象的类型都相同。因为 vector 容纳着其他对象,所以它也被称为容器(container)。
要想使用 vector,必须包含适当的头文件。
#include <vector>
using std::vector;
C++语言既有类模板(class template),也有函数模板,其中 vector 是一个类模板。模板本身不是类或函数,相反可以将模板看作为编译器生成类或函数编写的一份说明。编译器根据模板创建类或函数的过程称为实例化(instantiation)。
对于类模板来说,我们通过提供一些额外信息来指定模板到底实例化成什么样的类,以 vector 为例:
vector<int> ivec; // 保存 int 类型的 vector 对象
vector<vector<string>> file; // 保存 vector<int> 类型的 vector 对象
3.3.1 定义和初始化 vector 对象
以下以 int 为例:
vector<int> v1; // 空
vector<int> v2(v1);
vector<int> v3 = v1; // v2 v3 都是v1的副本
vector<int> v4(5, 1); // v4 中包含了5个 int 型元素,其值都为1
vector<int> v5(10); // v5 有10个 int 型对象,其值为默认初始值
vector<int> v6{1, 2, 3};
vector<int> v7 = {1, 2, 3}; // v6、v7 包含了3个 int 型元素,其值分别为 1,2,3
3.3.2 向 vector 对象中添加元素
int val = 3;
vector<int> arr;
arr.push_back(val); // 向arr尾部添加元素 val
3.3.3 其他 vector 操作
vector<int> arr;
arr.size(); // 返回元素个数
arr.empty(); // 返回是否为空
arr.push_back(); // 添加元素
arr.pop_back(); // 删除末尾元素
arr[i]; // 第i个位置上的引用
arr1 == arr2; // 是否各个位置上对应元素都相同
3.4 迭代器介绍
除了使用下标运算符来访问 string 对象的字符或 vector 对象的元素,我们还可以使用迭代器来访问。
3.4.1 使用迭代器
我们使用这些类型的成员 begin 和 end 来使用迭代器。
vector<int> arr;
auto b = arr.begin(); // b 表示 arr 的第一个元素
auto e = arr.end(); // e 表示 arr 尾部元素的下一个位置
迭代器运算符
*iter; // 返回迭代器 iter 所指元素的引用
iter->mem; // 获取该元素的名为 mem 的成员
++iter; // 指向容器的下一个元素
--iter; // 指向容器的上一个元素
iter1 == iter2; // 判断两个迭代器是否相等
begin 和 end 运算符
C++11新标准中引入了两个新函数,分别为 cbegin 和 cend,用于获得 const_iterator 类型的返回值,只可读不可写。
3.4.2 迭代器运算
iter + n; // 仍是一个迭代器,与之前相比向前移动了 n 个位置
iter - n; // ......向后移动了 n 个位置
iter += n;
iter -= n;
iter1 - iter2; // 返回两者之间的距离
> >= < <=; // 参与运算的迭代器必须指向同一个容器,谁的位置在前面,谁下
3.5 数组
3.5.1 定义和初始化内置数组
数组是一种复合类型。数组的声明形如 a[d],其中 a 是数组的名字,d 是数组的维度(必须是一个常量表达式)。
显式初始化数组元素
可以对数组的元素进行列表初始化,此时允许忽略数组的维度。
const unsigned sz = 3;
int a1[sz] = {0, 1, 2};
int a2[] = {0, 1, 2};
int a3[5] = {0, 1, 2}; // == {0, 1, 2, 0, 0}
string a4[3] = {"hi", "bye"}; // == {"hi", "bye", ""}
int a5[2] = {0, 1, 2}; // 报错
字符数组
字符数组有一种额外的初始化形式,我们可以用字符串字面值对此类数组初始化,当使用这种方式时,一定要注意字符串字面值的结尾处还有一个空字符,这个空字符也会想字符串的其他字符一样被拷贝到字符数组中去:
char a1[] = {'C', '+', '+'}; // 列表初始化,没有空字符
char a2[] = {'C', '+', '+', '\0'}; // 列表初始化,含有显式的空字符
char a3[] = "C++"; // 自动添加表示字符串结束的空字符
const char a4[6] = "Daniel"; // 错误:没有空间可存放空字符
不允许拷贝和赋值
不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值。
int a[] = {0, 1, 2};
int a2[] = a; // 错误
a2 = a; // 错误
理解复杂的数组声明
int *ptrs[10]; // ptrs 是含有是个整形指针的数组
int &refs[10]; // 错误,不存在引用的数组
int (*Parray)[10] = &arr; // Parray 指向一个含有 10 个整数的数组
int (&arrRef)[10] = arr; // arrRef 引用一个含有 10 个整数的数组
3.5.2 访问数组元素
在使用数组下标的时候,通常将其定义为 size_t 类型,在 cstddef 头文件中定义了 size_t 类型。
3.5.3 指针和数组
我们可以使用指针来访问数组元素
char arr[] = {'a', 'b', 'c'};
char *p1 = &arr[0]; // p1 指向arr的第一个元素
char *p2 = arr; // p1 = p2
当使用数组作为一个 auto 变量的初始值时,推断得到的类型是指针而不是数组:
int ia[] = {0, 1, 2, 3};
auto ia2(ia); // ia2 是一个整形指针
当使用 decltype 关键字时,上述转换不会发生:
decltype(ia) ia3 = {0, 1, 2, 3}; // ia3 是一个含有四个元素的数组
指针也是迭代器
int arr[] = {0, 1, 2, 3};
int *p = arr; // p 指向 arr 的第一个元素
++p; // p 指向 arr[1]
标准库函数 begin 和 end
与之前容器中的两个同名成员类似,C++11标准引入了两个名为 begin 和 end 的函数,让指针的使用更简单、高效。
#include <iterator>
int[] arr = {0, 1, 2, 3};
int *b = begin(arr);
int *e = end(arr);
指针运算
给指针加上一个整数,得到的新指针仍需指向同一数组的其他元素,或者也指向同一数组的尾元素的下一位置。
int *p1 = arr;
int *p2 = p1 + 4; // 指向 arr[4]
两个指针相减的结果类型是一种名为 ptrdiff_t 的标准库类型,定义在 cstddef 头文件中。
3.6 多维数组
严格来说,C++语言中没有多维数组,通常所说的多维数组其实是数组的数组。
int a[3][4]; // 大小为3的数组,每个元素是含有4个整数的数组
多维数组的初始化
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 a[3][4] = {{0}, {4}, {8}}
// 只初始化第一行
int a[3][4] = {0, 3, 6, 9};
1029



被折叠的 条评论
为什么被折叠?



