第三章 字符串、向量和数组
3.1 using声明
- 可以对单个名字进行独立的using声明,比如
using std::cin
- 头文件中不应该包含
using
声明。
3.2 string
- 拷贝初始化(copy initialization):
=
- 直接初始化(direct initialization):
()
string 的操作 - 字符串字面值和string是不同的类型(为了与C兼容) cctype头文件中定义了一组标准库函数来处理string的字符
3.3 vector
- 列表初始化:
vector<string> v{"a", "an", "the"};
(C++11) - 拷贝初始化:
=
- 直接初始化:
()
- 数组初始化:
vector<int> v(arr.begin(), arr.end());
vector 支持的操作 - 范围
for
语句内不应该改变其遍历序列的大小。 vector
对象(以及string
对象)的下标运算符,只能对确知已存在的元素执行下标操作,不能用于添加元素。
3.4 迭代器iterator
使用迭代器
- 养成使用迭代器和
!=
的习惯(泛型编程),循环判断中少使用<
(因为所有标准库迭代器都定义了==
和!=
,但是只有很少一部分定义了<
)。 - 但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。
3.5 数组
定义和初始化内置数组
- 初始化:列表初始化
- 字符数组可以用字符串字面值进行初始化,结尾
\0
也拷贝到字符数组中
- 字符数组可以用字符串字面值进行初始化,结尾
- 数组不允许直接拷贝或赋值给另一个数组。
- 想复制可以使用
memcpy(void *dst, void *src, size_t size)
- 想复制可以使用
- 复杂的数组声明,指向数组的指针,绑定到数组的引用各类指针的详细介绍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; // arr是int[10]类型 int (*pa)[10] = &arr; // pa是指向arr(int[10]类型)的指针,右边当然要取地址(虽然arr==&arr) for(int i = 0; i < 10; ++i) cout<<*( (*pa) +i)<<" "; // *pa是数组指针解引用,即得到arr(即为数组名);剩下部分即为通常的指针访问数组 // cout<< (*pa)[i] << " "; // 或者这样访问元素 cout<<endl; int (&ra)[10] = arr; // ra是绑定到arr(int[10]类型)的引用,右边即为对象 for(int i = 0; i < 10; ++i) cout<<*(ra + i) << " "; // cout<< ra[i] <<" "; cout<<endl; int m[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} }; int (*p)[5] = m; // 左边是指向int[5]类型的指针,右边是&m[0](即为m) for(int i = 0; i < 3; ++i) for(int j = 0; j < 5; ++j) cout<< (*(p + i)) [j] << " "; // p+i表示指向哪一个int[5]类型,解引用即为者5个int数组的数组名 // 等价写法:p[i][j], *(p[i] + j), *(*(p+i) + j) int (*pp[10])[5]; // 首先pp与[10]结合,表明这是一个数组 // 剩下的部分就是数组元素的类型:int (*)[5] // 即每个数组元素是一个指针,指向int[5]类型
数组与指针
- 很多情况下,编译器会将数组名自动替换为指向数组首元素的指针(相当于顶层const指针)
- 数组的类型推断
- 当auto变量基于数组名进行类型推断时,得到的是对应类型的指针
- 当使用decltype进行类型推断时,返回数组类型
1 2 3 4
int arr[] = {1,2,3}; auto ptr(arr); //ptr是int*类型 decltype(arr) arr2 = {4,5}; // 相当于arr2的类型是int[3] for(auto i: arr2) cout<<i<<" "; // 输出是4 5 0而非4 5 3
- 数组名不是指针
- 证据一:sizeof(数组名) = 整个数组所占内存大小,如果数组名是指针,则为4或8
- 证据二:对数组名取地址得到的是整个数组的地址,如果数组名是指针,则对数组名取地址是另外一个地址(指针的地址)
arr
(数组首个元素的地址)与&arr
(数组地址)在值上是相等的,但是意义不同
- 函数传参和接受参数时,传递的都是指针,即使形参是
int arr[]
这样表示整个数组(可能情形是,外面传入的实参是数组名,但是形参实例化时自动转换为指针)- 否则需要进行数组的拷贝,效率低
int arr[x]
中即使带数组容量,也会被忽略,x随便取都无所谓
- 参考
C风格字符串
- C风格字符串:以空字符
\0
结束的char数组 - 相互使用:
- 可以直接将char字符数组赋值给string字符串
- 字符串到字符数组:
const char *arr = str.c_str();
str, arr
使用的是同一块数据
- C 风格字符串的函数:
strlen, strcmp, strcat, strcpy
- 传入参数必须是字符数组,注意列表初始化字符数组时必须最后带
\0
- 传入参数必须是字符数组,注意列表初始化字符数组时必须最后带
- 字符串与字符数组:
- 字符串:使用字符指针指向只读数据段的一片区域
- 字符数组:放在栈中或者数据段
- 参考
3.6多维数组
- 多维数组阅读顺序由内向外
- 多维数组的初始化
1 2 3 4 5
int ia[2][4] = { {0, 1, 2, 3}, {4, 5, 6, 7} }; int ib[2][4] = {0, 1, 2, 3, 4, 5, 6, 7}; // 初始化部分元素 int ic[2][4] = {{ 0 }, { 4 }}; // 初始化第一列 int id[2][4] = {0, 3, 6, 9}; // 初始化第一行
- 使用range-for语句时,除了最内层的循环外,外层循环的控制变量都应该是引用类型。
- 否则编译器自动将数组名转换为指针,无法使用range-for语法
- 遍历:
int ia[2][4] = { {0, 1, 2, 3}, {4, 5, 6, 7} };
- range-for:
1 2 3 4 5 6 7 8 9
// row为int[4]类型,如果row不是引用类型(不加&),编译器会自动将row从int[4]类型转变为指针,无法遍历指针 for(auto &row: ia) for(auto item: row) cout<<item<<" "; // 不使用auto,写出变量类型: for(int (&row)[4]: ia) // 理解山相当于:int[4] &row for(int item: row) cout<<item<<" ";
- 使用i,j下标进行访问
- 使用指针访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
for(auto p = ia; p != ia+2; ++p) for(auto q = *p; q!= *p+4; ++q) cout<<*q<<" "; // 不使用auto: for(int (*p)[4] = ia; p != ia+2; ++p) for(int *q = *p; q != *p+4; ++q) cout<<*q<<" "; // 使用类型别名 using arr = int[4]; // typedef int arr[4]; // 看起来比较奇怪 for(arr *p = ia; p != ia+2; ++p) for(int *q = *p; q != *p+4; ++q) cout<<*q<<" ";
- range-for:
其他
const、指针和引用、多维数组
|
|
- 也进行顶层const和底层const的区分
动态数组
- 使用
new
和delete
表达和c中malloc
和free
类似的功能,即在堆中分配存储空间。 - 定义:
int *pia = new int[n];
- 释放:
delete [] pia;
,注意不要忘记[]
。