深入理解指针(二)

目录

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]就是整型⼀维数组中的元素。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

本地无法连接linux上的MariaDB数据库

使用mysql -u root -p 输入密码&#xff1a; 进去之后没有user表&#xff0c;无法改user、host等信息。

Blender帧动画

时间线窗口Timeline用于定义帧动画 -视图&#xff1a;方法&#xff0c;平移&#xff0c;框显全部 -帧范围&#xff1a;可以调整动画共多少帧 -当前帧&#xff1a;可以拖动或手工指定 默认每秒24帧 定义一个帧动画类似unity的Timeline&#xff0c;只需定义关键帧&#xff0c…

【太原理工大学】软件安全技术—书本重点梳理、带背

收回我上一篇的话&#xff0c;这科挂人还是挺狠的&#xff0c;去年好像挂了四十号人 ( 老师没有划重点&#xff0c;这篇是我自己根据之前的博主的和课本总结的一些重点&#xff0c;本篇为理解性带背。(&#xff61; _ &#xff61;) ### 第一章&#xff1a;软件安全基础 - **零…

Flask快速入门(路由、CBV、请求和响应、session)

Flask快速入门&#xff08;路由、CBV、请求和响应、session&#xff09; 目录 Flask快速入门&#xff08;路由、CBV、请求和响应、session&#xff09;安装创建页面Debug模式快速使用Werkzeug介绍watchdog介绍快速体验 路由系统源码分析手动配置路由动态路由-转换器 Flask的CBV…

青书学堂 看视频 耍课时

1. 获取课程节点id ( /nynzy/Student/Course/GetStudyRecordAndScore ) 接口地址 2. 把所有的nodeId 保存下来 保存到 old.txt 格式 课程id 与 nodeId 用 | 隔开 3. 然后创建 test.php 注意把 cookie 换成自己的 <?php$oldFilename ./old.txt; $newFilename ./new.…

使用MySQL全文索引实现高效搜索功能

MySQL全文索引是MySQL提供的一种高效的搜索功能&#xff0c;可以快速地搜索文本内容。全文索引可以用于搜索大量文本数据&#xff0c;通常应用在文章、博客、论坛等需要搜索的场景中。 什么是MySQL全文索引 MySQL全文索引是一种用于快速搜索文本内容的索引技术。它可以在存储和…

vue+elementplus模拟“山野愚人居”简单实现个人博客

目录 一、项目介绍 二、项目截图 1.项目结构图 2.项目首页 3.文章详情 4.留言 5.读者 三、源码实现 1.项目依赖package.json 2.项目启动 3.读者页面源码 四、总结 一、项目介绍 模仿原博客&#xff1a;山野愚人居 - 记录我的生活、所见、所闻、所想…… 本项目参考以…

联邦学习权重聚合,联邦学习权重更新

