【C++】数组,多维数组的使用
文章目录
简介
本文为《C++ Primer》第五版关于数组,多维数组的读书笔记
!!!本文的源码均来自于《C++ Primer》第五版!!!(可能会稍作修改)
The source codes in this article are all from C++ Primer, The Fifth Edition
1. 数组
数组(Array):数组是一块内存中紧靠的空间,使用起来类似容器,但由于是比较底层的内容,所以并不像标准库的容器一样有丰富的功能
1.1. 定义和使用数组
初始化数组:初始化一个数组,值均使用类型默认值,必须指定它的容量大小,并且指定容量必须使用整型常量/常量表达式
int a1[5]; // 正确:直接使用右值创建数组
int normalInt = 5;
const int constInt = 5;
int a2[normalInt]; // 错误:使用了非常量
int a3[constInt]; // 正确:使用了常量
显性初始化数组:在初始化数组时直接定义其中的内容
int a[] = { 0, 1, 2 };
数组无法直接拷贝:数组之间无法直接拷贝,直接给数组赋值另一个数组会报错
- 编译器扩展:少数编译器扩展支持数组拷贝,但仍然建议养成不使用的好习惯,因为那样的源码放到其他编译器会报错
int a[] = { 0, 1, 2 };
int a2[] = a; // 错误:无法拷贝
1.2. 访问数组的元素
下标类型size_t:就像vector使用的size_type一样,数组通常使用size_t来表示下标
int a[] = { 1, 2, 3 };
size_t index = 2;
cout << a[index] << endl; // 输出“3”
循环访问:由于数组没有类似vector的size()来获取数组大小,很多情况下若我们不知道数组大小,循环访问可能会有下标越界(Index Out of Range)的情况,所以最安全的方法是使用范围for循环
int a[] = { 1, 2, 3 };
for (auto ele : a)
{
cout << ele << endl; // 每行输出一个元素
}
1.3. 数组和指针
数组和指针:数组在编译时时长被作为指针(指向首个元素),我们可以用这一特性,利用指针来操作数组
int a[] = { 1, 2, 3 };
// 下面两行代码本质上相同,但一般使用第二种,更简单方便
int *p1 = &a[0] // 指针p1指向数组a中的首个元素
int *p2 = a; // 指针p2指向数组a中的首个元素
- auto判断:auto会判断数组为指针
int a[] = { 1, 2, 3 };
auto b = a;
b = 3; // 错误:b为整型指针,无法被赋值一个整型
- decltype判断:decltype因为直接传入,会判断数组为数组(并且连数组容量都会一起判断)
int a[] = { 1, 2, 3 };
decltype(a) b = a; // 错误:数组不能拷贝
decltype(a) c = { 1, 2, 3, 4}; // 错误:数组元素超标,容量为3但有4个元素
decltype(a) d = { 2, 3, 4 }; // 正确:元素数量正确,初始化方式正确
指针等于迭代器:指针有着和迭代器一样的功能,也就是说对迭代器有用的数学运算,那么对指针也有用
- 模拟begin():创建一个指针指向数组中的首个元素
- 模拟end():创建一个指针指向一个不存在数组的位置(尾部元素后一个,不存在于数组中),即为“尾后”
int a[] = { 1, 2, 3 };
int *b = a; // 模拟begin
int *e = &a[3]; // 模拟end
- 标准库函数begin()和end():C++11之后引入了标准库函数,功能和标准库容器的成员函数begin()和end基本一样
int a[] = { 1, 2, 3 };
for (int* b = begin(a); b != end(a); b++)
{
cout << *b << endl; // 每行输出一个元素
}
- 指针运算:和迭代器一样,简单的加减就可以移动指针指向的位置
int a[] = { 1, 2, 3 };
int* p = a + 2; // p指向第3个元素
cout << *p << endl; // 输出“3”
p -= 1; // p指向第2个元素
cout << *p << endl; // 输出“2”
1.4. C风格字符串
C风格字符串(C-style String):C语言的字符串实际上是一个字符数组,并且结尾元素是空字符(‘\0’),以空字符结尾是被成为空字符结束(Null Terminated)的习惯,没有空字符结尾的C风格字符串,将无法直接判断字符串的大小和结尾位置,使用时可能会造成很严重的问题
常用的函数:截图来自《C++ Primer》第五版,P109
string到C风格字符串的接口:通过c_str()方法,可以获得一个指向常量字符的指针,这样我们能以读取字符数组一样读取字符串,但没有办法更改字符串中的内容
2. 多维数组
2.1. 定义和使用多维数组
多维数组:多维数组实际上在内存上仍然是一整块紧凑的内存,也就是说它并不是真正意义上的“多维”,因此我们可以初始化时可以像普通数组一样初始化(下面的例子可以帮助理解)
int a[2][3] = { {1, 2, 3}, {4, 5, 6} }; // 数组初始化后:[[1, 2, 3], [4, 5, 6]]
int b[2][3] = { 1, 2, 3, 4, 5, 6 }; // 数组初始化后:[[1, 2, 3], [4, 5, 6]]
int c[2][3] = { {1}, {4} }; // 数组初始化后:[[1, 0, 0], [4, 0, 0]]
int d[2][3] = { 1, 4 }; // 数组初始化后:[[1, 4, 0], [0, 0, 0]]
2.2. 访问多维数组的元素
多维数组的下标:和数组没什么区别,只是每个维度都有各自的下标
int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
cout << a[1][1] << endl; // 输出“5”
范围for语句处理多维数组:和正常处理数组时不同,外循环使用了引用类型,因为如果没有引用,auto会把数组视为指针
int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
for (auto &r : a)
{
for (auto c : r)
{
cout << c << endl; // 每行输出一个元素
}
}
2.3. 指针和多维数组
指针和多维数组:同样的,多维数组也和指针联系的很深,我们也可以通过指针来访问数组
int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
int *p1 = a; // 错误:不能让一个int指针指向int[3]类型
int (*p2)[3] = a; // 正确
使用指针和auto:这一块仅仅需要好好阅读下面的代码实例,并理解一开始的注释,这么多类型真的很折磨(比较难理解的地方就是为什么auto p1 = a会让p1变成指针,因为auto判断数组时会自动判断其为指针)
int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
// a: int[2][3]
// a + 2: int[3]指针
// p1: int[3]指针
// *p1: int[3]
// *p1 + 3: int指针
// p2: int指针
// *p2: int
for (auto p1 = a; p1 != a + 2; ++p1)
{
for (auto p2 = *p1; p2 != *p1 + 3; ++p2)
{
cout << *p2 << endl; // 每行输出一个元素
}
}
使用指针和auto和begin/end:功能同上方的代码,但上面的代码好理解多了,毕竟没了auto转数组为指针的部分
int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
for (auto p1 = begin(a); p1 != end(a); ++p1)
{
for (auto p2 = begin(*p1); p2 != end(*p1); ++p2)
{
cout << *p2 << endl; // 每行输出一个元素
}
}
使用类型别名简化:这个也是等同上面两个代码,也是易懂的版本
int a[2][3] = { {1, 2, 3}, {4, 5, 6} };
typedef int int_arr[3];
for (int_arr* p1 = a; p1 != a + 2; ++p1)
{
for (int* p2 = *p1; p2 != *p1 + 3; ++p2)
{
cout << *p2 << endl; // 每行输出一个元素
}
}