深入理解指针(二)

news2025/1/12 0:50:08

目录

1. 数组名的理解

2. 使用指针访问数组

3. ⼀维数组传参的本质

4. 冒泡排序

5. 二级指针

6. 指针数组

7. 指针数组模拟二维数组


1. 数组名的理解

有下面一段代码:

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[0];//取出第一个元素的地址,放到p里面。
	return 0;
}

上面代码就是&arr[0]取出首元素的地址,其实数组名就是首元素的地址,下面做个测试。

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

运行代码:

既然arr是数组首元素的地址,是地址的话就应该是4或者8个字节,那么为什么这里会输出40呢?

其实数组名就表示数组首元素的地址,这个说话是正确的,但是有两个例外:

1.sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。

2.&数组名,这⾥的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素的地址是有区别的)。

除此之外,所以的数组名都表示数组首元素的地址。

我们知道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的地址一样,本质上有啥区别呢?

前面我们说过指针类型决定了指针的差异,整型指针加1跳过4个字节,字符指针加1跳过1个字节。

#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[0]+1 = %p\n", &arr[0]+1);

	printf("arr       = %p\n", arr);
	printf("arr+1     = %p\n", arr+1);

	printf("&arr      = %p\n", &arr);
	printf("&arr+1    = %p\n", &arr+1);
	return 0;
}

运行代码:

 

2. 使用指针访问数组

#include <stdio.h>
int main()
{
	int arr[10] = { '\0' };
	//输入
	for (int i = 0; i < 10; i++)
	{
		scanf("%d", &arr[i]);
	}
	//输出
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

 上面这种代码是使用下标的方式来访问数组,那我们也可以使用指针的方式来访问数组。 

#include <stdio.h>
int main()
{
	int arr[10] = { '\0' };
	//输入
	for (int i = 0; i < 10; i++)
	{
		scanf("%d", arr+i);
		//scanf函数需要的是地址,arr表示首元素的地址,加i来遍历我的数组
	}
	//输出
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(arr + i));
		//arr表示首元素的地址,arr+i来遍历数组,*解引用就拿到地址中的值
	}
	return 0;
}

由此可以看出,使用指针也是可以的。

将*(arr+i)换成arr[i]也是能够正常打印的,所以本质上p[i] 是等价于 *(p+i)。 

同理arr[i]应该等价于*(arr+i),数组元素的访问在编译器处理的时候,也是转换成首元素的地址+偏移 量求出元素的地址,然后解引用来访问的。

我们知道加法是支持交换的,a+b就等价于b+a,那么*(arr+i)也可以写成*(i+arr)。

 i[arr]和arr[i]等价,本质上没什么区别,在编译器底层也是转换成指针,但是可读性不高。

总结:

1.数组就是数组,是一块连续的空间,是可以存放一个或者多个数据的。

2.指针变量是一个变量,是可以存放地址的变量。

3.数组和指针不是一回事,但是可以使用指针来访问数组。

为什么可以使用指针来访问数组呢?

1.数组在内存中是连续存放的。

2.指针的加减可以很方便的遍历数组,取出数组的内容(指针的运算)。

3. ⼀维数组传参的本质

求数组的元素个数。

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", sz);
	return 0;
}

那如果我想写一个函数来计算数组元素个数呢?

 

这样写就是在函数内部求数组元素个数啊,可为什么是1呢?

 数组传参的时候,形参可以写成数组,也可以写成指针,数组传参的本质是传递的首元素的地址,所以形参即使写成数组的形式,本质上也是一个指针变量。

显然,在函数内部求数组元素的格式是不行的,那么函数参数就需要多加一个参数。

#include <stdio.h>
void func(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 sz = sizeof(arr) / sizeof(arr[0]);
	func(arr,sz);
	return 0;
}

我们需要将数组元素个数计算出来然后传给函数形参。

4. 冒泡排序

冒泡排序的核心思想就是:两两相邻的元素进行比较。

