先从存储的角度对二维数组作一个全面的了解。二维数组在内存中的存储,是按照先行后列依次存放的。从内存的角度看,可以这样说,二维数组其实就是一个一维数组,在内存中没有二维的概念。如果把二维数组的每一行看成一个整体,即看成一个数组中的一个元素,那么整个二维数组就是一个一维数组,它以每一行作为它的元素,这个应该很好理解。
第一,来详细介绍二维数组与指针的关系。- 首先定义个二维数组 array[3][4],p 为指向数组的指针。 若p=array[0],此时p指向的是二维数组第一行的首地址,则 p+i 将指向array[0]数组中的元素array[0][i]。由以上所介绍的二维数组在内存中的存储方式可知,对数组中的任一元素array[i][j] ,其指针的形式为:p+i*N+j (N为每一行的长度)。 元素相应的指针表示法为:*(p+i*N+j) ,下标表示法为:p[i*N+j] 。 For Example: array[4][3]={ {1,2,3},{4,5,6},{7,8,9},{10,11,12}}; int * p=array[0]; 数组array有四个元素,分别为array[0],array[1],array[2],array[3],每个元素为包含3个元素的一维数组, 如array[0]的3个元素为 array[0][0],array[0][1],array[0][2]。 元素array[2][2]对应指针为:array+2*, 指针表示法为:*(array+2*) , 下标表示法为:array[2*] 。特别注意:虽然 array[0] 与 array 都是数组首地址,但两者指向的对象不同,这点要非常明确。
array是一个数组名,右值退化为指针,指向的是一个一维数组a[0]整体,也就是指向{1,2,3}这个数组整体的地址。
array[0]是一个数组名,右值退化为指针,指向的是{1,2,3}这个数组中第一个元素的地址,也就是arrar[0][0]=1的地址.
array作为数组名,左值代表数组,有四个元素,分别为array[0],array[1],array[2],array[3],每个元素为包含3个元素的一维数组,
array[0]作为数组名,左值代表数组,有3个元素为 ,分别为array[0][0],array[0][1],array[0][2]。它作为右值转化为指针,指向的是一维数组array[0]的首地址。(array[0]是一维数组的名字,array[0]作为左值代表数组,作为右值代表的是指针。*array[0]这个表达式中,array[0]作为右值,数据类型由数组退化为指针(该指针指向了array[0]这个数组首元素的地址),所以*array[0]的结果就是第一行第一个元素的值,*array[0]==array[0][0]。
由上面可知:array作为数组名(左值)代表的是二维数组整体,arr[0]作为数组名(左值)代表的是行数组的整体。位运算符sizeof()中,数组名代表左值,所以:
二维数组的总字节长度为:sizeof(arr)
二维数组中每行的字节长度表示为:sizeof(arr[0])
而 array 是二维数组的名字,它指向的是所属元素的首地址,其每个元素为一个行数组。它是以‘行’来作为指针移动单位的,如array+i 指向的是第 i 行。对 array 进行 * 运算,得到的是一维数组 array[0] 的首地址,所以 *array 与 array[0] 为同个值(作为左值都是一个行数组)。【此时array先退化为指针,该指针指向的是第一行这个一维数组(array是一个数组指针,这个指针中的值就是array中第一行这个数组的地址),*array就是对指针取值,结果就是第一行这个数组。此时如果用printf函数打印*array,*array作为第一行这个数组,会退化为指针,打印结果就是第一行首元素的地址】
如果定义 int* p,p为指int类型的指针,指向int 类型,而不是地址。故以下操作 :p=array[0] (正确) ,p=array (错误) 。这点要非常注意。(错误原因是p=array这个表达式中,array作为指针,其类型是一个数组指针,指向了{1,2,3}这个数组,所以p和array两个指针的类型不匹配。)
C语言中不同类型的指针之间如何自动转换的??。指针存储的是地址,地址的位宽根据不同的系统有区别,64位是8字节。
int (*p)[3]; 它表示,数组 *p 具有三个int类型元素,分别为 (*p)[0] , (*p)[1] , (*p)[2] ,即 p指向的是具有三个int类型的一维数组,也就是说,p为行指针。此时,以下运算 p=array 是正确的。
01.数组指针是指向数组首元素的地址的指针,其本质为指针(这个指针存放的是数组首地址的地址,相当于2级指针,这个指针不可移动)。int (*p)[10]; p即为指向数组的指针,又称数组指针。(行数组指针)
02.指针数组是数组元素为指针的数组,其本质为数组。
int*p[2]是指针数组,实质是一个数组,里面的两个元素都是指针, []的优先级比*的优先级高,p先与[]结合,形成数组p[2],有两个元素的数组,再与*结合,表示此数组是指针类型的,每个数组元素相当于一个
int *pointer_array[3]; //指针数组,即是一个存放指针元素的数组,定义后即会有含有三个指针元素的数组,但是每个指针元素并没有初始化(相对而言,char *n[3]={"gain","much","strong"};是一个初始化了的指针数组,其数组中的元素是char型指针)有限个类型相同的变量的集合命名,那么这个名称为数组名
根据数组的定义所以可以知道指针数组中,所有的指针都是相同类型的指针。
字符数组中存放的是字符串的首地址,不是字符串本身,字符串本身位于其他的内存区域,和字符数组是分开的。
03.二维:如char string_1[10][10]只要定义了一个二维数组,无论赋不赋值,系统都会给他分配相应空间,而且该空间一定是连续的。其每个元素表示一个字符。我们可以通过指定下标对其元素进行修改。
数组:如char *str_B[5] 系统至少会分配5个连续的空间用来存储5个元素,表示str_B是一个5个元素的数组,每个元素是一个指向的一个指针。
如果我做这样的定义:
char a[3][8]={"gain","much","strong"};
char *n[3]={"gain","much","strong"};
他们在内存的存储方式分别如右图所示,可见,系统给a分配了3×8的空间,而给n分配的空间则取决于具体字符串的长度。
此外,系统分配给a的空间是连续的,而给n分配的空间则不一定连续。
由此可见,相比于比二维字符数组,数组有明显的优点:一是指针数组中每个元素所指的字符串不必限制在相同的字符长度;二是访问指针数组中的一个元素是用指针间接进行的,效率比下标方式要高。 但是二维字符数组却可以通过下标很方便的修改某一元素的值,而指针数组却无法这么做。
二维数组作为函数参数。
二维数组作为函数参数一般有两种方式:(1) void func(int **array){...} (2) void func(int array[ ][N])
注意第二种方式一定要指明二维数组的列数
当二维数组名作为函数实参时,对应的形参必须是一个行指针变量。
“数组名被改写成一个指针参数”规则并不是递归定义的。
数组的数组会被改写成“数组的指针”,而不是“指针的指针”:(二维数组传递参数后退化位,数组指针。)
指针数组,作为实参传递给函数会退化位二级指针。
实参 所匹配的形参 数组的数组 char c[8][10]; char (*c)[10]; 数组指针 指针数组 char *c[10]; char **c; 指针的指针 数组指针(行指针) char (*c)[10]; char (*c)[10]; 不改变 指针的指针 char **c; char **c; 不改变
对二维数组做如下总结:
1.二维数组和二维指针不是等价的,不能相互赋值
2.指针数组*[] 可以转换为二级指针** 他们相互等价
3.二维数组名和数组指针虽然是一个指针,但编译器并不理解,对他来说是数组类型的指针,但可以类型强制转换
pointer = (int *)array_pointer; array_pointer = (int(*)[3])pointer; pointer = (int *)bi_array;
4.如果想用函数传递二维数组,一般形参用二级指针**p或指针数组*[],可以支持二级指针和指针数组的实参传递,特殊的还可以用(*p)[N]
数组名绝对不等于指针,而且不是指针
数组名仅仅是一个符号,不是变量,它没有自己的存储空间,而指针实实在在的是个变量,有自己的空间:
数组名不是指针,它就是一个符号。
这个是重点分析的地方,很对多人对于数组名就是指针持赞同观点的一个冠冕堂皇的证据是“数组名不能被修改,因为数组名是一个常量指针”,也就是不能执行 a = a+1;这句话对一般,错一半,对的是,数组名确实不能被修改,错的是,不能被修改的原因不是因为数组名是常量指针,而是因为数组名只是一个符号,不是一个变量,因此不能作为一个左值,因此不能被修改,这里又涉及到左值和右值的问题,就不再赘述,网上资料很多。
数组名和指针的本质区别:指针是一个变量,有自己对应的存储空间,而数组名仅仅是一个符号,不是变量,因而没有自己对应的存储空间,到此已经可以得出结论,“数组名永远不等于指针”。