本篇会加入个人的所谓‘鱼式疯言’
❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言
而是理解过并总结出来通俗易懂的大白话,
我会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.
可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 !!!
在上一篇文章中小编主要讲解了
野指针:小编总结了野指针出现的情况并说明其应对 对策
assert 断言:小编带着大家试着怎么用 assert 并体会了 assert 防止野指针的益处
传值调用与传址调用: 理解了 传值 和 传址 调用的不同,并说明分别用于哪些 使用场景 更好
二级指针:二级指针的理解,同时并知晓如何 定义 和 解引用
在本篇文章中将继续 和友友们一起探访 指针 的小秘密, 这次咱们只要围绕着 指针 与 数组 的关系展开更进一步的理解
那么友友们让我们开始这次美好的指针之旅吧
小爱同学就有疑问了 ,数组名不就是个名字么,不是相当于 变量名 一样的么,怎么还有含义呢 ? ? ?
是的,是有含义的 ❤️ ❤️ ❤️
数组名啊 !一般而言,本质上的含义就是 首元素的地址
但也有 特殊情况 的出现,下面宝子们就和小编一起去发现吧
我们可以先来做个测试
#include
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] = %p\n", &arr[0]);
printf("arr = %p\n", arr);
return 0;
}
我们发现数组名和数组首元素的地址打印出的结果一模一样,数组名就是数组首元素(第一个元素)的地
址。
这时候小爱同学就会有疑问?数组名如果是数组首元素的地址,那下面的代码怎么理解呢?
#include
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", sizeof(arr));
return 0;
}
输出的结果是:40,如果arr是数组首元素的地址,那输出应该的应该是 4 / 8 才对。
其实数组名就是 数组首元素(第一个元素) 的地址是对的,但是有两种特殊情况
• sizeof(数组名) ,sizeof 中单独放数组名,这里的数组名表示 整个数组 ,计算的是 整个数组 的大小, 单位是 字节
• & 数组名,这里的数组名表示整个数组,取出的是 整个数组 的地址 (整个数组的地址和数组首元素的地址 是有区别的) 除此之外,任何地方使用数组名,数组名都表示首元素的地址。
& 和 sizeof 喜欢 搞特殊 ,贪心的想得到 数组的全部 ,其他都很知足,只需要数组 首元素的地址
#include
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//取地址首元素
printf("&arr[0] = %p\n", &arr[0]);
//数组名
printf("arr = %p\n", arr);
//取地址数组名
printf("&arr = %p\n", &arr);
return 0;
}
三个打印结果一模一样,那小爱同学又纳闷了,那 arr 和 &arr 有啥区别呢 ?
最后小小的证明一下小编的说的到底有没有依据吧
#include
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//取地址首元素并 +1
printf("&arr[0] = %p\n", &arr[0]);
printf("&arr[0]+1 = %p\n", &arr[0] + 1);
//数组名并 +1
printf("arr = %p\n", arr);
printf("arr+1 = %p\n", arr + 1);
//取地址数组名并 +1
printf("&arr = %p\n", &arr);
printf("&arr+1 = %p\n", &arr + 1);
return 0;
}
&arr[0] 和 &arr[0]+1 相差 4 个字节
arr和 arr+1 相差 4 个字节
是因为 &arr[0] 和 arr 都是首元素的地址,+1 就是跳过 4个字节(一个元素)。
但是 &arr 和 &arr+1 相差 40(十六进制的地址就相差28) 个字节
这就是因为 &arr是数组的地址,+1 操作是跳过整个数组的。
到这里大家应该搞清楚 数组名 的意义了吧。
数组名是 数组首元素 的地址,但是有 2种 特殊情况。
当我们需要输入输出数组时 , 我猜小爱同学可能会这样写
#include
int main()
{
int arr[10] = { 0 };
//输入
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
//输入
for (i = 0; i < sz; i++)
{
scanf("%d", &arr[i]);
}
//输出
for (i = 0; i < sz; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
#include
int main()
{
int arr[10] = { 0 };
//输入
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
//输入
for (i = 0; i < sz; i++)
{
//利用数组名是首元素地址
//数组名在 + i 地址进行右移
scanf("%d", arr + i);
//scanf("%d", &arr[i]);
//也可以这样写
}
//输出
for (i = 0; i < sz; i++)
{
//得到每个元素的地址
//解引用打印数据
printf("%d ", *(arr + i));
//printf("%d ",arr[i]);
//也可以这样写
}
return 0;
}
是的,友友们这下就明白原来我们数组的输入输出还能这样玩啊
输入时
- 对该下标的元素进行 &
- 利用数组名是 首元素 的地址,进行 指针加减偏移 来操作
输出时:
- 除了我们想数组的下标访问
- 利用数组名是 首元素的地址 ,再 指针加减偏移 ,我们也可以通过得到我们 *每一个元素的的地址 *,来 * 解引用访问
鱼式疯言
*(arr+i) <==> &arr[i]
arr+i <==> &arr[i]
数组我们学过了,之前也讲了,数组是可以传递给函数的,这个小节我们讨论一下一维数组传参的本质。
首先小伙伴可以思考一个问题 ? ? ?
我们之前都是在 函数外部 计算数组的元素个数
那我们可以把函数传给一个函数后, 函数内部 求数组的元素个数吗?
不妨让我们试一下吧
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//在 test 函数外部求数组长度
int sz1 = sizeof(arr) / sizeof(arr[0]);
// 验证其大小
printf("数组长度为 : %d\n", sz1);
//test(arr);
return 0;
}
void test(int arr[10])
{
int sz1 = sizeof(arr) / sizeof(arr[0]);
printf("数组长度为 : %d\n", sz1);
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
test(arr);
return 0;
}
我们发现在函数内部是没有正确获得数组的元素个数。
小爱同学就深思了,这到底是什么原因呢 ? ? ?
竟然我们今天要学习 数组传参的本质 ,那这个 内在的原因 肯定 和 数组传参 脱不了干系
友友们可以思考下
我们上一节提过数组名本质上是啥,是首元素的地址吧
那么有没有一种可能数组名在传参的时候根本没有把整个数组传过去,而是传了 首元素的地址 呢
居然我们提出了猜想,不妨我们用代码来验证下吧
#include
// 参数写成数组形式,本质上还是指针
void test(int arr[])
{
//验证一下
//在 X86 的环境下
//一个指针大小是4个字节
printf("%d\n", sizeof(arr));
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
test(arr);
return 0;
}
实践证明,我们本质上数组传参本质上传递的是数组首元素的地址
如果是这样子的话,那么我们的代码以后是否可以这样写呢 ! ! !
#include
// 参数写成指针形式
void test1(int* arr)
{
printf("%d\n", sizeof(arr));
//计算一个指针变量的大小
for (int i = 0; i < 10; i++)
{
printf("%d ", *(arr + i));
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
test1(arr);
return 0;
}
但是,我们知道了本质,
这不禁也知道sizeof(arr) 计算的是一个地址的大小(单位字节)而不是数组的大小(单位字节)。
正是因为函数的参数部分是本质是 指针,所以在函数内部是没办法求的 数组元素个数 的
那我们该怎么解决呢,如果是这样的
唯一的解决办法就是把对应的数组长度现在函数外部求好,利用 参数 传过去。
#include
// 参数写成指针形式
// sz 来接收数组长度
void test1(int* arr,int sz)
{
//利用最大长度来输出打印我们的数组元素
for (int i = 0; i < sz; i++)
{
printf("%d ", *(arr + i));
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
// 函数外部求数组长度
int slz = sizeof(arr) / sizeof(arr[0]);
// 传入 slz 长度
test1(arr, slz);
return 0;
}
一维数组传参还是 首元素地址 ,数组长度先算好,额外加一份 参数再传
首先我们来思考一个问题
指针数组是指针还是数组?
友友们可以类比一下
整型数组 ,是存放 整型 的数组
字符数组 是存放 字符 的数组
那 指针数组 呢?是存放 指针 的数组。
C语言中,指针数组是指一个数组,它的每个元素都是一个指针变量。每个指针变量可以指向不同的数据类型或对象
指针数组可以声明为以下形式:
dataType *arrayName[size];
dataType 表示指针变量所指向的 数据类型
arrayName 是数组的 名称
size 是数组的 大小。
小爱同学是不是还是有点困惑呢
没关系 没关系
不妨我们画个图理解一下吧 ! ! !
指针数组的每个 元素 是 地址 ,又可以 指向 一块区域。
很多个 相同类型 的 指针 哪里放,指针数组 管饭吃
想必大家都学过二维数组吧,如果让宝子们写个二维数组,你可能会这样写 ! ! !
#include
int main()
{
//二维数组初始化
int a[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };
int i = 0,j=0;
//外循环为行数
for ( i = 0; i < 3; i++)
{
//内循环为列数
for ( j = 0; j < 4; j++)
{
printf("%d ", a[i][j]);
}
printf("\n");
}
return 0;
}
但今天小编的主要想法还是带着小伙伴一起用指针数组 来模拟咱们的 二维数组 。
小伙伴都眼睛睁大咯,可不要眨眼哦
宝子们请先看代码哦
//指针数组模拟二维数组
#include
int main()
{
int a1[5] = {1,2,3,4,5};
int a2[5] = {2,3,4,5,6};
int a3[5] = {3,4,6,7,8};
//将每个一维数组的首地址放入指针数组中
int* a[3] = { {a1},{a2},{a3} };
int i = 0, j = 0;
//先找到第几个 一维数组
for (i = 0; i < 3; i++)
{
//再找到该 一维数组中的每一个元素
for ( j = 0; j < 5; j++)
{
printf("%d ", a[i][j]);
}
printf("\n");
}
return 0;
}
我们的把每个一维数组的地址都放在指针数组中,通过指针数组的下标 i 我们可以访问 哪个一维数组
再通过该一维数组的下标 j 来访问具体元素,从而达到 二维数组 的效果 。
先走指针,后走一维数组
如果觉得小编写的还不错的咱可支持三连下 (定有回访哦) , 不妥当的咱请评论区指正
希望我的文章能给各位宝子们带来哪怕一点点的收获就是 小编创作 的最大动力