#include <stdio.h>
void BubbleSorting(int* arr, int sz)
{
	for (int i = 0; i < sz - 1; i++)
	{
		for (int j = 0; j < sz - i - 1; j++)
		{
			if ((*(arr + j) > *(arr + j+1)))
			{
				int tem = *(arr + j);
				*(arr + j) = *(arr + j + 1);
				*(arr + j + 1) = tem;
			}
		}
	}
}

int main()
{
	int arr[10] = { '\0' };
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
		scanf("%d", arr + i);
	BubbleSorting(arr, sz);
	for (int i = 0; i < sz; i++)
		printf("%d ", *(arr + i));
	return 0;
}

上面的代码虽然可以完成我们的需求,但是还是不够好,加入是1,2,3,4,5,6,7,8,9,10这样的数字呢?明显就是升序啊,而上面的代码还会一一去比较大小,这样就是在浪费时间,当我内置的循环第一次结束的时候而一对数字都没有交换的话就说明数组本来就是升序,这个时候就不需要进行第二轮的比较了。

优化后:

#include <stdio.h>
void BubbleSorting(int* arr, int sz)
{
	for (int i = 0; i < sz - 1; i++)
	{
		int flag = 0;//假设是有序的
		for (int j = 0; j < sz - i - 1; j++)
		{
			if ((*(arr + j) > *(arr + j + 1)))
			{
				flag = 1;//不是有序的
				int tem = *(arr + j);
				*(arr + j) = *(arr + j + 1);
				*(arr + j + 1) = tem;
			}
		}
		if (!flag)
			break;
	}
}

int main()
{
	int arr[10] = { '\0' };
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
		scanf("%d", arr + i);
	BubbleSorting(arr, sz);
	for (int i = 0; i < sz; i++)
		printf("%d ", *(arr + i));
	return 0;
}

为了看出效果,我们可以拿优化前和优化后的代码做个对比。 

 对于这种以及是升序的代码,优化前需要比较45次,而优化后只需要比较9次。

5. 二级指针

指针变量也是变量,是变量就有地址,那指针变量的地址就存放在⼆级指针 。

int main()
{
	int a = 10;

	int* p = &a;//p是一级指针

	int** pa = &p;//pa是二级指针
	return 0;
}

那么二级指针该怎么用呢?

 当有一天我需要将指针变量的地址存起来的时候就可以使用二级指针,二级指针和二维数组没有对应的关系。

6. 指针数组

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

如果实在不理解,我们可以换个方式。

char ch[10];//字符数组  --  存放字符的数组
int arr[10];//整型数组  --  存放整型的数组

那么指针数组就是存放指针的数组,数组的每个元素其实都是指针类型。

那么我们可以来写一下指针数组。

char* ch[5];//存放字符指针的数组,每个元素是char*类型,一个5个元素
int* arr[5];//存放整型指针的数组,每个元素是int*类型,一个5个元素

写个代码来理解一下吧。

 

#include <stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int* arr[3] = { &a,&b,&c };

	for (int i = 0; i < 3; i++)
		printf("%d ", *(arr[i]));

	return 0;
}

我们怎么放进去的就怎么拿出来,但是这种写法比较死板。

7. 指针数组模拟二维数组

#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* arr[] = { arr1,arr2,arr3 };
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
			printf("%d ", arr[i][j]);//使用二维数组的方式访问
            //arr[i][j] <===> *(*(arr+i)+j)
		printf("\n");
	}
	return 0;
}

用指针数组也可以模拟实现二维数组。

 arr[i]是访问arr数组的元素,arr[i]找到的数组元素指向了整型⼀维数组,arr[i][j]就是整型⼀维数组中的元素。

上述的代码模拟出⼆维数组的效果,实际上并非完全是⼆维数组,因为每一行并非是连续的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1820146.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【python】通行网格地图四叉树化 (leeccode 427)

【python】通行网格地图四叉树化 受到Leecode 427题的启发&#xff0c;427. 建立四叉树 想将由0和1组成的网格地图绘制为四叉树地图&#xff0c;0表示可通行网格&#xff0c;1表示不可通行网格。 import matplotlib.pyplot as plt import matplotlib.patches as patches …

一文教会你静态住宅代理IP的优势和选择技巧,跨境小白收好这份指南!

