前面已经向大家介绍了指针的一些基本内容,接下来,就在再我来先大家讲解一下指针的其他内容。
1. 数组名的理解
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
在学习数组的过程中,我们肯定会写过以上代码,我们知道 int 是该数组的数据类型,[10] 是该数组的大小,arr是数组名,那数组名代表这什么吗?它除了是数组的名字之外,还有其他意义吗?
接着还是用代码来探索其意义
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("arr=%p", arr);printf("arr[0]=p", &arr[0]);return 0;
}
我们写出以上代码并运行,分别打印其地址,运行后我们惊奇的发现arr的地址和arr[0]的地址恰好一样,而arr[0]有恰好是首元素。由此。我们得出数组名不仅仅是数组的名字,其也是数组首元素的地址。
那是不是在所有情况下,数组名都是首元素地址吗?
答案肯定不是,有两种情况除外。
(1)sizeof(arr) ,当我们用sizeof()来计算数组的大小时,此时数组名代表的是整个数组。
我们还是用代码来直观感受一下
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int sz1 = sizeof(arr);printf("arr=%d\n", sz1);int sz2 = sizeof(arr[0]);printf("arr[0]=%d", arr[0]);return 0;
}
用sz1来存储arr的大小,用sz2存储arr[0]的大小( 数组中一个元素的大小)。
运行代码
发现sz1的值为40,恰好是整个数组的大小,说明此时数组名代表整个数组。
(2)&arr 当数组名放在取地址操作符后面时,此时数组名也代表着整个数组。
我们还是用代码直接感受
int* p1 = &arr;
int* p2 = &arr[0];
printf("&arr =%p\n", &arr);
printf("&arr+1=%p\n", arr + 1);
printf("&arr[0] =%p\n", &arr[0]);
printf("&srr[0]+1=%p\n", &arr[0] + 1);
分别对arr和arr[0]取地址,分别将其存于指针变量p1和p2中
运行代码发现,分别对其加1时,指针p1跳过了40个字节,而指针p2却只跳过了一个字节。
前面我们学过,指针变量的类型绝定了其加1减1,一次能跳过的大小。
所以只有当指针p1指向整个数组时,p1才可能一次跳过40个字节。所以当数组名在&操作符后面时,此时数组名也代表整个数组。
除了这两种情况外,数组名都代表数组首元素地址。
2.使用指针访问数组名
既然数组名可以代表首元素地址,那么就可以用指针变量来保存其地址。
所以当我们用数组名作为一个函数的实参时,其形参可以写成数组的形式也可以用指针变量来接收。
我们还是用代码直接感受一下
int main()
{int arr[5] = { 0 };int sz = sizeof(arr) / sizeof(arr[0]);int* p = arr;int i = 0;for (i = 0; i < sz; i++){scanf("%d ", p + i); //这里arr本身就有 & 含义了,所以不用在家 &}for (i = 0; i < sz; i++){printf("%d ", *(p + i));}return 0;
}
从上面代码可以看出*(p+i)和arr[i]是等价的。
其实 *(p+i)写成p[i]也是可以运行的,所以说本质上*(p+i)是等同于p[i]的。
有个小技巧,我们可以将 [ ] 看成一个解引用操作符就好理解了。
总结:数组的访问,是以首元素地址的基础上进行的偏移量进行访问的。
3.一维数组传参的本质
当我们将一个一维数组传递给一个函数的时候,本质上如何传递的呢?接下来探讨一下。
以代码为例
void test(int arr[])
{int sz2 = sizeof(arr)/sizeof(arr[0]);printf("sz2 = %d\n", sz2);
}
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int sz1 = sizeof(arr)/sizeof(arr[0]);printf("sz1 = %d\n", sz1);test(arr);return 0;
}
从代码运行结果发现,sz1的值和sz2的值并不相同。这是为什么呢?
哦,原来当我们将一个数组名传过去的时候,其实是传过去了一个地址,则形参的类型就是一个指针类型,所以当我们在test()函数里面计算arr的大小时,其实并不是计算数组的大小,而是计算了一个指针的大小。正因为函数部分参数是一个指针,所以导致我们无法在函数内部计算数组的大小。
总结:一维数组传参时,形参部分可以写成数组形式,也可以写成指针形式。
4.二级指针
既然变量都有属于自己的地址,那么指针变量也有属于它自己的地址,那要用什么来存储指针变量呢?
那便是二级指针。
这就是一个二级指针和一个一级指针的关系图
当我们对二级指针进行解引用,得到的是一级指针的地址,再对二级指针进行一次接应用,我们就可以得到一级指针里面保存的内容。
5.指针数组
紧接着我们再来将一个特别的数组---------指针数组。
通俗易懂,指针数组就是一个用来存放指针的数组。
6.指针数组模拟二维数组
int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };int* parr[] = { arr1,arr2,arr3 };int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){printf("%d ", parr[i][j]);}printf("\n");}return 0;
}
分析代码:
首先我们要清楚,二维数组本质上可以看成是有多个一维数组组成的,这里我们将每个数组的数组名都存储在一个指针数组中,其中arr1,arr2,arr3 分别是指针数组中的第1个,第2个,第3个元素,前面我们提到 [ ] 可以看成解引用操作符。假如我们对parr解引用一次,便得到了arr1,也就是arr1的首元素地址,在对其进行一次解引用,就进一步的到了arr1里面的内容。
简单来说,parr[1]就等于arr1,parr[1][i]就相当于arr[1][j]。后面的以此类推。
代码运行如下
以上就是我对数组名的理解。谢谢大家的观看。