- 数组名有两个含义:
- 第一含义是:整个数组
- 第二含义是:首元素地址
- 当出现以下情形时,那么数组名就代表整个数组:
- 在数组定义中 int arr [3] ; arr 表示整个数组
- 在 sizeof 运算表达式中 sizeof(arr) ; 得到的结果是整个数组的占用内存 type * 元素个数
- 在取址符&中 p = & arr ; 取得数组 arr 的地址, 因此 表示整个数组
- 其他任何情形下,那么数组名就代表首元素地址。即:此时数组名就是一个指向首元素的指针。
int arr [5] = {3,4,5,1,2}; int * p = arr ; // 当前的arr不属于以上三种情况, 因此它代表首元素的首地址 数据 3 的地址 int (*p1) [5] = &arr ; // p1 是一个指针 它指向(存放的地址)的数据类型,是 int [5]的类型 // &arr 表示整个数组的地址 int arr[5] = {1,3,5,7,9} ; int * p = arr ; // arr 是数组首元素的首地址, 因此指针 p 指向了数据1 的地址 int * p1 = arr+1 ; // arr 是数组首元素的首地址 +1 则 + 一个元素的类型 // 因此 arr+1 则+了一个整形大小,也就是 数据 3 的地址 printf("p:%d p1:%d\n" , *p , *p1 ); int * p3 = &arr + 1 ; // &arr 表示整个数组 arr 的地址 // 整个数组的地址 + 1 则 + 一个数组的大小
示例:
int a[3]; // 此处,a 代表整个数组 printf("%d\n", sizeof(a)); // 此处,a 代表整个数组 printf("%p\n", &a); // 此处,a 代表整个数组,此处为整个数组的地址 int *p = a; // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0] p = a + 1; // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0] function(a); // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0] scanf("%d\n", a); // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]
C语言只有在第一含义的场合下表现为数组,其他大部分场合都表现为首元素的地址,当数组表现为首元素地址时,实际上就是一个指向其首元素的指针。数组运算实际上就是指针运算。
数组下标:
- 数组下标实际上是编译系统的一种简写,其等价形式是:
a[i] = 100; 等价于 *(a+i) = 100;
根据加法交换律,以下的所有的语句均是等价的:
-
a[i] = 100; *(a+i) = 100; *(i+a) = 100; i[a] = 100; // 3[arr] = 123 ; // arr[3] = 123 ; // 以下四个语句都能正确访问数据 printf("arr[2]:%d\n" , arr[2]) ; printf("*(arr+2):%d\n" , *(arr+2)) ; printf("*(2+arr):%d\n" , *(2+arr)) ; printf("2[arr]:%d\n" , 2[arr]) ;
- 数组运算,等价于指针运算。
字符串常量
- 字符串常量在内存中的存储(存储在常量区-该区域不允许被修改(只读ReadOnly)),实质是一个匿名数组
- 匿名数组,同样满足数组两种涵义的规定
- 示例:
printf("%d\n", sizeof("abcd")); // 此处 "abcd" 代表整个数组 printf("%p\n", &"abcd"); // 此处 "abcd" 代表整个数组 printf("%c\n", "abcd"[1]); // 此处 "abcd" 代表匿名数组的首元素地址 char *p1 = "abcd"; // 此处 "abcd" 代表匿名数组的首元素地址 char *p2 = "abcd" + 1; // 此处 "abcd" 代表匿名数组的首元素地址
字符串常量是以字符串数组的形式存储在数据段(常量区)。
零长数组
- 概念:长度为0的数组,比如 int data[0];
- 用途:放在结构体的末尾,作为可变长度数据的入口
- 示例:
struct node { /* 结构体的其他成员 */ // 成员1 // 成员2 // ... ... int len; char *data[0]; }; // 给结构体额外分配 10 个字节的内存。 struct node *p = malloc(sizeof(struct node) + 10); p->len = 10; // 额外分配的内存可以通过 data 来使用 p->data[0] ~ p->data[9]
变长数组
- 概念:定义时,使用变量作为元素个数的数组(使用变量作为数组定义时的大小)。
- 要点:变长数组仅仅指元素个数在定义时是变量,而绝非指数组的长度可长可短。实际上,不管是普通数组还是所谓的变长数组,数组一旦定义完毕,其长度则不可改变。
- 示例:
int arr[60]; int len = 5; // 可能通过某些运算,或用户的选择得到了数组的大小 int a[len]; // 数组元素个数 len 是变量,因此数组 a 是变长数组 len = 30 ; // 再次修改变量的值并不会影响数组的大小 int x = 2; int y = 3; int b[x][y]; // 数组元素个数 x、y 是变量,因此数组 b 是变长数组 int b[2][y]; // 数组元素个数 y 是变量,因此数组 b 是变长数组 int b[x][3]; // 数组元素个数 x 是变量,因此数组 b 是变长数组
- 语法:变长数组不可初始化,即以下代码是错误的:
int len = 5; int a[len] = {1,2,3,4,5}; // 数组 a 不可初始化 , 应在再定义数组之前该数组的大小都是未知的,因此无法进行初始化 // 编译器报错: variable-sized object may not be initialized
总结:
数组在定义之处就已经确定了大小,在未定义的时候可以通过计算得到恰当的大小,当数组被定义出来后他的大小就无法再被改变。
指针数组:
概念:一个用来存放指针的数组。
语法:int * arr[5] ; int * (arr[5]) ;
如何初始化以及赋值:
int a = 123 ;
int b = 456 ;
int c = 789 ;
// 定义并初始化
int * arr[5] = { &a ,&b , &c } ;
int d = 111 ;
int k = 555 ;
// 给数组赋值
arr[3] = &b ;
arr[4] = &k ;
如何通过指针数组来访问各个指针的内容:
int a = 123 ;
int b = 456 ;
int c = 789 ;
// 定义并初始化
int * arr[5] = { &a ,&b , &c } ;
int d = 111 ;
int k = 555 ;
arr[3] = &b ;
arr[4] = &k ;
printf( "*(arr[0]):%d\n" , *(arr[0]) ); // arr[0] --> *(arr+0)
printf( "*(arr+1):%d\n" , **(arr+1) ); // arr+1 --> **(&b 的地址) -- > *(&b) --> b
printf( "*(2[arr]):%d\n" , *(2[arr]) );
printf( "a--> **arr:%d\n" , **arr );
二维数组的加减法问题
int arr[3][5] = { {1,2,3,4,5} , {11,22,33,44,55} , {111,222,333,444,555} };
printf("&arr:%p\n" , &arr);
// arr 是一个数组的名字 arr[3] --> 因此该数组名arr 表示的是首元素的地址
// 首元素 {1,2,3,4,5} (int [5])
// arr + 1 加的是一个元素的类型的大小
int * p = arr + 1 ; // 因此p 应该距离入口地址 5个元素*int大小 = 20 个字节
printf("p:%p\n" , p );
// & arr 表示整个数组的地址,因此他的类型 int [3][5]
int * p2 = &arr + 1 ; // 因此 p2 距离数组的入口地址相差 60 字节
printf("p2:%p\n" , p2 );
// arr 表示首元素的地址 {1,2,3,4,5}
// *arr 的到 {1,2,3,4,5} 的首元素的首地址
// {1,2,3,4,5} 的首元素 就是 数据1 ,因此他的地址就 int 类型的
// *arr + 1 则加一个 int 类型的大小 4字节
int *p3 = *arr + 1 ; // p3距离 数组的入口地址相差 4 字节
printf("p3:%p\n" , p3 );
假设有int arr [100][450]数组, 请写出访问该数组中第 1430 个元素的语句:
arr[ m ] [ n ] :
m = 1430 / 450
n = 1430 % 450 - 1
arr[3][4] - 写出 第 8 个元素:
arr [ 1 ] [ 3 ]