指针专题(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.mzph.cn/diannao/864.shtml

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

相关文章

Linux 基于 TCP 协议的简单服务器-客户端应用

目录 一、相关函数 1、listen() 2、accept() 3、connect() 4、两种IP地址转换方式 5、TCP和UDP数据发送和接收函数对比 5、log.hpp自定义记录日志 二、udp_server.hpp单进程版本 三、tcp_server.cc 四、Telnet客户端&#xff08;代替tcp_client.cc&#xff09; 五…

ColBERT和ColBERTv2:兼具Bi-encoder和cross-encoder优势的多向量排序模型

文章目录 简介ColBERTColBert 原理ColBERT如何训练ColBERT 如何使用离线索引用ColBERT 实现top-k Re-ranking用ColBERT 实现top-k 端到端的检索 ColBERTv2ColBERTv2原理SupervisionRepresentation IndexingRetrieval 总结参考资料 简介 ColBERT是一种多向量排序模型&#xff0…

数据分析案例-中国黄金股票市场的EDA与价格预测

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

Redis的Stream 和 实现队列的方式【List、SortedSet、发布订阅、Stream、Java】

Redis队列与Stream、Redis 6多线程详解 Redis队列与StreamStream总述常用操作命令生产端消费端单消费者消费组消息消费 Redis队列几种实现的总结基于List的 LPUSHBRPOP 的实现基于Sorted-Set的实现PUB/SUB&#xff0c;订阅/发布模式基于Stream类型的实现与Java的集成 消息队列问…

算法打卡day39

今日任务&#xff1a; 1&#xff09;卡码网57. 爬楼梯&#xff08;70. 爬楼梯进阶版&#xff09; 2&#xff09;322.零钱兑换 3&#xff09;279.完全平方数 4&#xff09;复习day14 卡码网57. 爬楼梯&#xff08;70. 爬楼梯进阶版&#xff09; 题目链接&#xff1a;57. 爬楼梯…

ipv4Bypass:一款基于IPv6实现的IPv4安全绕过与渗透测试工具

关于ipv4Bypass ipv4Bypass是一款基于IPv6实现的安全绕过与渗透测试工具&#xff0c;该工具专为红队研究人员设计&#xff0c;可以帮助广大研究人员通过IPv6绕过目标安全策略&#xff0c;以此来检测安全检测机制的健壮性。 20世纪90年代是互联网爆炸性发展时期&#xff0c;随着…

Llama 3王者归来,可与GPT-4分庭抗礼,开源模型即将追上闭源模型了?

“有史以来最强大的开源大模型”Llama 3引爆AI圈&#xff0c;马斯克点赞&#xff0c;英伟达高级科学家Jim Fan直言&#xff0c;Llama 3将成为AI大模型发展历程的“分水岭”&#xff0c;AI顶尖专家吴恩达称Llama3是他收到的最好的礼物。 4月18日&#xff0c;AI圈再迎重磅消息&a…

写一个uniapp的登录注册页面

目录 一、效果图 二、代码 1、登录 &#xff08;1&#xff09;页面布局代码 &#xff08;2&#xff09;逻辑实现代码 &#xff08;3&#xff09;css样式 2、注册 &#xff08;1&#xff09;页面布局代码 &#xff08;2&#xff09;逻辑实现代码 &#xff08;3&#x…

一个完全用rust写的开源操作系统-Starry

1. Starry Starry是2023年全国大学生计算机系统能力大赛操作系统设计赛-内核实现赛的二等奖作品。Starry是在组件化OS的arceos的基础上&#xff0c;进行二次开发的操作系统内核&#xff0c;使用宏内核架构&#xff0c;能够运行Linux应用的内核。 原始的操作系统大赛的仓库为 …

51-42 NÜWA:女娲,统一的多模态预训练模型

21年11月&#xff0c;微软、北大联合发布了NUWA模型&#xff0c;一个统一的多模态预训练模型&#xff0c;在 8 个下游任务上效果惊艳。目前该项目已经发展成为一系列工作&#xff0c;而且都公开了源代码。 Abstract 本文提出了一种统一的多模态预训练模型N̈UWA&#xff0c;该…

【精简改造版】大型多人在线游戏BrowserQuest服务器Golang框架解析(1)——功能清单

1.匿名登录 2.服务连接 3.新手引导 4.随机出生点 5.界面布局 6.玩法帮助 7.NPC会话 8.成就系统 9.成就达成 10.用户聊天 11.战斗&信息展示 12.药水使用 13.副本传送 14.玩家死亡 15.超时断开

实验:使用FTP+yum实现自制yum仓库

实验准备 FTP服务器端&#xff1a;centos-1&#xff08;IP:10.9.25.33&#xff09; 客户端&#xff1a;centos-2 两台机器保证网络畅通&#xff0c;原yum仓库可用&#xff0c;已关闭防火墙和selinux FTP服务器端 ①安装vsftpd并运行&#xff0c;设定开机自启动 安装vsftpd…

金融数字化能力成熟度指引

1 范围 本文件提出了金融数字化能力成熟度模型、成熟度计算方法&#xff0c;明确了不同维度金融数字化转型能力 相应的分档要求。 本文件适用于金融机构衡量金融科技应用和数字化转型发展水平&#xff0c;检视自身数字化发展优势与短板&#xff0c; 加快数字化转型&#xff0c…

金蝶云星空和金蝶云星空单据接口对接

金蝶云星空和金蝶云星空单据接口对接 来源系统:金蝶云星空 金蝶K/3Cloud结合当今先进管理理论和数十万家国内客户最佳应用实践&#xff0c;面向事业部制、多地点、多工厂等运营协同与管控型企业及集团公司&#xff0c;提供一个通用的ERP服务平台。K/3Cloud支持的协同应用包括但…

Linux Makefile

1.开发背景 linux 下编译程序需要用到对应的 Makefile&#xff0c;用于编译应用程序。 2.开发需求 编写 Makefile 编译应用程序 1&#xff09;支持多个源文件 2&#xff09;支持多个头文件 3&#xff09;支持只编译修改的文件&#xff0c;包括源文件和头文件 4&#xff09;支持…

Web程序设计-实验03 JavaScript语言基础

题目 【实验主题】 素数问题求解。计算&#xff08;判断&#xff09; 1~100中哪些是素数、哪些是合数。 素数也称为质数&#xff0c;是只能被1及其自身整除的自然数。与素数相对应的是合数&#xff0c;合数可以被分解为若干个素数的乘积&#xff0c;这些素数称为这个合数的质…

数据结构从入门到实战——顺序表的应用

目录 一、基于动态顺序表实现通讯录 二、代码实现 2.1 通讯录的初始化 2.2 通讯录的销毁 2.3 通讯录的展示 2.4 通讯录添加联系人信息 2.5 通讯录删除联系人信息 2.6 通讯录修改联系人信息 2.7 通讯录的查找联系人信息 2.8 将通讯录中联系人信息保存到文件中 2.9…

【Windows10】Anaconda3安装+pytorch+tensorflow+pycharm

文章目录 一、下载anaconda0.双击下载的文件1. 选择All users2. 安装路径3. 勾选环境变量和安装python4.安装完成5.添加环境变量6.测试是否安装成功 二、安装pytorch&#xff08;先看四&#xff01;先检查一下自己电脑是不是只能安装GPU版的1.查看conda图形化界面2.在安装pytor…

【图解计算机网络】网络协议分层解析

网络协议分层解析 网络协议分层应用层传输层网络层数据链路层 TCP/IP分层模型通讯示例 网络协议分层 网络协议分层一共有OSI七层网络协议&#xff0c;TCP/IP四层网络网络协议&#xff0c;还有五层网络协议。 七层由于分层太多过于复杂&#xff0c;实际应用中并没有使用&#x…

代码编辑工具PilotEditPro18.4版本在Windows系统的下载与安装配置

目录 前言一、PilotEdit Pro安装二、使用配置总结 前言 “ PilotEdit Pro是一个功能强大且功能丰富的文本和代码编辑器&#xff0c;可满足程序员、开发人员和IT专业人员的不同需求。定位为一个多功能的编辑解决方案&#xff0c;PilotEdit Pro以其对广泛的文本和代码文件格式的…