目录 联邦学习权重聚合 model.state_dict() 保存模型参数 加载模型参数 注意事项 联邦学习权重更新 联邦学习权重聚合 model.state_dict() 在PyTorch框架中,model.state_dict() 是一个非常重要的方法,它用于获取模型的参数(即权重和偏置)作为一个有序字典(Order…

最流行的后端框架:如何选择适合自己的框架

最流行的后端框架&#xff1a;如何选择适合自己的框架 在当今快节奏的数字环境中&#xff0c;软件开发需要高效、可扩展且可靠的解决方案。最流行的后端框架&#xff0c;这就是后端框架的用武之地。这些软件框架提供了构建 Web 应用程序的骨干&#xff0c;处理了从数据库交互到…

Spring系统学习 - Bean的作用域

bean作用域介绍 Spring框架提供了不同的作用域来管理Bean的生命周期和可见性&#xff0c;这对于控制不同类型的组件和处理并发请求尤其重要。 singleton&#xff08;默认&#xff09;&#xff1a; 每个Spring IoC容器只有一个bean实例。当容器创建bean后&#xff0c;它会被缓存…

CP AUTOSAR标准中文文档链接索引(更新中)

AUTOSAR标准的核心组件包括通信、诊断、安全等&#xff0c;这些组件通过模块化结构进行组织。系统被划分为多个模块&#xff0c;每个模块负责特定的功能。模块之间通过接口进行通信&#xff0c;接口定义了模块之间的交互规则。AUTOSAR标准支持模块的配置&#xff0c;可以根据不…

Vue25-内置指令02:v-text指令

一、v-html对比v-text v-html支持结构的解析&#xff0c;v-text不支持结构的解析。 二、v-html的安全性问题 2-1、cookie的原理&#xff08;node.js&#xff09; 7天免登录&#xff0c;cookie实现。 cookie的本质就是类似于json的字符串&#xff0c;格式是&#xff1a;key-va…

Web端在线/离线Stomp服务测试与WebSocket服务测试

Stomp服务测试 支持连接、发送、订阅、接收&#xff0c;可设置请求头、自动重连 低配置云服务器&#xff0c;首次加载速度较慢&#xff0c;请耐心等候 预览页面&#xff1a;http://www.daelui.com/#/tigerlair/saas/preview/lxbho9lkzvgc 演练页面&#xff1a;http://www.da…

「OC」UI练习(二)——照片墙

「OC」UI练习——照片墙 文章目录 「OC」UI练习——照片墙UITapGestureRecognizer介绍照片墙实现 UITapGestureRecognizer介绍 UITapGestureRecognizer是UIKit框架中的一个手势识别器类&#xff0c;用于检测用户在视图上的轻击手势。它是UIGestureRecognizer的一个子类&#x…

基于51单片机的智能恒温箱设计--数码管显示

一.硬件方案 根据恒温箱控制器的功能要求&#xff0c;并结合对51系列单片机软件编程自由度大&#xff0c;可用编程实现各种控制算法和逻辑控制。所以采用AT89C52作为电路系统的控制核心。按键将设置好的温度值传给单片机&#xff0c;通过温度显示模块显示出来。初始温度设置好…

Java I/O模型

引言 根据冯.诺依曼结构&#xff0c;计算机结构分为5个部分&#xff1a;运算器、控制器、存储器、输入设备、输出设备。 输入设备和输出设备都属于外部设备。网卡、硬盘这种既可以属于输入设备&#xff0c;也可以属于输出设备。 从计算机结构的视角来看&#xff0c;I/O描述了…

龙迅LT6911GX HDMI 2.1转四 PORT MIPI或者LVDS,支持图像处理以及旋转,内置MCU以及LPDDR4

龙迅LT6911GX描述&#xff1a; LT6911GX是一款高性能的HDMI2.1到MIPI或LVDS芯片&#xff0c;用于VR/显示器应用。HDCP RX作为HDCP中继器的上游端&#xff0c;可以与其他芯片的HDCP TX协同工作&#xff0c;实现中继器的功能。对于HDMI2.1输入&#xff0c;LT6911GX可配置为3/4车…

Elasticsearch集群运维,重平衡、分片、宕节点、扩容

个人博客&#xff1a;无奈何杨&#xff08;wnhyang&#xff09; 个人语雀&#xff1a;wnhyang 共享语雀&#xff1a;在线知识共享 Github&#xff1a;wnhyang - Overview 参考 探索集群 Elasticsearch 中文文档 https://www.elastic.co/guide/en/elasticsearch/reference…

linux系统——wget命令

wget命令可以用于下载指定的url地址文件&#xff0c;支持断点续传&#xff0c;支持ftp&#xff0c;http协议下载&#xff0c;在下载普通文件时&#xff0c;即使网络出现故障&#xff0c;依然会不断尝试下载 wget命令直接加url地址 使用-o参数可以将下载文件改名&#xff0c;-c…

windows11 建立批处理bat文件来删除指定目录下的所有隐藏的文件。

今天在导入项目的时候发现之前项目中的文件夹中有很多隐藏的临时文件&#xff0c;这个文件应该是版本控制产生的&#xff0c;导致导入后文件夹上有X&#xff0c;然后里面文件是一个没有错。 我们来建立一个bat来&#xff0c;进行批量删除隐藏文件就可以了&#xff1a; echo o…