一 概述
二 类型
三 语句
四 函数
五 数据
六 内存
七 代码
附录
文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
4. 数组
数组包含多个类型相同且连续存储的元素,其类型由元素类型和元素数量共同构成。
type name[ number_of_elements ];
- 元素数量必须大于 0 的整数。(GNU 允许 0 长度数组)
- 元素类型不能是函数类型或不完整类型,可改用指针。
typedef void (fn_t) (); fn_t x[3]; ^ error: declaration of 'x' as array of functions
可通过元素数量和类型长度计算数组长度,反之亦然。
int x[10]; assert(sizeof(x) == sizeof(x[0]) * 10); assert(sizeof(x) / sizeof(x[0]) == 10);
定长数组 (fixed-length)元素数量为编译期可确定常量(或通过初始化推断),可定义为全局变量; 变长数组 (variable-length, VLA)元素数量是运行期提供,只能用于函数内,且不能有静态声明。
static int X[] = {1, 2, 3}; // 相当于 X[3]。 static int Y[3]; size_t test (int n) { int z[n]; return sizeof(z); } int main (void) { assert(sizeof(X) / sizeof(X[0]) == 3); assert(sizeof(Y) / sizeof(Y[0]) == 3); assert(test(10) == sizeof(int) * 10); return 0; }
初始化
全局数组变量会自动初始化,而局部数组变量元素值未定,需显式初始化。
- 不能初始化变长数组。(长度未定,无法确定元素数量)
- 可省略数组长度,通过初始化列表推断。
- 部分初始化时,其余元素为 0 值。
- 初始化值数量不能超过限制。(GNU 警告并忽略多余值)
- 不能以空表达式初始化。(GNU 允许)
int main (void) { // int[3]: 多余逗号被忽略。 int a[] = {1, 2, 3, }; assert(sizeof(a) / sizeof(a[0]) == 3); // int[8]: 以索引指定。 int b[] = {1, [6] = 100, 101}; assert(sizeof(b) / sizeof(b[0]) == 8); assert(b[7] == 101); // 其余元素值为 0。 int c[4] = { 1, 2 }; assert(c[2] == 0); // 全部为 0。 int d[4] = { 0 }; assert(d[1] == 0); return 0; }
int main (void) { typedef struct { int x; int y; } data_t; data_t x[] = { { .x = 1, .y = 1 }, { .x = 1, .y = 1 }, { .x = 1, .y = 1 }, }; return 0; }
size_t test (int n) { int z[n] = {1, 2, 3}; ^~~ error: variable-sized object may not be initialized
多维数组
多维数组的元素也是数组,比如二维数据构成一个行列组成的表(矩阵)。
只有第一维长度可以省略。因为
int[][]
的元素类型是不完整类型int[]
,编译器报错。
int main (void) { int x[3][3] = { {0} }; assert(x[2][2] == 0); int y[][3] = { // row 0 : { 1, 2, 3} {1, 2, 3}, // row 1 : { 4, 5, 0} {4, 5}, // row 2 : { 0, 0, 0} [3] = {7, 8, 9}, // row 3 : { 7, 8, 9} [4][2] = 12, // row 4 : { 0, 0, 12} {13, 14, 15}, // row 5 : {13, 14, 15} }; assert(sizeof(y) / sizeof(int[3]) == 6); assert(sizeof(y) / sizeof(y[0][0]) == 18); assert(y[4][0] == 0); return 0; }
多维数组依旧连续存储,将其展开,可看成是一维数组。
int main (void) { int x[][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9}, }; // 计算数据总量,指针。 int n = sizeof(x) / sizeof(x[0][0]); int *p = (int *)x; for (int i = 0; i < n; i++) { printf("%d, ", *p++); } printf("\n"); return 0; }
数组指针
数组名可看成隐式指向数组第一元素的指针常量(指针本身无法修改)。
|<------------ &x, ap ------------->| | | +===========+===========+===========+ int x[3]; // 数组 | 1 | 2 | 3 | p = x; // 元素指针 +===========+===========+===========+ ap = &x; // 数组指针 | | |<--- x --->| x[1] == *(p + 1) == p[1] |<- &x[0] ->| *ap == x |<--- p --->| (*ap)[1] == x[1]
int main (void) { int x[] = {1, 2, 3}; assert(x == &x[0]); // 元素指针(单个元素)。 int *p = x; assert(p[0] == x[0]); p++; assert(*p == x[1]); // 数组指针(整个数组)。 int (*ap)[] = &x; assert((*ap)[1] == x[1]); return 0; }
int main (void) { int x[] = {1, 2, 3}; void *p = &x; // 类型转换。 int (*ap)[]; ap = (int(*)[])(p); assert((*ap)[1] == x[1]); return 0; }
如果是二维数组,那么数组名就是一个 数组指针 。
|<--- y[0] ---->|<--- y[1] ---->| | | | +=======+=======+=======+=======+ | 1 | 2 | 3 | 4 | int y[2][2]; // 数组 +=======+=======+=======+=======+ p = y; // 元素指针,元素也是数组。 | | |<---- y ------>| p[1][1] == y[1][1] |<--- &y[0] --->| *p == y[0] |<---- p ------>| (*p)[1] == y[0][1]
int main (void) { int y[][2] = { {1, 2}, {3, 4}, }; // 元素本身就是数组。 // 必须是 int[2],因为 int[] 不确定长度,无法指针运算。 int (*p)[2] = y; assert(p[0][1] == y[0][1]); assert((*p)[1] == y[0][1]); // 下一元素,也就是第二行数组。 p++; assert((*p)[1] == y[1][1]); return 0; }
指针数组
还有一种元素为指针类型的数组,被称作 指针数组 。
array pointer vs. pointer array ============= ============= 数组指针:int (*ap)[] // 指向整个数组的指针。 指针数组:int* pa[] 或 int *pa[] // 元素为指针的数组。 ~~~~~~~~ 这个写法更易理解。
int main (void) { int a = 1; int b = 2; int c = 3; int* x[] = { &a, &b, &c }; assert(*(x[1]) == b); return 0; }
既然元素是指针,而数组名又是指向第一元素的指针,那么指针数组名也可以看作指针的指针。
char* x[] : 最易理解。 char* *p : 指针的指针,二级指针未必是数组。 char **p : 同上。
int main (void) { char* x[] = { "aa", "bb", "cc" }; char* *p = x; int n = sizeof(x) / sizeof(x[0]); for (int i = 0; i < n; i++) { assert(*p == x[i]); p++; } return 0; }
数组参数
虽然数组作为参数传递时,总是隐式当作指针,但还是要尽可能声明为数组类型。
相比之下,
sum (int x[])
和sum (int *x)
表达的意思未必相同。
int sum (int x[5]) { int n = 0; for (int i = 0; i < 5; i++) { n += x[i]; } return n; } int main (void) { int x[] = {1, 2, 3, 4, 5}; assert(sum(x) == 15); return 0; }
参数可以是变长数组,需额外传递长度。
// 和 int sum (int num, int x[]) 效果相同。 // 但 x[num] 更易理解,尤其是有更多参数的时候。 int sum (int num, int x[num]) { int n = 0; for (int i = 0; i < num; i++) { n += x[i]; } return n; }
// 同样可写成 int sum (int nrow, int ncol, int x[][ncol]) // 参数 int x[][ncol] 里的 ncol 不能省略,因为不能是不完整类型。 // 还是建议写完整。 int sum (int nrow, int ncol, int x[nrow][ncol]) { int n = 0; for (int r = 0; r < nrow; r++) { for (int c = 0; c < ncol; c++) { n += x[r][c]; } } return n; }
除了传递长度参数外,也可以特定标志结束,比如 NULL 等。
void test (char* ss[]) { while (ss && *ss) { puts(*ss++); } } int main (void) { char* ss[] = { "aa", "bb", "cc", NULL }; test(ss); return 0; }
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论