那些 C语言指针 你不知道的小秘密 (3)

本文详细解释了数组名的含义,指针与数组的关系,包括野指针的处理、传值调用与传址调用的区别,以及一维数组传参的本质和指针数组的运用。通过实例演示,揭示了数组名作为首元素地址的特性及其在实际编程中的应用策略。
摘要由CSDN通过智能技术生成

本篇会加入个人的所谓‘鱼式疯言’
❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言
而是理解过并总结出来通俗易懂的大白话,
我会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.
🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 !!!
在这里插入图片描述

前言

在上一篇文章中小编主要讲解了

  • 野指针:小编总结了野指针出现的情况并说明其应对 对策

  • assert 断言:小编带着大家试着怎么用 assert 并体会了 assert 防止野指针的益处

  • 传值调用与传址调用: 理解了 传值传址 调用的不同,并说明分别用于哪些 使用场景 更好

  • 二级指针:二级指针的理解,同时并知晓如何 定义解引用

在本篇文章中将继续 和友友们一起探访 指针 的小秘密, 这次咱们只要围绕着 指针数组 的关系展开更进一步的理解

那么友友们让我们开始这次美好的指针之旅吧 💕 💕 💕

目录

  1. 数组名的含义
  2. 数组名的实际运用
  3. 一维数组传参的本质
  4. 指针数组
  5. 指针数组的实际运用

一. 数组名的含义

<1>. 数组名的简介

小爱同学就有疑问了 🤔 🤔 🤔,数组名不就是个名字么,不是相当于 变量名 一样的么,怎么还有含义呢 ? ? ?
是的,是有含义的 ❤️ ❤️ ❤️

数组名啊 !一般而言,本质上的含义就是 首元素的地址
但也有 特殊情况 的出现,下面宝子们就和小编一起去发现吧

我们可以先来做个测试

<2>. 举个栗子

#include <stdio.h>
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 <stdio.h>
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 <stdio.h>

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 <stdio.h>
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 <stdio.h>
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 <stdio.h>
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;
}

在这里插入图片描述
是的,友友们这下就明白原来我们数组的输入输出还能这样玩啊 😎 😎 😎
输入时

  1. 对该下标的元素进行 &
  1. 利用数组名是 首元素 的地址,进行 指针加减偏移 来操作

输出时:

  1. 除了我们想数组的下标访问
  1. 利用数组名是 首元素的地址 ,再 指针加减偏移 ,我们也可以通过得到我们 *每一个元素的的地址 *,来 * 解引用访问

鱼式疯言

*(arr+i) <==> &arr[i]

arr+i <==> &arr[i]

三. 一维数组传参的本质

数组我们学过了,之前也讲了,数组是可以传递给函数的,这个小节我们讨论一下一维数组传参的本质。
首先小伙伴可以思考一个问题 ? ? ?
我们之前都是在 函数外部 计算数组的元素个数
那我们可以把函数传给一个函数后, 函数内部 求数组的元素个数吗?
不妨让我们试一下吧 😃 😃 😃

<1>. 函数外部求数组长度

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;
}

在这里插入图片描述
在函数外部是完全正确的,没有问题 。

<2>. 函数内部

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<stdio.h>

// 参数写成数组形式,本质上还是指针
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<stdio.h>
// 参数写成指针形式
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) 计算的是一个地址的大小(单位字节)而不是数组的大小(单位字节)。

正是因为函数的参数部分是本质是 指针,所以在函数内部是没办法求的 数组元素个数

那我们该怎么解决呢,如果是这样的 😣 😣 😣

<3>. 传参方案

唯一的解决办法就是把对应的数组长度现在函数外部求好,利用 参数 传过去。

#include<stdio.h>

// 参数写成指针形式
// 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;
}

在这里插入图片描述

鱼式疯言

一维数组传参还是 首元素地址 ,数组长度先算好,额外加一份 参数再传

四. 指针数组

<1>. 指针数组的简介

首先我们来思考一个问题

指针数组是指针还是数组?

友友们可以类比一下

整型数组 ,是存放 整型 的数组

字符数组 是存放 字符 的数组

指针数组 呢?是存放 指针 的数组。

C语言中,指针数组是指一个数组,它的每个元素都是一个指针变量。每个指针变量可以指向不同的数据类型或对象
指针数组可以声明为以下形式:

dataType *arrayName[size];

dataType 表示指针变量所指向的 数据类型

arrayName 是数组的 名称

size 是数组的 大小

在这里插入图片描述
小爱同学是不是还是有点困惑呢 😣 😣 😣
没关系 没关系
不妨我们画个图理解一下吧 ! ! !
在这里插入图片描述

  • 指针数组的每个元素都是用来存放 地址(指针) 的。
    如下图 :
    在这里插入图片描述

指针数组的每个 元素地址 ,又可以 指向 一块区域。

鱼式疯言

很多个 相同类型指针 哪里放,指针数组 管饭吃

五. 指针数组的实际运用

想必大家都学过二维数组吧,如果让宝子们写个二维数组,你可能会这样写 ! ! !

<1>. 二维数组

 #include<stdio.h>

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;
}

在这里插入图片描述
但今天小编的主要想法还是带着小伙伴一起用指针数组 来模拟咱们的 二维数组
小伙伴都眼睛睁大咯,可不要眨眼哦 😁 😁 😁

<2>. 模拟二维数组

宝子们请先看代码哦


//指针数组模拟二维数组
#include<stdio.h>

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 来访问具体元素,从而达到 二维数组 的效果 。

鱼式疯言

人狠话不多
看图说话
在这里插入图片描述

先走指针,后走一维数组

总结

  • 数组名的含义 :知晓了数组名的一般含义,以及他的两种特殊情况
  • 数组名的实际运用 : 我们通过指针的形式更进一步利用数组名自身含义的特点来输入输出
  • 一维数组传参的本质 : 传参的本质还是数组名的自身含义,以及数组长度如何解决的办法
  • 指针数组 : 我们更了解了指针数组的基本概念
  • 指针数组的实际运用 : 通过我们了解的指针数组的基本概念来对实践我们的理解

如果觉得小编写的还不错的咱可支持三连下 (定有回访哦) , 不妥当的咱请评论区指正

希望我的文章能给各位宝子们带来哪怕一点点的收获就是 小编创作 的最大动力 💖 💖 💖

在这里插入图片描述

  • 35
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邂逅岁月

感谢干爹的超能力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值