本文最后更新于:2023年8月25日 凌晨
第3章 字符串、向量和数组
1. 命名空间的using声明
在前面的示例程序中,输入和输出都是写成 std::cin ,
std::cout , 我们可以在使用前使用 using
声明
1 2 3 4 5 6 7 8 9 10
| #include <iostream> using std::cin; using std::cout; using std::endl; int main() { int v1, v2; cin >> v1 >> v2; cout<<"Ths sum is"<< v1 + v2 <<endl; return 0; }
|
一行可放多条using声明语句,但每个名字都需要独立的using声明。
实际更常见的做法是使用 using namespace std;
把整个命名空间都引入,这样std命名空间下的成员都可以使用了。
注意:头文件中尽量不要引入整个命名空间,因为头文件的内容会拷贝到所有引用它的文件中去,如果头文件里有某个
using 声明,那么每个使用了该头文件的文件就都会有这个声明
,这样可能会和自己写的一些类名冲突。
2. 标准库类型 string
string表示可变长的字符序列,string的使用需要包含一个头文件和命名空间
1 2
| #include <string> using namespace std;
|
2.1 定义和初始化string对象
1 2 3 4 5 6
| string s1; string s2(s1); string s2 = s1; string s3("value"); string s3 = "value"; string s4(n, 'c');
|
2.2 string对象上的操作
1 2 3 4 5 6 7 8 9 10 11
| os << s; is >> s; getline(is, s); s.empty(); s.size(); s[n]; s1 + s2; s1 = s2; s1 == s2; s1 != s2; < , <=, >, >=
|
size()函数返回的类型是string::size_type
字面值和string对象相加
当把 string
对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符
(+) 的两侧的运算对象至少有一个是string。
1 2 3 4 5
| string s1 = "hello"; string s4 = s1 + ", "; string s5 = "hello" + ", "; string s6 = s1 + ", " + "world"; string s7 = "hello" + ", " + s2;
|
字符串字面值与string是不同的类型
2.3 处理string对象中的字符
在头文件 cctype 中定义了一组相关的函数

cctype
是c语言的头文件,在c++中包含c的头文件有两种形式
#include <ctype.h> 和c语言一样
#include <cctype> 不加 .h
而是在前面加一个 c
基于范围的for语句
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| for (declaration : expression) statement
string str = "helloWorld"; for (auto s : str) { cout << s << endl; }
for (auto &c : str) { c = toupper(c); } cout << str << endl;
|
基于范围的for,只适用于可迭代的对象
3. 标准库类型 vector
标准库类型 vector
表示对象的集合,其中所有对象的类型都相同。用vector需要包含下面的头文件和声明命名空间
1 2
| #include <vector> using std::vector;
|
vector
类似于数组,但是比数组用于更多的操作。vector是一个模板类,所谓模板就是该类内部中的属性没有指定某种特定的数据类型,我们可以在声明
vector时指定数据类型(包括基本类型和自定义类型)
1 2 3
| vector<int> ivec; vector<Sales_item> Sales_vec; vector<vector<string>> file;
|
3.1 定义和初始化vector对象
定义vector对象的常用方法

注意 () 和 {} 初始化vector对象的区别。
()是用来构造vector对象;{}是列表初始化该对象
如果初始化时使用了花括号的形式但是提供的值又不能用来列表初始化,就要考虑用这样的值来构造
vector对象了
1 2 3 4
| vector<string> v5{"hi"}; vector<string> v6("hi"); vector<string> v7{10}; vector<string> v8{10,"hi"};
|
3.2 vector操作
vector常用的操作

1 2 3 4 5
| vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9}; for (auto &i : v) i *= i; for (auto i : v) cout << i << " ";
|
vector使用注意事项:
- 不能使用下标形式添加元素
- 不要在范围for中改变vector的大小(比如增加元素等操作)
4. 迭代器
迭代器可以理解成一种特殊的指针,他有指针类似的操作,除此之外还有自己独特的一些操作。
4.1 使用迭代器
通常是使用 being 和 end
方法获取迭代器,begin 返回第一个元素,end
返回最后元素的下一个位置,所以end
返回的迭代器叫做尾后迭代器
1
| auto b = v.begin(), e = v.end();
|
如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器
迭代器和指针类似,所以指针有的运算符,迭代器基本也有

因为 end
返回的迭代器并不实际指示某个元素,所以不能对其进行递增或解引用的操作。
1 2 3 4
| string s("some string");
for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it) *it = toupper(*it);
|
for循环中使用!=而非<进行判断:所有标准库容器的迭代器都定义了==和!=,而只有string和vector等一些标准库类型有下标运算符。
迭代器类型
vector 和 string对应的迭代器类型:iterator和const_iterator
1 2 3 4 5
| vector<int>::iterator it1; string::iterator ii2;
vector<int>::const_iterator it3; string::const_iterator it4;
|
it1, it2
是对应类型的迭代器,可以读写。对于常量对象(用const修饰的对象)需要使用
const_iterator ,不是常量对象也可使用
const_iterator ,只是const迭代器只能读不能修改元素。
begin和end
1 2 3 4 5
| vector<int> v; const vector<int> cv; auto it1 = v.begin(); auto it2 = cv.begin(); auto it3 = v.cbegin();
|
解引用和成员访问
1 2 3 4
| (*it).empty(); *it.empty(); it->empty();
|
4.2 迭代器运算
vector 和 string 迭代器支持的运算

