指针专题(2)

前言

上一节我们学习了指针的相关内容,本节我们继续学习指针专题,更加深入的了解指针,那么废话不多说,我们正式进入今天的学习

1.对数组名的深入理解

在上一节的内容中,我们提到了用指针来访问数组的操作,我们通过使用 &arr[0] 来拿到数组首元素的地址。因为数组的内容在内存空间里面是连续排放的,所以我们只要知道了首元素的地址和数组中元素的个数就可以访问到每个元素的地址。

在前面的学习中我们知道:数组名是首元素的地址,我们来验证一下:

int main(void)
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("arr		= %p\n", arr);
	printf("&arr[0] = %p\n", &arr[0]);
	return 0;
}

通过以上代码,因为两者打印出来的结果是一样的,所以我们就可以确认数组名是首元素地址

(代码运行的环境是X32)

现在我们再来联想一下之前学过的sizeof函数,如果说数组名是首元素的地址的话,那么我们打印sizeof(arr)的结果只有可能是8或者4,其取值只和64位或者32位有关;

int main(void)
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("arr	= %p\n", arr);
	printf("&arr[0] = %p\n", &arr[0]);
	printf("sizeof(arr) = %d\n", sizeof(arr));
	return 0;
}

我们来运行一下这段代码,但却得出了我们意料之外的取值:

所以我们可以知道:数组名是首元素的地址这个说法存在一定的疏漏

数组名是数组首个元素的地址基本上是正确的,但是存在两个特殊情况

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

2.&+数组名,这里的数组名也表示整个数组,取出的是整个数组的地址

除了这两种特殊情况以外,所有地方的数组名都表示首元素的地址

此时我们可能会对"整个数组的地址"的概念产生疑问,我们写一串代码来看看&+数组名的情况:

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

我们发现这三种情况打印出来的结果是一模一样的,都是从第一个元素地址开始的,那么&+数组名和数组名又有什么区别呢?

我们写一串代码来表示它们之间的区别吧:

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

这⾥我们发现

&arr[0] 和 &arr[0]+1相差4个字节;

arr 和 arr+1相差4个字节;

是因为&arr[0]和arr都是首元素的地址,说明+1是跳过⼀个元素

 但是 &arr 和 &arr+1 相差40个字节,这就是因为&arr是数组的地址,说明+1操作是跳过整个数组

这样我们就能很清楚的知道&+数组名和数组名的区别了

因为&arr+1跳过的不是4个字节而是40个字节,所以说它的类型就不是int*,其具体类型请看后文

2.使用指针访问数组

该内容其实在上一节就有过讲解,但学习完数组名的理解后我们会对这部分内容有更深入的理解,所以我们再看一次来加深印象

我们先来看看这两串代码:

int main()
{
	int arr[10] = { 0 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", p + i);
		//scanf("%d", arr+i);//也可以这样写

	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p + i));
	}
	return 0;
}
int main()
{
	int arr[10] = { 0 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = arr;
	for (i = 0; i < sz; i++)
	{
		scanf("%d", p + i);
		//scanf("%d", arr+i);//也可以这样写
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", p[i]);
	}
	return 0;
}

这两串代码都可以完成通过指针来访问数组的功能。通过比较,我们可以发现:我们将*(p+i)换成p[i] 也是能够正常打印的,所以本质上 p[i] 是等价于 *(p+i)

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

3.一维数组传参的本质

我们知道:数组是可以传递给函数的,那么我们讨论⼀下数组传参的本质

我们来看一下下面代码:

void test(int arr[])
{
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("sz2 = %d\n", sz2);
}
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	printf("sz1 = %d\n", sz1);
	test(arr);
	return 0;
}

之前我们都是在函数外部计算元素的个数的,但是我们以如上的形式在函数内部计算元素的个数的时候却出现了问题:我们发现我们往函数内部传入数组的时候,在函数内部没有正确获得数组的元素个数

数组传参的时候,传递的是数组名。所以本质上数组传参本质上传递的是数组首元素的地址,正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的;

所以函数形参的部分理论上应该使用指针变量来接收首元素的地址,但是我们写作数组的形式也没有问题,例如以下代码:

写成指针形式更规范,写成数组的形式更容易理解

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

通过上面的代码我们知道了:⼀维数组传参,在函数内形参接收时可以写成数组的形式,也可以写成指针的形式,但是本质上还是指针

总结:

1.一维数组传参的时候,传过去的是数组首元素地址

2.形参的部分可以写成指针的形式,也可以写成数组的形式,但是其本质上还是指针,写成数组的形式只是为了方便理解