一、什么是静态住宅代理IP&#xff1f; 静态住宅代理IP是指分配给个人住宅网络的IP地址&#xff0c;这些IP地址在长时间内保持不变。它们是从互联网服务提供商&#xff08;ISP&#xff09;获取的&#xff0c;因此拥有更高的可信度和较低的被封禁风险。静态住宅代理IP因其独特的…

SpringBoot3 常用的第三方接口调用十种方式

环境&#xff1a;SpringBoot.3.3.0 简介 在项目中调用第三方接口是日常开发中非常常见的。调用方式的选择通常遵循公司既定的技术栈和架构规范&#xff0c;以确保项目的一致性和可维护性。无论是RESTful API调用、Feign声明式HTTP客户端、Apache HttpClient等调用方式&#x…

Word同行内的文字如何左右分别对齐

先打开标尺&#xff08;视图-标尺&#xff09; 开右边&#xff0c;选一个制表位置&#xff0c;比如我选34 切回开始&#xff0c;点段落段落右下角 然后 然后 我修改为35&#xff08;因为“6月13日”总共3个字符&#xff09; 在文字中间按下Tab键&#xff0c;效果如下

Spring Boot 自定义校验注解

1.创建注解&#xff0c;可参考其他检验注解进行创建 2.创建校验类&#xff0c;需实现ContraintValidator并重写isValid方法,注意范型中表示给那个注解(State)提供校验及校验类型&#xff08;String&#xff09;,然后自行编写校验规则true为检验成功&#xff0c;false为失败 3.使…

网工内推 | 外企、上市公司运维工程师,有软考中高项证书优先

01 优尼派特&#xff08;苏州&#xff09;物流有限公司 &#x1f537;招聘岗位&#xff1a;软件运维测试工程师 &#x1f537;任职要求&#xff1a; 1、负责公司自主研发的软件售后服务工作, 包括软件的安装, 调试, 升级,培训, 参数配置, 需求与Bug的处理; 2、负责数据库升级及…

unDraw —— 免费且可定制的插画库,为您的设计注入灵魂

&#x1f3a8; unDraw —— 免费且可定制的插画库&#xff0c;为您的设计注入灵魂 在寻找能够完美融入您品牌风格的插画吗&#xff1f;unDraw&#xff0c;一个提供大量免费插画资源的网站&#xff0c;可能是您的理想选择&#xff01; &#x1f310; 网站特色 免费且开源 unDraw…

Doris集群管理工具Doris Manager安装使用(已踩坑)

背景&#xff1a;Doris集群管理、监控相对复杂&#xff0c;就想着有没有免费的、好用的管理工具&#xff0c;就发现了Doris Manager&#xff0c;给大家分享一下。 官网&#xff1a;https://docs.selectdb.com/docs/enterprise/cluster-manager-guide/deployment-guide/deployme…

【算法训练记录——Day28】

