C 语言数组名
C语言数组名
一维数组名
在
C
语言中,几乎所有使用数组的表达式中,数组名的值就是一个指针常量,不能作为左值。它是数组第一个元素的地址,它的类型取决于数组元素的类型。
#include <stdio.h>
int main(int argc, char *argv[])
{
int array[3] = {1, 2, 3};
/**
* 数组名array是一个指针常量,它的类型是int *
*/
int *p_array = array;
printf("array[0] = %d\narray[1] = %d\narray[2] = %d\n", p_array[0], p_array[1], p_array[2]);
/**
* 一维数组
* 数组名(array): 是数组首元素的首地址,一般是一个地址常量,和&array[0]的值相同
* &数组名(&array): 是整个数组array的首地址(指向数组的指针)
* 它们的值相同,但是意义不同
*/
printf("array addr = %p\n&array addr = %p\n", array, &array);
return 0;
}
array
和 &array
的值是相同的:
在以下的两种场合下,数组名并不是使用指针常量来表示:
- 数组名作为"
sizeof
"关键字的操作数时,返回整个数组的长度,而不是指向数组的指针的长度 - 数组名作为单目运算符"
&
"的操作数时,取一个数组名的地址所产生的是一个指向数组的指针,而不是指向某个指针常量的指针。
普通二维数组
二维数组就是一维数组的每个元素存放的是一个一维数组。
通常以行和列在逻辑上来理解它,但它实际上在内存中是一维的。
格式:
类型 二维数组名[第一维大小][第二维大小]
如:
int array[3][2]; /* 可以推广到 int array[m][n] */
表示有 3 个一维数组,每个一维数组里面都存放的是由 2 个 int
类型元素组成的一维数组。
元素初始化
- 给每个元素初始化
int array[3][2] = {
{1, 3},
{2, 4},
{5, 7}
};
- 其它的初始化形式
由于二维数组的元素在内存中实际上是按一维进行连续存放的,因此初始化列表可以写成一维形式。
int array[3][2] = {1, 3, 2, 4, 5, 7};
如果提供了初始化列表,则可以省略第一维的长度,如:
int array[][2] = {
{1, 3},
{2, 4},
{5, 7}
};
注: 如果初始化列表是一维形式,则不能省略第一维的长度
如图是一个二维数组在内存中的大致描述,为帮助更好的理解二维数组(不考虑大小端):
二维数组访问元素
使用下标访问
如:
a[0][0]
, a[0][1]
可以理解为:a[0]
是把二维数组看成一个一维数组后的第一个元素,它也是一个一维数组,a[0][0]
就是这个一维数组里面的第一个元素,a[0][1]
就是这个一维数组的第二个元素。
二维数组的数组名
我们知道,一维数组的数组名,它是数组的第一个元素的首地址。
二维数组的数组名是把二维数组看成一维数组后的第一个元素的首地址。
如:
int array[3][2] = {1, 3, 2, 4, 5, 7};
看成一维数组:
array[0]
, array[1]
, array[2]
每个一维数组都存放的是一个由 2 个 int
类型组成的一维数组,因此看成一维数组后的第一个元素是:array[0]
array[0]
的类型是 int [2]
,那么它的地址的类型为 int (*) [2]
,它的地址是一个指向 int [2]
类型的一维数组的指针,因此数组名 array
实际上是一个指向一维数组的数组指针,数组名 和 &array[0]
是等价的。
/* 下面两语句效果一样 */
int (*p_array) [2] = array;
int (*p_array) [2] = &array[0];
将二维数组和每一维元素赋值给指针
/**
* array是int (*) [2]类型,不难理解应该用数组指针
*/
int (*p_array)[2] = array;
/**
* array[0] 是int [2]类型,为什么要用int * ?
*/
int *p_one_array = array[0];
/**
* &array[0][0]是对一个int的元素取地址,不难理解应该用int *类型指针
*/
int *p_element = &array[0][0];
个人理解
array[0]
看成是一个一维数组,它有 2 个元素,方便理解,把array[0]
看成一个整体,并将其替换为ary
,即:ary[2]
,不难看出它的数组名其实就是array[0]
,和一维数组的数组名有异曲同工之妙。所以ary
要用int *
,即:array[0]
赋值给int *
类型的指针变量。
常见的几种赋值
#include <stdio.h>
int main(int argc, char *argv[])
{
int array[3][2] = {1, 3, 2, 4, 5, 7};
/**
* 二维数组的数组名是把二维数组看成一维数组后的第一个元素的首地址。
* 二维数组名本质就是一个数组指针
*/
int (*p_array)[2] = array;
/**
* 对一维数组取地址,即整个一维数组的地址,指向数组的指针,指针类型为 int (*) [2]
*/
int (*p_ary)[2] = &array[0];
int *p_ary_one = array[0];
int *p_ary_element = &array[0][0];
/* 它们的值是一样的 */
printf("array addr = %p\n&array[0] addr = %p\narray[0] addr = %p\n&array[0][0] = %p\n",
array, &array[0], array[0], &array[0][0]);
return 0;
}
几种写法的值都是一样的:
二维数组作为函数形参
将一个二维数组传递给函数时,函数的形式参数也需要定义为二维数组形式。
如:
void test_fun(int array[3][2])
{
//int a = array[0][1];
}
由于编译器在处理数组形参时,会转换为指针,形参中第一维长度会被忽略,编译器不会检查实参和形参的第一维长度是否匹配,因此通常省略第一维长度。
如:
void test_fun(int array[][2])
{
//int a = array[0][1];
}
为什么不能将普通二维数组赋值给一个二级指针?
将二维数组作为实参传递给形参二级指针
void test(int **pary)
{
...
}
void caller()
{
int ary[3][2] = {1, 2, 3, 4, 5, 6};
test((int **)ary); /* 错误做法 */
}
解引用操作符:
根据指针当前的地址值,以及所指向的数据类型,访问一块连续的内存空间(大小由指针所指向的数据类型决定),将这块空间的内容转换成相应的数据类型,并返回左值。
如下代码:
将二维数组强制转换后赋值给二级指针,当对该二级指针解引用时,就得到了数组第一个元素 array[0][0]
的值 99
,如果再次解引用就会把 99
当成地址来取int
类型大小的值,这样会导致非法访问内存。
#include <stdio.h>
int main(int argc, char *argv[])
{
int array[3][2] = {99, 3, 2, 4, 5, 7};
int **p_test = (int **)array;
int (*p_ary)[2] = array;
/**
* 对二维数组解引用,得到一维数组,因此可以继续用[]进行下标访问
*/
//array <==> &array[0]
printf("*array[1] = %d <==> %d\n", *array[1], *p_ary[1]);
printf("(*array)[1] = %d <==> %d\n", (*array)[1], (*p_ary)[1]);
/**
* 此处对二级指针解引用,得到的一级指针是数组的第一个元素值(99)
* 把这个值(99)当成地址再次解引用会非法访问内存,因此不能直接将一个二维数组名强制转换后赋值给二级指针。
*/
printf("*p_test = %d\n", *p_test);
/**
* array + 1 偏移一个一维数组大小,*array + 1 偏移int类型大小
*/
printf("**array = %d *(*array + 1) = %d\n", **array, *(*array + 1));
return 0;
}
结果如图:
因此直接将普通二维数组赋值给二级指针或者作为实参传递给形参是二级指针的函数也是不正确的做法。
二维数组与二级指针的参数匹配关系
数组名被改写成指针的规则不是递归定义的,
数组的数组会被改写成“数组指针”,而不是“指针的指针”。
实参 | 所匹配的形式参数 |
---|---|
数组的数组 char ary[3][2] |
数组指针 char (*pary)[2] |
指针数组 char *pstr[3] |
指针的指针 char **ptr |
数组指针(行指针) char (*pary)[3] |
不改变 char (*pary)[3] |
指针的指针 char **ptr |
不改变 char **ptr |