09-数组的含义以及零长数组变长数组与多维数组
文章目录
- 09-数组的含义以及零长数组变长数组与多维数组
- 一、数组名的含义
- 1.1 表示整个数组的首地址
- 1.2 表示整个数组首元素的首地址
- 二、数组下标
- 字符串常量
- 三、零长数组
- 3.1 示例
- 四、变长数组
- 4.1 示例
- 五、多维数组
- 5.1 定义与初始化
- 5.2 引用元素
- 5.3 实例讲解
- 5.4 总结
一、数组名的含义
1.1 表示整个数组的首地址
在某些特定情况下,数组名表示整个数组的首地址
:
- 数组定义的时候
- 在使用
sizeof
运算符中:使用 sizeof 运算符求的是整个数组的大小 - 在取地址符中
&arr
#include <stdio.h>
int main() {
int arr[10];
// 在数组定义的时候
printf("数组定义时的地址: %p\n", (void*)arr);
// 使用 sizeof 运算符
int len = sizeof(arr);
printf("数组的大小: %d\n", len);
// 取地址符中
int (*p)[10] = &arr;
printf("取地址符中: %p\n", (void*)p);
return 0;
}
1.2 表示整个数组首元素的首地址
在其他情况下,数组名表示数组的首元素的首地址
:
#include <stdio.h>
int main() {
int arr[10];
// 表示数组的首元素的首地址
int *p1 = arr;
printf("数组首元素的地址: %p\n", (void*)p1);
return 0;
}
二、数组下标
数组下标只是编译器提供的一种简写
,实际上如下:
#include <stdio.h>
int main() {
int a[100];
a[10] = 250; // ==> 等价于 *(a + 10) = 250
*(a + 10) = 250;
*(10 + a) = 250;
10[a] = 250;
printf("a[10] = %d\n", a[10]);
return 0;
}
字符串常量
字符串常量是一个被存放在常量区
的字符串,实际上也可以称为一个匿名数组
。匿名数组,同样满足数组名的含义。
#include <stdio.h>
int main() {
char *msg2 = "Hello Even"; // 输出:"Hello Even" 字符串常量首元素的首地址
char *msg1 = "Hello Even" + 1; // 输出:"ello Even" 字符串常量首元素的首地址加 1
printf("%s\n", "Hello Even"); // "Hello Even" 字符串常量首元素的首地址
printf("%s\n", &"Hello Even"); // "Hello Even" 字符串常量的整个数组的地址
// 访问字符串中的某个字符
printf("%c\n", "Hello Even"[6]); // 输出: 'E'
return 0;
}
注意事项:
- 数组名的双重含义:
- 表示整个数组的首地址时:在数组定义、
sizeof
运算符、取地址符&arr
中。 - 表示数组首元素的首地址时:在大多数其他情况下。
- 表示整个数组的首地址时:在数组定义、
- 数组下标运算:
a[i]
等价于*(a + i)
,甚至i[a]
也是合法的。 - 字符串常量:可以被视为匿名数组,数组名(即字符串字面值)表示其首元素的首地址。
三、零长数组
零长数组(zero-length array)是数组长度为0
的数组,通常用于结构体的最后一个成员
,作为可变长度数据的入口。
用途:用于结构体中的可变长度数据。尽管C99标准已经引入了柔性数组成员(flexible array member),零长数组仍在一些遗留代码中使用。
3.1 示例
#include <stdio.h>
#include <stdlib.h>
struct node {
int a;
char b;
float c;
int len;
char arr[0]; // 零长数组
};
int main() {
struct node *p = malloc(sizeof(struct node) + 20); // 分配足够的内存-->+ 20 就是在原有的基础上增加20字节
p->len = 20; // 设置额外增长的长度为20
// 使用零长数组
for (int i = 0; i < p->len; i++) {
p->arr[i] = 'A' + i;
}
for (int i = 0; i < p->len; i++) {
printf("%c ", p->arr[i]);
}
printf("\n");
free(p);
return 0;
}
四、变长数组
概念:变长数组(variable-length array,VLA)是其长度在定义时由一个变量决定的数组。定义之后,其长度不能再改变。
重点: 变长数组并不是说在任意时候他的长度可以随意变化, 实际上只是在定义之前
数组的长度是未知的有一个变量来决定, 但是定义语句过后变长数组的长度由定义那一刻
变量的大小来决定。
4.1 示例
#include <stdio.h>
int main() {
int a = 200;// // a 作为一个普通的变量 , 200 则可以作为arr 的长度
a = 99; // 99 可以作为 arr 的长度
int arr[a]; // a 当前是99,因此数组长度为99
//从此以后该数组的长度已经确定为99 不会再变换
for (int i = 0; i < a; i++) {
arr[i] = i;
}
for (int i = 0; i < a; i++) {
printf("%d ", arr[i]);
}
printf("\n");
a = 10 ; // a = 10 并不会影响数组的长度
return 0;
}
注意:
- 因为数组的长度未确定, 因此它不允许初始化。
- 在使用的时候可以通过该变长数组来有限的节省内存空间。
五、多维数组
概念:多维数组是指数组的元素也是数组,例如二维数组、三维数组等。
示例:
int a[2][3];
这个二维数组 a
包含了 2 个一维数组,每个一维数组有 3 个元素。
5.1 定义与初始化
-
定义和初始化带有明确的嵌套大括号:
int arr[2][3] = { {1, 2, 3}, {4, 5, 6} };
上述语句定义了一个包含 2 行 3 列的二维数组,并初始化其值为:
arr = { {1, 2, 3}, {4, 5, 6} }
-
省略嵌套大括号的初始化:
int arr1[2][3] = { 1, 2, 3, 4, 5, 6 };
上述语句的效果等同于:
arr1 = { {1, 2, 3}, {4, 5, 6} }
5.2 引用元素
-
通过下标引用:
arr[0][0] = 100;
-
通过指针偏移引用:
*(*(arr + 0) + 0) = 100;
5.3 实例讲解
#include <stdio.h>
int main() {
int arr[2][3] = { {1, 2, 3}, {4, 5, 6} };
int arr1[2][3] = { 1, 2, 3, 4, 5, 6 };
// 遍历并打印二维数组的元素
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("arr[%d][%d]: %d\t", i, j, arr[i][j]);
}
printf("\n");
}
printf("\n");
// 使用指针偏移访问数组元素
for (int i = 0; i < 6; i++) {
printf("*(*(arr+0) + %d): %d\t", i, *(*(arr+0) + i));
}
printf("\n");
// 使用指针偏移访问数组元素的另一种方式
for (int i = 0; i < 6; i++) {
printf("*(*(arr + %d)): %d\t", i, *(*(arr + i / 3) + i % 3));
}
printf("\n");
// 将二维数组的首元素地址赋给指针 p
int *p = &arr[0][0];
for (int i = 0; i < 6; i++) {
printf("*(p + %d): %d\n", i, *(p + i));
}
return 0;
}
分析:
-
直接访问二维数组的元素:
for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++) { printf("arr[%d][%d]: %d\t", i, j, arr[i][j]); } printf("\n"); }
这个循环直接通过
arr[i][j]
访问和打印二维数组的元素。 -
使用指针偏移访问元素:
for (int i = 0; i < 6; i++) { printf("*(*(arr + 0) + %d): %d\t", i, *(*(arr + 0) + i)); }
这个循环通过指针偏移来访问二维数组的元素。
*(arr + 0)
获取二维数组的第一行,*(*(arr + 0) + i)
获取第一行第i
个元素。 -
另一种指针偏移的访问方式:
for (int i = 0; i < 6; i++) { printf("*(*(arr + %d)): %d\t", i, *(*(arr + i / 3) + i % 3)); }
这个循环同样通过指针偏移来访问元素,但它通过
i / 3
计算行号,通过i % 3
计算列号。 -
将二维数组的首元素地址赋给指针
p
并访问:int *p = &arr[0][0]; for (int i = 0; i < 6; i++) { printf("*(p + %d): %d\n", i, *(p + i)); }
p
指向二维数组的首元素(第一个元素arr[0][0]
),通过*(p + i)
访问所有元素。这种方法将二维数组视为一个一维数组。
5.4 总结
-
多维数组:本质上是数组的数组。
-
定义与初始化:可以使用嵌套大括号或直接平铺的方式。
-
引用元素:可以使用下标或指针偏移。
-
指针与多维数组:可以将多维数组的地址赋给指针,通过指针进行遍历和访问。
总结: -
零长数组:用于结构体末尾作为可变长度数据入口,虽然C99标准引入了柔性数组成员,但零长数组仍在遗留代码中使用。
-
变长数组:在定义时长度由变量决定,定义后长度不再改变。注意:变长数组不能初始化。
-
多维数组:数组元素也是数组,可以通过下标和指针偏移访问。