2、指针与数组
2.1、数组名的理解
在研究数组名之前要明晰一个点:在C语言中虽然没有对象,但是我们定义的变量是具有对象的结构的,即一个变量不止单纯地存放一个值,还有其他诸多属性。
定义一个数组变量,例如:
int a[5] = {0};
通过&数组变量名
我们可以得到这个数组变量在内存中的指针,根据之前所学可知指针是具有内置信息的:这个指针的所指向的对象的数据类型为:5元素整型数组
,所指向对象的量级为4*5byte
。
可是,数组变量名的特点就在于,它内置了一个指向数组中首个一阶元素的指针,这个指针是一个地址字面量而非指针变量,即不可修改、赋值,这个指针的内置信息为:所指向的对象的数据类型为:int
,所指向的对象的量级为4byte
。
也就是说,单纯输出数组变量名
,与输出&数组变量名
,得到的是两个不同的指针。
2.2、数组名的退化与升维
以下两种情况下,数组变量名会被视作整个数组:
1、sizeof(数组变量名)
,即sizeof()
单独作用于数组名
,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
2、&数组变量名
,即&
单独作用于数组名
,这里的数组名表示整个数组,取出的是指向整个数组的指针。
除了以上两种情况,数组变量名都被视作指向首个一阶元素的指针(不是指针变量!内置的这个指针的值不可改变!)。特别注意一种情况:sizeof(数组名参与的运算式)
,例如:sizeof(数组名+1)
,此时sizeof()
作用于运算式数组名+1
的结果,故而此时数组名是被视作指向首个一阶元素的指针来参与的。
其实以上的根本原因是:
1、*
操作符对数组指针有弱化作用,即:会把指向数组的指针弱化为指向此数组首个一阶元素的指针。
#include <stdio.h>
int main()
{
int arr[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
//arr为指向首个一阶元素{0,1,2,3}的指针,也就是数组指针
printf("%p\n",arr);//000000000061FDF0
//由于{0,1,2,3}的量级为4*4=16,故而偏移步长为16
printf("%p\n",arr+1);//000000000061FE00
//*a使得数组指针arr弱化,弱化为指向{0,1,2,3}的首个元素0的指针
printf("%p\n",*arr);//000000000061FDF0
//由于0的4,故而偏移步长为4
printf("%p\n",*arr+1);//000000000061FDF4
return 0;
}
2、sizeof()
和&
这两个操作符仅对数组名中内置的指针具有升维作用,即:会把数组名中内置的指针视作指向整个数组的指针。(仅对数组名中内置的指针有此作用,对其他指向首个一阶元素的指针无此作用)。
#include <stdio.h>
int main()
{
int arr[5] = {0};
printf("%zd\n",sizeof(arr));//20
printf("%zd\n",sizeof(&arr[0]));//8
printf("%zd\n",sizeof(&arr[1]));//8
return 0;
}
2.3、通过数组名访问元素的实质
因此可知通过数组名访问元素:arr[n]
,它的实质就是:*(arr+n)
;也可以int* p = arr
,再以p[n]
的方式访问元素。
再探究深一点,以arr[n]
的格式访问元素,[]
是双目操作符,那么就有两个操作数:arr
与n
,arr[n]
这种访问格式在编译阶段都会转化为*(arr+n)
,因此arr
与n
这两个操作数的前后顺序是不影响结果的,故而*(arr+n)
可写成*(n+arr)
,那么arr[n]
也可写成n[arr]
。
2.4、一维数组传参的本质
我们知道,一维数组传参的格式为:函数名(数组名)
,实质上作为实参的是数组名内置的指向数组内首个一阶元素的指针。
如下代码:
#include <stdio.h>
void test(int arr2[])
{
int sz2 = sizeof(arr2)/sizeof(arr2[0]);
printf("sz2 = %d\n", sz2);
}
int main()
{
int arr1[10] = {1,2,3,4,5,6,7,8,9,10};
int sz1 = sizeof(arr1)/sizeof(arr1[0]);
printf("sz1 = %d\n", sz1);
test(arr);
return 0;
}
test()
函数的形参定义格式为:int arr2[]
,注意,这里的形参不是数组!而是一个指针,写成这样类似数组的格式是为了方便使用,当我们使用sizeof(arr2)
得到的返回值是8byte
,也就是指针变量的量级。
2.5、二级指针
指针变量也是变量,是变量就有地址,二级指针变量就是用来存放一级指针的地址的。
例如:指针变量A的地址为&A
,用另一个指针变量B来存放&A
,即让指针变量B指向指针变量A,则指针变量B就是一个二级指针。
代码如下:
int a = 1;
int* pa = &a;
int** ppa = &pa;
ppa
就是二级指针,注意二级指针的定义方式int**
,格式中后面部分的*
指明当前定义的变量是一个指针变量,前面部分的int*
指明当前这个指针所指向的对象的数据类型是整型指针
,所指向的对象的量级为8byte
。
通过对paa
单次解引用*paa
可得到pa
,双次解引用**paa
可得到a
。
n级指针以此类推。
2.6、指针数组
指针数组是指元素为指针的数组。
例如:
int arr1[4] = {0,1,2,3};
int arr2[4] = {4,5,6,7};
int arr3[4] = {8,9,10,11};
int* arr = {arr1,arr2,arr3};
这里的arr
就是一个指针数组,而arr
还内置了一个指向首个一阶元素的指针,而这个数组的一阶元素的类型为int*
,所以arr
内置的指针是一个指向整型指针的二级指针。
2.7、数组指针变量
首先要明晰一个知识点:
数组的数据类型格式为:元素数据类型...[元素个数]
,取决于元素的数据类型与元素的个数:两个数组的元素的数据类型不同,数组的数据类型不同;两个数组的元素的数据类型相同,但元素个数不同,那么数组的数据类型也不同。所以数组的数据类型是千变万化的。
例如:
int arr[10] = {0};
数组arr
的数据类型为:int...[10]
。
数组指针变量是指一个指针变量指向的对象是一个数组。在之前就提过,&数组名
得到的是一个指向数组的指针,所指向对象的量级是整个数组,数组指针变量就是用于存放指向数组的指针。由于数组的数据类型是千变万化的,那么数组指针变量的数据类型也是千变万化的。
数组指针变量的定义格式如下:
int arr[10] = {0};
int (*parr) [10] = &arr;
以上代码中,int ... [10]
就是数组的数据类型,代表指针所指向的对象,*
与parr
结合代表parr
是指针变量。
之所以要加上()
是因为[]
的运算符优先级高于*
,如若不加会导致parr
与[]
先结合:
int* parr[10] = &arr;
意思就变成了定义一个整型指针数组。
数组指针变量一般用于在二维数组传参中担任形参
的角色。
2.8、数组指针变量与二维数组的关系
我们知道一个二维数组arr[m][n]
,arr内置的是指向首个一阶元素的指针,而arr[m]
内置的是指向第m+1
个一阶元素内部的首个二阶元素的指针。
例如:
int arr[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
其中,arr
内置的指针指向首个一阶元素:一维数组{0,1,2,3}
;
arr[0]
内置的指针指向第1个一阶元素(一维数组{0,1,2,3}
)的首个二阶元素:0
;
arr[1]
内置的指针指向第2个一阶元素(一维数组{4,5,6,7}
)的首个二阶元素:4
;
arr[2]
内置的指针指向第3个一阶元素(一维数组{8,9,10,11}
)的首个二阶元素:8
。
由此可看出,二维数组的数组名内置的指针是指向一维数组的数组指针。
其实原因就是:
arr[0]
实际上就是*(arr+0)
,arr+0
的结果依然是指向{0,1,2,3}
的数组指针,而*
会将数组指针弱化为指向数组首个一阶元素的指针,故而*(arr+0)
是指向二阶元素0
的整型指针。
arr[1]
实际上就是*(arr+1)
,arr+1
的结果依然是指向{4,5,6,7}
的数组指针,而*
会将数组指针弱化为指向数组首个一阶元素的指针,故而*(arr+1)
是指向二阶元素0
的整型指针。
arr[2]
实际上就是*(arr+2)
,arr+2
的结果依然是指向{8,9,10,11}
的数组指针,而*
会将数组指针弱化为指向数组首个一阶元素的指针,故而*(arr+2)
是指向二阶元素0
的整型指针。
同时也可以印证:在创建二维数组时,第一个[]
内的数字不能省略,因为二维数组中arr[n]
的格式是一个有意义的整体,即指针。
2.9、数组指针的弱化
十分重要:
&数组变量名
得到的是指向数组的指针,再对这个指针进行解引用:*(&数组变量名)
,会得到指向首个一阶元素的指针,也就是数组变量名内置的指针。原因是因为解引用操作符*
会将数组指针弱化为指向首个一阶元素的指针。
例如:
int arr[5] = {0};
int (*parr) [5] = &arr;
printf("%zd\n",sizeof(*parr));
以上代码中,*parr
等价于arr
。
也可以这样理解:parr
实质就是&arr
,由于*
与&
相遇会发生消解,故而*parr==*&arr==arr
。