4.冒泡排序

如果我们在数组中有一组数据,我们能否通过指针来实现对数组元素的排序呢?答案是可行的,我们此时就需要用到排序

排序的算法有很多,举几个常见的例子:

1.冒泡排序

2.选择排序

3.插入排序

4.快速排序

5.堆排序

6.希尔排序

那么今天我们就来学习一下冒泡排序;

假设数组里面有以下元素:

int arr[] = { 9,8,7,6,5,4,3,2,1,0 };

我们需要把数组里面的内容排为升序要该怎么做呢?

首先我们需要知道冒泡排序的原理和思想:

我们首先需要对两两相邻的元素的大小进行比较,如果不满足我们所求的顺序(降序或者升序)就把两个元素进行交换,如果满足顺序就找下一对

9876543210
8976543210
8796543210

到最后:

8765432109

这样就执行了一趟冒泡排序,我们解决了最高位的9

我们再重复将一趟冒泡排序执行八次(一共执行了九次,是最坏的情况):

0123456789

所以我们可以得知:一趟冒泡排序能解决数组中一位元素的顺序,在最坏的情况下(升序改为降序或者降序改为升序)会执行(数组里的元素个数-1)次冒泡排序

注意:我们第一趟冒泡排序需要进行9次比较。而第二趟冒泡排序因为数组里面的最高为元素已经被确定,所以我们此时只需要进行8此比较;进行第三趟冒泡排序的时候最高的两位元素已经被确定,所以我们此时只需要进行7次比较;

所以我们可以知道:我们每进行一趟冒泡排序,都可以确定一位元素的位置,那么下一趟冒泡排序的比较数据的次数就要减少一次

通过以上我们归纳的注意事项,我们就可以实现冒泡排序了:

void bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		//一趟冒泡排序
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

int main(void)
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	//排序为升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr,sz);
	print(arr, sz);
}

优化

虽然刚才的代码能够完成冒泡排序的功能,但是代码存在冗杂的情况,我们还可以对其进行优化

我们举一个极端的例子,如果我们数组里面的数据如下:

	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };

它已经存在着顺序了,我们按常理来说并不需要进行任何的操作,但是我们按照之前代码的做法还是会一直比较,这样就会拖垮程序的运行时间,影响程序效率

我们知道:如果一趟冒泡排序过后,如果没有一对元素进行了交换,说明此时数组里面已经有顺序了,此时就不需要再往后执行冒泡排序了

因为,我们不知道具体要进行几趟冒泡排序以后数组里面的元素才会有顺序,所以此时我们先假设是有序的,我们定义一个变量并且命名为flag,我们设flag的初始值为1,表示默认数组里面所有元素都是有序的一旦数组里面元素有交换,我们就把flag赋值为0,每次进行完一趟冒泡排序以后我们都对flag的取值进行一次判断,如果flag的取值仍然为1,说明此时数组内的元素就已经有顺序了,我们执行break跳出循环

​
int count = 0;
void bubble_sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		//一趟冒泡排序
		int flag = 1;//假设是有序的
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			count++;
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				flag = 0;//不是有序的
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}

void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

int main(void)
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
	//排序为升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr,sz);
	print(arr, sz);
	printf("冒泡排序的次数为 %d\n", count);
}

​

此时冒泡排序只需要执行9次,相比之前的45得到了优化(这里的次数不是趟数,次数是判断两个相邻元素的大小的次数)

5.二级指针

我们通过之前的学习知道了指针变量也是一个变量,那么指针变量也会有自己的地址,那么我们可能会产生疑问 :是不是指针变量的地址也可以用一个变量来存起来呢?

这种存储指针变量的地址的变量就叫做二级指针

	int a = 0;
	int* p = &a;

如上之前我们所学过的指针变量p其实叫做一级指针变量

因为p指针变量也有自己的地址,所以我们可以用&p来取出p的地址,此时我们需要定义一个二级指针变量pp来存入p的地址

所以我们可以知道二级指针变量是用来存放一级指针变量的地址的

int main(void)
{
	int a = 0;
	int* p = &a;
	int** pp = &p;
	return 0;
}

二级指针的运算

int main(void)
{
	int a = 0;
	int* p = &a;
	int** pp = &p;
	printf("   a = %d\n", a);
	printf("   p = %p\n", p);
	printf("  &a = %p\n", &a);
	printf(" *pp = %p\n",*pp);
	printf("**pp = %d\n", **pp);
	return 0;
}

1.*ppa 就是对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa 

2.**ppa 先通过 *ppa 找到 pa ,然后对pa 进⾏解引用操作等同于*pa 找到的是 a

