目录
前言
1. 指针访问数组
1.1 数组名的含义
1.2 使用指针访问数组
2. 一维数组传参的本质
3. 二级指针
4. 指针数组
4.1 指针数组模拟二维数组
结语
前言
在本篇文章中,我们将要一起来探讨指针与数组之间的关系,以及如何理解指针数组及其运用
1. 指针访问数组
1.1 数组名的含义
讲清楚数组名之前,我们先来看一段代码
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = &arr[0];
printf("%p\n", p);
return 0;
}
在代码中,我们创建了一个数组,还创建了一个指针 p 来接收数组第一个元素的地址。实际上,数组名就是数组首元素的地址,我们还可以用数组名来重新写一遍上面的代码
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* p1 = &arr[0];
int* p2 = arr;
printf("%p\n", p1);
printf("%p\n", p2);
return 0;
}
运行结果如下:
我们发现数组名和首元素地址打印出来的结果一模一样的,那么我们可以认为:
数组名就是数组首元素的地址,也就是说 arr == &arr[0]
但如果这样的话,会有一个“bug”,当我们用 sizeof 来操作数组名时,结果跟我们的预期并不相符
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%zd\n", sizeof(arr));
printf("%zd\n", sizeof(&arr[0]));
return 0;
}
运行结果如下:
(在X84环境下)
不是说好了 arr == &arr[0] 吗,那为什么结果会不一样呢
其实,“数组名就是数组首元素的地址”这句话没错,但是有两个例外:
- sizeof(数组名):当 sizeof 中单独放了数组名,这里的数组名表示的是整个数组,计算出来就是整个数组的大小,单位是字节
- &数组名:取地址 & 后面加数组名,这里的数组名表示的也是整个数组,因此取出的就是整个数组的地址
- 除了以上两种情况外,数组名表示的都是数组首元素的地址
所以才会出现上面 sizeof(arr)== 40,而 sizeof(&arr[0])== 4 的情况
明白了这个点之后,我们再来看一段代码:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("&arr = %p\n", &arr);
printf("&arr[0] = %p\n", &arr[0]);
return 0;
}
运行结果如下:
三个结果一模一样的,这是为什么呢?别急,我们这就来揭晓:
//我们把上面的代码抄下来,再各自加上 1
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("arr+1 = %p\n", arr + 1);
printf("&arr = %p\n", &arr);
printf("&arr+1 = %p\n", &arr + 1);
printf("&arr[0] = %p\n", &arr[0]);
printf("&arr[0]+1 = %p\n", &arr[0] + 1);
return 0;
}
运行结果如下:
我们可以发现:
- arr 和 arr + 1 相差了 4 个字节
- &arr[0] 和 &arr[0] + 1 相差了 4 个字节
- &arr 和 &arr + 1 相差了 40 个字节
因为 &arr 取出的是数组的地址,当实现 + 1 操作时,是会跳过整个数组的,因此会 + 40 个字节
1.2 使用指针访问数组
在理解了数组名的内涵后,我们就可以很方便地使用指针访问数组了
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
size_t sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int* p = arr;
for (i = 0; i < sz; i++)
{
scanf("%d", p + i);
}
for (i = 0; i < sz; i++)
{
printf("%d", *(p + i));
}
return 0;
}
从上面的代码中我们可以看出:数组名 arr 和指针 p 是等价的,我们平时一般使用的是 arr[i] 来访问数组的元素,那 p[i] 是不是也可以访问数组呢?答案是肯定的
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
size_t sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int* p = arr;
for (i = 0; i < sz; i++)
{
scanf("%d", p + i);
}
for (i = 0; i < sz; i++)
{
printf("%d", p[i]); // 改成p[i]
}
return 0;
}
同理可得:arr[i] 也等价于 *(arr + i)。实际上,数组元素的访问的本质就是转换成首元素的地址加上偏移量来求出元素的地址,然后再解引用来访问具体元素
2. 一维数组传参的本质
我们在使用函数时,偶尔会有参数是数组的情况,下面我们来看一下具体例子
#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));
}
void test2(int* arr)
{
printf("%d\n", sizeof(arr));
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
test1(arr);
test2(arr);
return 0;
}
总结:函数的形参可以写成数组的形式,也可以写成指针的形式
3. 二级指针
指针变量实质上也是一种变量,是变量就有地址。那么二级指针就是指针变量的指针(指针套娃)
对于二级指针:
- *pp 通过对 pp 中的地址进行解引用,这样访问的就是 p 的地址
- **pp 就是先通过 *pp 找到 p 的地址,再对 p 进行解引用操作,最后找到的就是 a
4. 指针数组
指针数组,顾名思义就是存放指针的数组
既然指针数组的每个元素是地址,那么又可以指向其他变量的地址
4.1 指针数组模拟二维数组
例子:按照二维数组的形式打印
#include <stdio.h>
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, j;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("%d ", parr[i][j]); // *(*(parr + i) + j)
}
printf("\n");
}
}
运行结果如下:
原理:
parr[i] 是访问 parr 数组的元素,这时候找到的是一维数组,parr[i][j] 访问的就是一维数组中的元素
该代码可以模拟出二维数组的效果,但和真正的二维数组完全不是一回事。因为它的每一个元素的空间并不是连续的,对于真正的二维数组中元素的空间是连续的。
结语
今天我们一起学习了指针与数组之间的关系,包括二级指针等;如有总结不到位的地方还请多多谅解,若有出现纰漏,希望大佬们看到错误之后能够在私信或评论区指正,博主会及时改正,共同进步!
欢迎各位在评论区友好讨论。如果觉得不错的话,麻烦您点个赞吧,十分感谢!