Day28——回溯算法Ⅳ 1.复原IP地址2.[全排列](https://leetcode.cn/problems/permutations/submissions/539240290/)3.[全排列Ⅱ](https://leetcode.cn/problems/permutations-ii/description/) ● 93.复原IP地址 ● 78.子集 ● 90.子集II 1.复原IP地址 思路&#xff1a;相当于…

【OceanBase DBA早下班系列】—— 性能问题如何 “拍CT“ (一键获取火焰图和扁鹊图)

1. 前言 最近接连遇到几个客户的环境在排查集群性能问题&#xff0c;总结了一下&#xff0c;直接教大家如何去获取火焰图、扁鹊图&#xff08;调用关系图&#xff09;&#xff0c;直击要害&#xff0c;就像是内脏的疾病去医院看病&#xff0c;上来先照一个CT&#xff0c;通过分…

HarmonyOS Next 系列之HTTP请求封装和Token持久化存储(四)

系列文章目录 HarmonyOS Next 系列之省市区弹窗选择器实现&#xff08;一&#xff09; HarmonyOS Next 系列之验证码输入组件实现&#xff08;二&#xff09; HarmonyOS Next 系列之底部标签栏TabBar实现&#xff08;三&#xff09; HarmonyOS Next 系列之HTTP请求封装和Token…

家用洗地机排行榜前十名:2024十大王牌机型精准种草

最近很多人都在问我洗地机相关的问题&#xff0c;不愧是改善家庭生活品质的“三神器”之一。洗地机依靠其清洁力和清洁效率吸引了越来越多的平时需要做家务人群的兴趣&#xff0c;为了解答大家关于洗地机的各种疑问&#xff0c;我把市面上目前非常火爆的洗地机型号和参数都进行…

探索未来通信的新边界:AQChat一款融合AI的在线匿名聊天

探索未来通信的新边界&#xff1a;AQChat一款融合AI的在线匿名聊天 在数字时代&#xff0c;即时通讯变得无处不在&#xff0c;但隐私和性能仍旧是许多用户和开发者关注的焦点。今天&#xff0c;我要介绍一个开创性的开源项目 —— AQChat&#xff0c;它不仅重定义了在线匿名聊…

Spring IoC注解

一、回顾反射机制 反射的调用三步&#xff1a;1&#xff09;获取类。2&#xff09;获取方法。3&#xff09;调用方法 调用方法&#xff1a;调用哪个对象&#xff0c;哪个方法&#xff0c;传什么参数&#xff0c;返回什么值。 方法&#xff08;Do&#xff09;类&#xff1a; …

eFuse电子保险丝,需要了解的技术干货来啦

热保险丝作为一种基本的电路保护器件&#xff0c;已经成功使用了150多年。热保险丝有效可靠、易用&#xff0c;具有各种不同的数值和版本&#xff0c;能够满足不同的设计目标。然而&#xff0c;对于寻求以极快的速度切断电流的设计人员来说&#xff0c;热保险丝不可避免的缺点就…

【高校科研前沿】北京大学赵鹏军教授团队在Nature Communications发文:揭示城市人群移动的空间方向性

文章简介 论文名称&#xff1a;Unravelling the spatial directionality of urban mobility 第一作者及单位&#xff1a;赵鹏军&#xff08;教授|第一作者|北京大学&#xff09;&王浩&#xff08;博士生|共同一作|北京大学&#xff09;; 通讯作者及单位&#xff1a;赵鹏军…

SCI二区|鲸鱼优化算法(WOA)原理及实现【附完整Matlab代码】

目录 1.背景2.算法原理2.1算法思想 3.结果展示4.参考文献5.代码获取 1.背景 2016年&#xff0c;S Mirjalili受到自然界座头鲸社会行为启发&#xff0c;提出了鲸鱼优化算法&#xff08;Whale Optimization Algorithm, WOA&#xff09;。 2.算法原理 WOA模拟了座头鲸的社会行为…

会议室占用全透明化,内幕大揭秘!

会议室管理的现实问题 &#x1f3e2; 有限的会议室资源: 在现代办公环境中&#xff0c;会议室资源通常是有限的&#xff0c;特别是在大型企业或繁忙的办公楼内&#xff0c;会议室的预订和管理变得尤为重要。 &#x1f552; 复杂的预订流程: 常常会出现会议室预订流程繁琐、不…

2024年6月14日 十二生肖 今日运势

小运播报&#xff1a;2024年6月14日&#xff0c;星期五&#xff0c;农历五月初九 &#xff08;甲辰年庚午月己酉日&#xff09;&#xff0c;法定工作日。今天世界献血日&#xff0c;捐献新鲜血液&#xff0c;挽救更多生命&#xff0c;每位献血者都是英雄&#xff01; 红榜生肖…

【论文复现|智能算法改进】基于改进鲸鱼优化算法的移动机器人多目标点路径规划

目录 1.算法原理2.数学模型3.改进点4.结果展示5.参考文献6.代码获取 1.算法原理 SCI二区|鲸鱼优化算法&#xff08;WOA&#xff09;原理及实现【附完整Matlab代码】 2.数学模型 使用 A* 算法生成所有目标点之间的距离矩阵U: U [ d 1 − 1 d 1 − 2 d 1 − 3 ⋯ d 1 − i d…