6.指针数组

我们初次看到指针数组这个概念的时候可能会产生疑问?指针数组里面既有指针又有数组,那么指针数组的本质是什么呢?

我们可以尝试类比一下:

之前我们学习过:

整型数组:例如int arr[10],整型数组是存放整型的数组

字符数组:例如char arr[10],字符数组是存放字符的数组

通过这些例子,我们可以推断:指针数组是存放指针的数组

所以我们就可以知道数组指针的表达形式为:Type* arr[n]

指针数组中的每个元素都是用来存放地址的,如下图所示:

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

我们先来思考一下:我们有没有什么办法能不使用二维数组来实现二维数组的功能呢?

因为数组名表示的是首元素的地址,我们此时就可以使用指针数组来模拟实现二维数组

//使用指针数组模拟实现二维数组
int main(void)
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	int* arr[3] = { arr1,arr2,arr3 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("%d", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

结尾

本节我们继续学习了数组有关的内容,下一节我们学习的内容还是指针,谢谢大家的浏览!!!

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

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

相关文章

昂科烧录器支持Nuvoton新唐科技的低功耗微控制器M482SIDAE

芯片烧录行业领导者-昂科技术近日发布最新的烧录软件更新及新增支持的芯片型号列表&#xff0c;其中Nuvoton新唐科技的低功耗微控制器M482SIDAE已经被昂科的通用烧录平台AP8000所支持。 M482SIDAE以Arm Cortex-M4F为核心&#xff0c;是带有DSP指令集的高效能低功耗微控制器。其…

Mybatis的注解开发

1、概述 mybatis中也提供了注解式开发方式&#xff0c;采用注解可以减少Sql映射文件的配置。 使用注解式开发的话&#xff0c;sql语句是写在java程序中的&#xff0c;这种方式也给sql语句的维护带来成本。 使用注解来映射简单语句会使代码显得更加简洁&#xff0c;但对于稍微…

ASP.NET基于WEB的工作计划流程管理系统的设计与实现

摘 要 信息技术的飞速发展&#xff0c;尤其是网络通讯技术、数据库技术及自动化技术的日新月异&#xff0c;为单位、企业的办公带来了极大的便利。但是由于单位、企业的工作性质众多&#xff0c;工作流程各有差异&#xff0c;企业、单位、部门之间的管理机制各不相同&#xf…

OpenHarmony实战开发-Web自定义长按菜单案例。

介绍 本示例介绍了给Webview页面中可点击元素&#xff08;超链接/图片&#xff09;绑定长按/鼠标右击时的自定义菜单的方案。 效果预览图 使用说明 长按Web页面中的图片或者链接元素&#xff0c;弹出自定义的Menu菜单&#xff0c;创建自定义的操作&#xff0c;如复制图片、使…

定时器详解

定时器&#xff1a;Timer类 常用方法方法&#xff1a; 1.schedule(TimeTask timetask,long delay,(long period)): TimeTask&#xff1a;实现了Runnable类&#xff0c;实现时需要重写run方法 delay&#xff1a;表示延迟多少(decay)后开始执行任务&#xff0c;单位是毫秒&#x…

密码学 | 椭圆曲线密码学 ECC 入门(四)

目录 正文 1 曲线方程 2 点的运算 3 求解过程 4 补充&#xff1a;有限域 ⚠️ 知乎&#xff1a;【密码专栏】动手计算双线性对&#xff08;中&#xff09; - 知乎 ⚠️ 写在前面&#xff1a;本文属搬运博客&#xff0c;自己留着学习。注意&#xff0c;这篇博客与前三…

LeetCode in Python 200. Number of islands (岛屿数量)

岛屿数量既可以用深度优先搜索也可以用广度优先搜索解决&#xff0c;本文给出两种方法的代码实现。 示例&#xff1a; 图1 岛屿数量输入输出示意图 方法一&#xff1a;广度优先搜索(bfs) 代码&#xff1a; class Solution:def numIslands(self, grid):if not grid:return 0…

【WSL报错】执行:wsl --list --online;错误:0x80072ee7

【WSL报错】执行:wsl --list --online&#xff1b;错误:0x80072ee7 问题情况解决方法详细过程 问题情况 C:\Users\17569>wsl --list --online 错误: 0x80072ee7 解决方法 开系统代理&#xff0c;到外网即可修复&#xff01;&#xff01;&#xff01;&#xff01;&#x…

Yolov8项目实践——基于yolov8与OpenCV实现目标物体运动热力图

概述 在数据驱动和定位的世界中&#xff0c;对数据进行解释、可视化和决策的能力变得日益重要。这表明&#xff0c;使用正确的工具和技术可能是项目成功的关键。在计算机视觉领域&#xff0c;存在许多技术来解释从视频&#xff08;包括录像、流媒体或实时视频&#xff09;中获…

「 网络安全常用术语解读 」软件成分分析SCA详解:从发展背景到技术原理再到业界常用检测工具推荐

软件成分分析&#xff08;Software Composition Analysis&#xff0c;SCA&#xff09;是一种用于识别和分析软件内部组件及其关系的技术&#xff0c;旨在帮助开发人员更好地了解和管理其软件的构建过程&#xff0c;同时可帮助安全人员揭秘软件内部结构的神秘面纱。SCA技术的发展…

递归、搜索与回溯算法:回溯,决策树

回溯算法是⼀种经典的递归算法&#xff0c;通常⽤于解决组合问题、排列问题和搜索问题等。 回溯算法的基本思想&#xff1a;从⼀个初始状态开始&#xff0c;按照⼀定的规则向前搜索&#xff0c;当搜索到某个状态⽆法前进时&#xff0c;回退到前⼀个状态&#xff0c;再按照其他…

【计算机组成原理】运算方法和运算器

数据与文字的表示方法 1. 数据格式1.1 定点数表示方法1.1.1 定点小数1.1.2 定点整数 1.2 浮点数表示方法1.2.1 浮点数表示1.2.2 浮点数的规格化1.2.2.1 尾数为原码表示的规格化1.2.2.2 尾数为补码表示的规格化 1.2.3 IEEE754标准⭐ 1.3 十进制数串的表示方法1.3.1 字符串形式1.…

网盘——私聊

在私聊这个功能实现中&#xff0c;具体步骤如下&#xff1a; 1、实现步骤&#xff1a; A、客户端A发送私聊信息请求&#xff08;发送的信息包括双方的用户名&#xff0c;聊天信息&#xff09; B、如果双方在线则直接转发给B&#xff0c;不在线则回复私聊失败&#xff0c;对方…

ProgressFlowmon的confluence接口存在任意命令执行漏洞(CVE-2024-2389)

声明&#xff1a; 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 简介 ProgressFlowmon是一整套用于网络映射、应用程序性能…

客户端动态降级系统

本文字数&#xff1a;4576字 预计阅读时间&#xff1a;20分钟 01 背景 无论是iOS还是Android系统的设备&#xff0c;在线上运行时受硬件、网络环境、代码质量等多方面因素影响&#xff0c;可能会导致性能问题&#xff0c;这一类问题有些在开发阶段是发现不了的。如何在线上始终…

大气的免费wordpress模板

国产的wordpress模板&#xff0c;更适合中国人使用习惯&#xff0c;更符合中国老板的审美的大气wordpress企业官网建站模板。 WordPress模板&#xff0c;也称为主题&#xff0c;是用于定义WordPress网站或博客外观和功能的预设计文件集。这些模板使用HTML、CSS和PHP代码构建&a…

上传文件到HDFS

1.创建文件夹 hdfs -dfs -mkdir -p /opt/mydoc 2.查看创建的文件夹 hdfs -dfs -ls /opt 注意改文件夹是创建在hdfs中的&#xff0c;不是本地&#xff0c;查看本地/opt&#xff0c;并没有该文件夹。 3.上传文件 hdfs dfs -put -f file:///usr/local/testspark.txt hdfs://m…

【深度学习】Vision Transformer

一、Vision Transformer Vision Transformer (ViT)将Transformer应用在了CV领域。在学习它之前&#xff0c;需要了解ResNet、LayerNorm、Multi-Head Self-Attention。 ViT的结构图如下&#xff1a; 如图所示&#xff0c;ViT主要包括Embedding、Encoder、Head三大部分。Class …

Docker in Docker的原理与实战

Docker in Docker&#xff08;简称DinD&#xff09;是一种在Docker容器内部运行另一个Docker实例的技术。这种技术允许用户在一个隔离的Docker容器中创建、管理和运行其他Docker容器&#xff0c;从而提供了更灵活和可控的部署选项。以下是DinD的主要特点&#xff1a; 隔离性&am…

力扣打卡第一天

101. 对称二叉树 C&#xff1a; class Solution { public:bool isSymmetric(TreeNode* root) {return check(root->left,root->right);}bool check(TreeNode *p,TreeNode *q){ /**定义check方法用来检查两棵树是否是镜像的*/if (!p && !q) return true; /* 如…
最新文章