5. 数组
数组类似于vector,但是数组的大小确定不变
5.1 数组的定义和初始化
维度必须是一个常量表达式
1 2 3 4 5 6
| unsigned cnt = 42; constexpr unsigned sz = 42; int arr[10]; int *parr[sz]; string bad[cnt]; string strs[get_size()];
|
显示初始化
1 2 3 4 5 6
| const unsigned sz = 3; int ial[sz] = {0, 1, 2}; int a2[] = {0, 1, 2}; int a3[5] = {0, 1, 2}; string a4[3] = {"hi", "byeM"}; int a5[2] = {0,1,2};
|
字符数组的特殊性
1 2 3 4
| char a1[] = {'C', 'P', 'P'}; char a2[] = {'C', 'P', 'P', '\0'}; char a3[] = "c++"; const char a4[3] = "c++";
|
不允许拷贝和赋值
1 2 3
| int a[] = {0,1,2}; int a2[] = a; a2 = a;
|
复杂的数组声明
1 2 3 4 5 6
| int arr[10]; int *ptrs[10]; int &refs[10] = ; int (*Parray)[10] = &arr; int (&arrRef)[10] = arr; int *(&arry)[10] = ptrs;
|
对于上面这些声明,使用从内至外的方法读比较合适。
使用数组初始化 vector对象
1 2
| int int_arr[] = {0, 1, 2, 3, 4, 5}; vector<int> ivec(begin(int_arr), end(int_arr));
|
5.2 指针和数组
在c++中,数组名就是指针,保存的是数组变量的首地址
1 2 3 4
| int arr[10];
int *p1 = arr; int *p2 = &arr[0];
|
数组就是指针,在把数组作为参数传入函数中,必须手动维护一个数组大小的变量
因为传入的数组是指针,无法获取到数组的长度
C++11 标准库函数
begin 和 end
这两个函数可以获取数组的头元素和尾后元素(最后一个元素的地址)
1 2 3
| int ai[] = {0, 1, 2, 3, 4, 5, 6, 7}; int *beg = begin(ia); int *last = end(ia);
|
指针的运算
指向数组元素的指针可以执行前序所述的迭代器运算。给(从)一个指针加上(减去)某整数值,结果仍是指针
。
1 2 3 4
| int arr[5] ={1,2,3,4,5}; int *p = arr; int *p2 = p + 4; int a = *(arr + 4);
|
下标和指针
1 2 3 4
| int arr[] = {1,2,3,4,5}; int *p = &ia[2]; int j = p[1]; int k = p[-2];
|
5.3 C风格的字符串
在c语言中通常是 const char* 表示字符串,并且以空字符
\0 为结尾。
1 2 3
| const char* str = "heo\0ll";
strlen(str);
|
c中的字符串是以空字符 \0
判断字符串结束,如果我们自己的定义的字符数组或是字符常量中没有
\0 或是字符中间有 \0 都不能得到正确的结果
C和C++字符串的转换
在C++中是定义了一个 string 类作为字符串类型,
string 类中重载了系列的运算符,所以从c 风格
(const char*) 到
c++风格(string)的转换都是自动完成的。以下是一些注意点
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
string s3("value"); string s3 = "value";
string s4 = s1 + ", "; string s5 = "hello" + ", "; string s6 = s1 + ", " + "world"; string s7 = "hello" + ", " + s2;
char *str = s; const char *str = s.c_str();
|
说明:在c++的string类种是重载了+,
改运算符返回的也是string类型,分析
- s4,
s1是string类型,会调用重载的 +
- s5,
hello 和 , 都是
const char* 类型,该类型并没有定义 + 运算
- s6,
(s1 + ", ") 和s4 一样,得到的是一个临时的 string,
再 + "world"
- s7,
"hello" + ", " 的错误和s5一样
6. 多维数组
初始化
1 2
| int arr[3][4] = {{0},{4},{8}}; int arr2[3][4] = {0,4,8,9};
|
使用范围 for 语句处理多维数组
1 2 3 4
| int ia[3][4]; for (auto &row : ia) for (auto col : row) cout<<col<<endl;
|
将外层循环的控制变量声明成了引用类型,是为了避免数组被自动转成指针,因为auto row : ia会将row的类型识别为int*,则内层循环不合法。
要使用范围 for
语句处理多维数组,除了最内层的循坏外,其他所有循环的控制变量都应该是引用类型。
指针和多维数组
1 2 3
| int arr[3][4]; int (*p)[4] = arr; // p指向含有4个int型的数组 p = &arr[2]; // p指向arr的尾元素
|