深入理解指针(二)
目录
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]就是整型⼀维数组中的元素。
上述的代码模拟出⼆维数组的效果,实际上并非完全是⼆维数组,因为每一行并非是连续的。
相关文章:

深入理解指针(二)
目录 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[…...

【Qt 学习笔记】Qt窗口 | 标准对话框 | 文件对话框QFileDialog
博客主页:Duck Bro 博客主页系列专栏:Qt 专栏关注博主,后期持续更新系列文章如果有错误感谢请大家批评指出,及时修改感谢大家点赞👍收藏⭐评论✍ Qt窗口 | 标准对话框 | 文件对话框QFileDialog 文章编号:Q…...

换卡槽=停机?新手机号使用指南!
刚办理的手机号莫名其妙的就被停用了?这到底是怎么回事?这篇文章快来学习一下吧。 先说一下,你的手机为什么被停机? 现在运营商对于手机卡的使用有着非常严格的要求,尤其是刚办理的新号码,更是“严上加…...
主题切换之根元素CSS自定义类
要实现CSS样式的主题切换,可以通过在HTML中添加一个按钮来触发JavaScript事件,进而通过JavaScript动态修改HTML元素的class或直接切换CSS文件,以达到改变页面整体风格的目的。以下是实现这一功能的步骤、原理及代码示例。 原理: …...

如何在 ASP.NET Core Web Api 项目中应用 NLog 写日志?
前言 昨天分享了在 .NET Core Console 项目中应用 NLog 写日志的详细例子,有几位小伙伴私信说 ASP.NET Core Web Api 项目中无法使用,其实在 ASP.NET Core Web Api 项目中应用 NLog 写日志,跟 .NET Core Console 项目是有些不一样的…...
selenium execute_script常用方法汇总
driver.execute_script() 是 Selenium WebDriver 中非常强大且灵活的功能,可以用来执行任意的 JavaScript 代码在浏览器上下文中。以下是一些常用的 execute_script() 方法的例子和用法: 修改元素的属性和值 python# 修改输入框的值 driver.execute_sc…...

如何选择最佳的APP封装平台-小猪APP分发为您解忧
在开发移动应用程序的过程中,选择一个可靠的APP封装平台显得尤为重要。无论你是初创企业还是大型企业,找到一个合适的平台可以大大简化你的开发流程。如何选择最佳的APP封装平台呢?今天我们就来聊聊这个话题,并重点介绍一下小猪AP…...

Linux基础 (十八):Libevent 库的安装与使用
目录 一、Libevent 概述 1.0 Libevent的安装 1.0.1 使用源码方式 1.0.2 终端命令行安装 1.1 主要特性 1.2 主要组件 1.3 Libevent 使用模型 1.4 原理 1.5 使用的基本步骤 1.5.1 初始化事件基础设施 1.5.2. 创建和绑定服务器套接字 1.5.3. 设置监听事件 1.5.4. 定义…...
冒泡排序的详细介绍 , 以及c , python , Java的实现方法
冒泡排序(Bubble Sort)是一种简单的排序算法,它重复地遍历要排序的数列,一次比较两个元素,如果他们的顺序错误就把他们交换过来。遍历数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成…...

使用llama.cpp实现LLM大模型的格式转换、量化、推理、部署
使用llama.cpp实现LLM大模型的格式转换、量化、推理、部署 概述 llama.cpp的主要目标是能够在各种硬件上实现LLM推理,只需最少的设置,并提供最先进的性能。提供1.5位、2位、3位、4位、5位、6位和8位整数量化,以加快推理速度并减少内存使用。…...

给你一个扫码支付的二维码,如何写测试用例?
前言 面试的时候,经常会临场出题:给你一个xxx, 如何测试, 或者说如何写测试用例?xxx可以是圆珠笔,水杯,电梯等生活中常见的场景。 那么给你一个支付的二维码,如何写测试用例呢? 二维码扫码支…...
计算机专业在未来的发展与抉择
目录 前言 计算机专业的发展历史 计算机专业的前景 计算机专业的挑战 如何判断自己是否适合计算机专业 计算机行业的未来发展态势 作为过来人和从业者 前言 随着2024年高考落幕,数百万高三学生又将面临人生中的重要抉择:选择大学专业。在这个关键…...

【Linux】基础IO——文件描述符,重定向
话接上篇: 1.文件描述符fd 磁盘文件 VS 内存文件? 当文件存储在磁盘当中时,我们将其称之为磁盘文件,而当磁盘文件被加载到内存当中后,我们将加载到内存当中的文件称之为内存文件。磁盘文件和内存文件之间的关系就像程…...

1.0 Android中Activity的基础知识
一:Activity的定义 Activity是一个应用组件,它提供了一个用户界面,允许用户执行一个单一的、明确的操作,用户看的见的操作都是在activity中执行的。Activity的实现需要在manifest中进行定义,不让会造成程序报错。 1.…...

线代知识点总结
目录 一.初等行/列变换 1.计算行列式时,行列变换都可 2.求矩阵的秩时,行列变换都可 3.解线性方程组时,仅能使用初等行变换 4.判定解的情况,单纯求r(A),r(A,b)的过程行列变换都可 5.求向量组极大无关组、线性表出关系&#x…...

案例学习-存量更新规划实施探索(武汉)
案例学习-存量更新规划实施探索(武汉) 武汉市在早期旧城更新实践中发现零散化的更新往往导致资源配置分散、城市建设破碎化等弊病,特别是由于过于强调项目自身“经济平衡”,在实施过程中也逐步暴露出住宅占比过大、强度偏高、公服…...

C#操作MySQL从入门到精通(17)——使用联结
前言: 我们在查询数据的过程中有时候查询的数据不是来自一个表而是来自多个表,本文使用的测试数据如下: 本文使用了两个表student_info、address_info student_info的数据如下: address_info的数据如下: 1、内联结 所谓内联结就是求交集,两个表都有的数据才是有效数…...

MyBatis 关于查询语句上配置的详细内容
1. MyBatis 关于查询语句上配置的详细内容 文章目录 1. MyBatis 关于查询语句上配置的详细内容2. 准备工作3. SQL查询结果,返回为POJO实体类型4. SQL查询结果,返回为List<POJO\> 集合类型5. SQL查询结果,返回为Map 集合6. SQL查询结果&…...

基于STM32和人工智能的智能家居监控系统
目录 引言环境准备智能家居监控系统基础代码实现:实现智能家居监控系统 4.1 数据采集模块4.2 数据处理与分析4.3 控制系统4.4 用户界面与数据可视化应用场景:智能家居管理与优化问题解决方案与优化收尾与总结 1. 引言 随着智能家居技术的快速发展&…...

这三款使用的视频、图片设计工具,提供工作效率
Videograp Videograp是一款专注于视频生成的工具,特别适合需要快速剪辑和编辑视频的用户。Videograp具备以下特点: 影音比例转换:Videograp支持调整视频的分辨率和比例,使其更适合不同的播放环境和设备。 AI快剪:该工…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...
在四层代理中还原真实客户端ngx_stream_realip_module
一、模块原理与价值 PROXY Protocol 回溯 第三方负载均衡(如 HAProxy、AWS NLB、阿里 SLB)发起上游连接时,将真实客户端 IP/Port 写入 PROXY Protocol v1/v2 头。Stream 层接收到头部后,ngx_stream_realip_module 从中提取原始信息…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...

Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...