7.1 数组
1. 数组声明和初始化
1.1 数组声明
数组声明的一般形式是:元素类型 数组名[元素个数]。数组对象在内存中连续存放,元素下标从 0 开始。元素个数 必须是能在编译期确定的常量表达式(变长数组除外),并且不能为负数或零。
int a[4]; // 4 个 int 对象
double table[8]; // 8 个 double 对象
char name[32]; // 32 个 char 对象2
3
如果在声明点能看到初始化器,也可以省略长度,让编译器按初始化器自动推导:
int a[] = {1, 2, 3}; // 长度为 3
char s[] = "mdr"; // 长度为 4,包含 '\0'2
在项目代码中,声明长度时建议使用具名常量或 enum 常量,而不是裸字面量,这样在需求变更时更容易维护。
1.2 数组初始化
字符串字面量初始化字符数组:可以把字符串字面量放在花括号里,但更多情况下省略。
如果数组的大小未知,那么字符串字面量里面的每个字符(包括空终止字符)用来初始化数组的各个元素;如果数组的大小已知,允许数组大小比字符串字面量的大小少 1,此时数组里无空终止字符。
cchar arr1[] = "mdr"; char arr2[] = { "mdr" }; // arr1 和 arr2 为 { 'm', 'd', 'r', '\0' } char arr3[3] = "mdr"; // arr3 为 { 'm', 'd', 'r' }1
2
3
4
5
6花括号环绕的初始化器列表初始化数组
每个初始化器前面可以带一个形如
[ 下标 ] =的指派符,指定这个初始化器初始化该数组的第 下标 个元素。如果一个初始化器无指派符,如果它是第一个初始化器则初始化下标为零的元素,否则初始化前一个初始化器初始化的元素的后一个元素。如果数组的大小未知,那么初始化器的最大下标会确定数组大小(也就是足够装得下数组里面的元素),同时在初始化过后,数组从不完整类型变成完整类型。
cint arr1[3] = { 1, 2, 3 }; int arr2[] = { [2] = 5 }; // { 0, 0, 5 } int arr3[] = { [2] = 5, 6, 7} // { 0, 0, 5, 6, 7 } int arr4[] = { 1, 2, 3, [2] = 5 }; // { 1, 2, 5 }1
2
3
4用嵌套初始化器初始化数组的数组:
cint arr[3][2] = { {1, 2}, {3, 4}, {5, 6} }; int arr[3][2] = { {1}, {3, 4}, {[1] = 6} }; // { { 1, 0 }, { 3, 4 }, { 0, 6 } }1
2如果嵌套的初始化器外围没有花括号,就会用尽可能多的初始化器依次初始化子数组的各个元素。
cint arr1[3][2] = { 1, {2}, {3, 4} }; // { { 1, 0 }, { 2, 0 }, { 3, 4 } } int arr2[3][2] = { 1, 2, 3, 4 }; // { { 1, 2 }, { 3, 4 }, { 0, 0 } }1
2指派符可以嵌套:
cint arr1[3][2] = { [2][1] = 3 }; // { { 0, 0 }, { 0, 0 }, { 0, 3 } }1
习题
输入两个整数 m 和 n(均小于 100),代表矩阵的行数和列数。接着输入两个 m x n 的整数矩阵 A 和 B。计算它们的和矩阵 C = A + B,并输出矩阵 C。
输入
设计程序,模拟“生命游戏”:
- 世界由
个方格组成,每个方格有两种状态:要么有生命,要么无生命; - 每个方格周围的 8 个方格(边 5 个,角 3 个)称为它的“邻居”;
- 在每代演化中,如果某方块有生命且其邻居中的 2 个或 3 个有生命,则该方块在下一代中保持有生命;如果该方块无生命且其邻居中的 3 个有生命,则该方块在下一代中变为有生命;其余情况则保持不变。
首先输入世界大小
给定一个正整数 n,计算并输出 n 的阶乘(n!)的值。
输入维数 n,再输入两个 n 维向量在某直角坐标系内的坐标,输出它们的夹角。
下标运算符
a[b] 定义为 *(a + b)。这条规则解释了为什么数组对象在表达式中常“退化”为首元素指针,也解释了指针加偏移后可直接使用下标语法。
int arr[4] = {10, 20, 30, 40};
int *p = arr;
int n1 = arr[2];
int n2 = p[2];2
3
4
5
二者结果一致,但越界访问仍然是未定义行为。下标语法不会自动做边界检查,工程上应在逻辑层显式约束索引范围。