数组
需要预先了解:
1. 数组
数组(Array):同一类型数据的连续内存集合。
数组中的所有元素的类型都相同,并且长度固定,C++中可以用[]运算符来定义数组:
// 类型 数组名[数组长度]
int arr[5]; // 长度为5的 int 类型的整数数组
char str[100]; // 长度为100的 char 类型变量的字符数组(字符串)
1.1 通过[]运算符来访问数组元素
数组的下标索引从0开始:
int main()
{
int arr[5];
arr[0] = 10;
arr[4] = 50;
arr[5] = 100; // 错误,arr的下标范围是0~4,没有5
}
1.2 常量数组
可以在定义数组的时候用const关键字来修饰数组元素,使得数组的每一个元素的值都不能被修改。
#include <iostream>
int main()
{
const int arr[5] = {1, 2, 3, 4, 5}; // 定义一个 const 数组
const char str[] = "Hello"; // 定义一个 const 字符数组(字符串)
arr[0] = 10; // 错误,不能修改 const 数组的元素
return 0;
}
1.3 数组长度与变长数组扩展(VLA)
标准 C++ 要求定义数组时,数组的长度必须是一个编译时就可以确定的常量表达式,不能是一个变量或者运行时才能确定的值。
#include <iostream>
int main()
{
const int N = 5; // N 是编译时就能确定的 const 常量
constexpr int M = 10; // M 是编译时就能确定的 constexpr 常量
int x; std::cin >> x; // x 是一个变量,运行时才能确定其值
const int y = x; // y 是运行时才能确定的 const 常量
int arr[5]; // 正确,5 是字面量
int arr1[N]; // 正确,N 是编译时常量
int arr2[M]; // 正确,M 是编译时常量
int arr3[x]; // 错误,x 是变量
int arr4[y]; // 错误,y 是运行时常量
return 0;
}
GCC 和 Clang 等编译器支持一种称为变长数组(Variable Length Array,VLA)的扩展特性,允许在定义数组时使用一个运行时才能确定的变量作为数组长度,因此直接编译运行上面的代码也是可以的。
我们在编译指令中加上 -pedantic-errors 选项来启用严格的标准兼容性检查,再进行编译,就会得到报错:
code.cpp: In function 'int main()':
code.cpp:14:9: error: ISO C++ forbids variable length array 'arr3' [-Wvla]
14 | int arr3[x]; // 错误,x 是变量
| ^~~~
code1.cpp:15:9: error: ISO C++ forbids variable length array 'arr4' [-Wvla]
15 | int arr4[y]; // 错误,y 是运行时常量
| ^~~~
1.4 数组的初始化
1. 完全显式初始化
用 {} 来依次排列出要初始化的元素值,中间用逗号分隔。
int arr[5] = {1, 2, 3, 4, 5}; // 定义并初始化一个长度为 5 的整数数组
2. 部分初始化
{} 中列出的元素值的个数小于数组长度,未列出的元素会被自动初始化为 0。
int arr1[5] = {1, 2};
// 将 arr[0] 初始化为 1
// 将 arr[1] 初始化为 2
// arr[2]、arr[3]、arr[4] 被初始化为 0
int arr2[5] = {}; // 空大括号会自动将 arr2 中的所有元素初始化为 0
3. 不指定数组长度,由编译器进行推导
在定义数组时,[] 中不指定数组长度,编译器会根据 {} 中列出的元素值的个数来推导数组的长度。
int arr[] = {1, 2, 3, 4, 5}; // 定义并初始化一个整数数组,长度由编译器推导为 5
1.5 二维数组与多维数组
二维数组
定义二维数组时,需要指定两个维度的大小,即类型 数组名[行数][列数];,例如:
int matrix[3][4];
初始化时,可以有以下方式:
int matrix1[3][4] = { // 按行初始化,每行元素用花括号括起来
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int matrix2[3][4] = { // 连续初始化,编译器会自动进行填充
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
};
int matrix3[3][4] = { // 部分初始化,未初始化的元素被赋予默认类型
{1, 2}, {5}
};
访问二维数组的元素时,也需要行索引和列索引,二者均从0开始:
int matrix[3][4] = {1, 2, 3, 4};
int x = matrix[1][2];
2. 数组的内存布局
数组在内存中是连续存储的,每个元素占用相同大小的内存空间。
比如对于 int arr[5],数组的每个元素都占用四个字节的空间,我们用其中第一个字节的地址来表示整个元素的地址。假设 arr[0] 的地址是 0x12345678ABCD1234,那么数组的内存布局如下:
arr[0] --- 0x12345678ABCD1234
arr[1] --- 0x12345678ABCD1238
arr[2] --- 0x12345678ABCD123C
arr[3] --- 0x12345678ABCD1240
arr[4] --- 0x12345678ABCD1244
2.1 多维数组的内存布局
行优先规则
行优先规则(Row Major):存放多维元素时,最靠右的下标最先连续变化;靠左的下标变化更慢。
对于二维数组arr[i][j],内存地址从小到大的填充顺序:
- 先固定行
i=0,把所有 j 依次放完 - 再 i=1,放所有 j
- 再 i=2,放所有 j
二维数组的内存布局
核心特征为:所有元素连续存储在一块无空隙的内存。
给定二维数组int arr[3][4],假设int类型变量占4个字节,&arr[0][0] == 0x1000:
| 元素 | 线性偏移元素序号 | 内存地址 |
|---|---|---|
| arr[0][0] | 0 | 0x1000 |
| arr[0][1] | 1 | 0x1004 |
| arr[0][2] | 2 | 0x1008 |
| arr[0][3] | 3 | 0x100C |
| arr[1][0] | 4 | 0x1010 |
| arr[1][1] | 5 | 0x1014 |
| arr[1][2] | 6 | 0x1018 |
| arr[1][3] | 7 | 0x101C |
| arr[2][0] | 8 | 0x1020 |
| arr[2][1] | 9 | 0x1024 |
| arr[2][2] | 10 | 0x1028 |
| arr[2][3] | 11 | 0x102C |
不难得到:
&arr[i][j] = &arr[0][0] + (i * 列数 + j) * sizeof(int);
更高维数组的内存布局
类似地,三维数组arr[i][j][k]的内存地址计算公式为:
&arr[i][j][k] = &arr[0][0][0] + (i * 行数 * 列数 + j * 列数 + k) * sizeof(int);
N维数组arr[i1][i2][i3]...[iN]的内存地址计算公式为:
&arr[i1][i2][i3]...[iN] = &arr[0][0][0]...[0] + (i1 * D2 * D3 *... * DN + i2 * D3 * D4 * ... * DN + ... + iN-1 * DN + iN) * sizeof(元素类型);


