C语言之指针初阶


目录

前言

一、内存与地址的关系

二、指针变量

三、野指针

四、const

五、传值调用与传址调用

总结


前言

        本文主要介绍C语言指针的一些基础知识,为后面深入理解指针打下基础,因此本文内容主要包括内存与地址的关系,指针的基本语法,指针运算,野指针,还有const修饰指针和assert断言的使用,最后还会讲到指针的传址调用,希望对大家有所帮助。


一、内存与地址的关系

指针作为C语言的核心知识,那么指针究竟是什么呢?

  1. 首先指针其实就是地址,而地址是内存中一个个内存单元的编号
  2. 我们知道计算机上CPU(中央处理器)在处理数据的时候,需要的数据是在内存中读取的,处理后的数据也会放回内存中,那我们买电脑的时候,电脑上内存有8GB/16GB/32GB等,这些内存就是程序运行时需要用到的内存
  3. 为了更高效的管理与使用这些内存,于是就将这些内存分为一个个内存单元,每个内存单元的大小取一个字节,也就是8个比特位,⼀个比特位可以存储一个2进制的位1或者0
  4. 每个内存单元也都有一个编号,有了这个内存单元的编号,CPU就可以快速找到一个内存空间,在计算机中我们把内存单元的编号也称为地址。C语言中给地址起了新的名字:指针
  5. 所以我们可以这样理解:内存单元的编号 == 地址 == 指针

如图:


二、指针变量

指针变量就是储存地址的变量

1. 取地址操作符  &

&操作符是一个单目操作符,用来取出一个变量的地址

比如,创建 int a,观察其地址

图中画红线的部分就为a变量在内存中的地址以及储存的数据,即0x004FF82C为地址,0a 00 00 00为以16进制储存的数据,其中 0a 表示的就是以16进制保存的十进制数字10,因为一个16进制数需要4个比特位表示,0a就是两个16进制数,占了8个比特位刚好为一个字节,而0a 00 00 00 四个这样的就刚好表示4个字节,至于为什么0a在前面,这是编译器自己的规则


2.指针变量创建

对于一般的指针变量的创建

(类型) * 变量名;

这样就将变量 a ,b 的地址存储在对应类型的指针变量里面,其余的如float、double等可以以此类推

注意:这里的*号表示其变量为指针变量,它是与变量名结合,前面的类型是指针变量的类型,它与指针访问地址时的权限大小有关(后面有讲)


3.解引用操作符  *

* 解引用操作符为一个单目操作符,它可以通过地址找到其对应的数据

因为指针变量存储的就是地址,所以指针变量搭配*就可以找到其对应的数据进行操作

如:

我们可以通过解引用操作符修改指针对应的变量数据


4.指针变量类型的意义

指针变量的大小和类型无关,只要是指针变量,在同一个平台下,大小都是一样的,比如在32位平台上指针类型的变量大小都是4个字节,64位平台上为8个字节(以下在32位上演示)

既然指针变量的大小和类型无关,那么指针变量的类型有什么意义呢?

其实,这个意义非常重要:指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)。比如: char* 的指针解引用就只能访问⼀个字节,而 int* 的指针的解引用就能访问四个字节,这个权限的大小就是指针变量类型的大小

如:我们再次创建一个变量a。(注:程序每次运行时分配的地址不一样)

除了a变量的地址不一样,其他和上面一样为 0a 00 00 00,它表示的是10,并且每两位表示一个字节,而一个字节表示一个内存单元,因此如上的0x0099FC8C其实表示的是储存0a的地址,我们可以一列一列的观察其内存

因为a为整形变量,占4个字节,因此其在内存中为4个连续的内存单元,如上标记的区域,此时如果我们创建一个整形指针变量接收a的地址,这时候指针变量接收的其实是a在内存中的连续区域的首地址,我们解引用该指针就可以操作这四个字节

注:指针存储一个大于1字节的数据时,存储的是该数据在内存中的首地址

如:

注:90 01 00 00 在读取时是以 00000190,也就是190,为16进制

十进制刚好为400

此时变量a可以被正常修改,而如果我们以字符类型的指针接收a的地址后,我们一次只能修改一个字节

如:

16进制28等于十进制40,如上我们貌似也能正常修改整形变量a的数组,但实际上只要我们修改的数值大于两位16进制能表达的最大数字,就不能正常修改a的数值

如:

我们只能改变一个字节,也就是char类型的指针一次只能修改一个字节

这就是指针类型的意义,当然不止如此,指针变量的类型还决定了指针加减整数的时候一次跳过多少字节,下面就会讲到


5.指针 + - 整数

先说结论:指针加减整数,会使指针前进或后退n个字节,而指针的类型决定了指针向前或者向后走一步有多大(距离)

也就是说,指针类型决定了指针加减1时的步长,比如char*指针,它一次只能跳过一个字节,它加减n也就是向前或向后跳过n*sizeof(char)个字节

比如:

(注:此处int *parr = arr不能写成&arr,下篇指针进阶文章我会讲到) 

此处我们就利用了循环来使数组首元素地址依次跳过 i个int类型大小的字节,实现了循环打印数组元素

此处我们有几处需要注意的点:

  1. 数组的元素在内存中是连续存放的,并且地址由低到高,不了解的可以参考我主页的数组文章
  2. 此处我们发现,如果把 *(arr+i) 换成 arr[ i ] 也就是我们之前的写法达到的效果是一样的,这是因为 *(arr+i) 是完全等价于 arr[ i ] ,也就是说,当编译器遇到 arr[ i ] 时会把它解读为 *(arr+i),按这样理解,因为 arr+i 等于 i+arr ,也就是可以写成 *(i+arr),进而可以写成 i[arr] ,我们可以验证一下
  3. 答案是完全可以的,但是平时不建议这样写,因为 i[arr] 可读性不如 arr[ i ]。总结就是 [ ] 操作符其实也是解引用的效果,只不过多了加法的作用


6.指针 - 指针

对于指针 - 指针这个运算来说,只有两指针指向的是同一块连续的内存区域时才有意义

我们可以通过指针 - 指针来计算数组两元素地址之间有多少个元素

如:

那么为什么是9个而不是10个呢?

我们可以画图来理解:

画图我们就可以很直观的感受到,arr[9] 的元素没有被计算到,因为如果指针指向的数据大小大于一个字节,那么指针储存的该数据地址为该数据储存在内存中的首地址,参考上文中的 int a 变量的地址观察


三、野指针

1.概念

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。

野指针成因:

  1. 指针未初始化:主要是创建在函数中的指针变量没有进行初始化,造成指针指向的地址是随机值,此时指针指向的地址随机,不能对其进行解引用。
  2. 指针越界访问:这种主要出现在数组中,指针指向的地址超出了数组所在的内存区域,指向了一个不确定的地址
  3. 指针指向的空间被释放:这种主要发生在指针变量指向的地址是已经被释放的内存空间地址,被释放的空间不属于该程序,虽然可能引用不会导致报错,但是不安全


2.如何规避野指针

野指针的危害有:访问违规、数据损坏、内存泄露、安全风险等。

野指针的危害众多、因此我们的代码中需要规避野指针,那么如何规避野指针呢?

  1. 指针变量初始化时如果没有需要赋值的地址就先赋值为NULL
  2. 指针变量不再使用时,及时置NULL,指针使用之前检查有效性:当指针变量指向一块区域的时候,我们可以通过指针访问该区域,后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问, 同时使用指针之前可以判断指针是否为NULL。
  3. 避免返回局部变量的地址,比如避免函数返回局部变量地址

除了以上的方法,还有一个常用的方法:

assert 断言

assert.h 头文件定义了宏 assert() ,用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”

比如:assert (p != NULL);

上面代码在程序运行到这一行语句时,验证变量 p 是否等于 NULL 。如果确实不等于NULL 继续运行,否则就会终止运行,并且给出报错信息提示。

程序 assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值非零), assert()不会产生任何作用,程序继续运行。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误流 stderr 中写入一条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号。

如:

assert() 的使用对程序员是非常友好的,使用 assert() 有几个好处:它不仅能自动标识文件和出问题的行号,还有一种无需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问 题,不需要再做断言,就在 #include <assert.h>语句的前面,定义一个宏 NDEBUG

如下assert()就会失去作用:

如果想再次启用assert()只需要注释掉第一行的宏就行

assert() 的缺点:因为引入了额外的检查,增加了程序的运行时间。 一般我们可以在 Debug 中使用,在 Release 版本中选择禁用 assert 就行,在 VS 这样的集成开发环境中,Release 版本中,assert()直接就是自动优化掉了。这样在debug版本写有利于程序员排查问题,在Release 版本不影响用户使用时程序的效率。


四、const

const的作用:被const修饰的变量不能被直接修改

如:

程序在还未运行时已经发出错误警告

虽然不能直接修改,但是还能通过指针变量间接修改:


但是如果const修饰的是指针变量,就分以下两种情况:

  • const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本身的内容可变。
  • const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

如:


五、传值调用与传址调用

传值调用与传址调用,这个主要针对的是自定义函数的参数,也就是说:

  1. 函数参数为非指针类型,调用时传入非指针类型参数,即为传值调用
  2. 函数参数为指针类型,调用时传给函数地址,即为传址调用

那么这两个有什么区别呢?

其实主要是传值调用时,在函数内部修改形参不会影响实参,而在传址调用时,修改形参也同样会会修改实参

比如这个例题:编写一个函数,交换两个整形变量的内容

此前我们在主函数中只需要再创建一个变量,通过三者交换即可达成题目这样的效果,但如果我们在自定义函数里面,函数参数为两个整形变量,分别接收需要交换内容的两个实参,使用一样的方法是达不到一样的效果的,这时候我们只需要使用传址调用即可

如:

通过传给函数实参的地址,在函数中用指针变量的形参接收,就可以在函数中解引用该指针变量来修改对应的实参变量的内容

这就是传值调用与传址调用的不同

另外,如果函数的参数为数组类型,其实也是指针变量,给函数传参时,一般传入的就是数组名,因为数组名就是数组首元素的地址,至于详细原因我会在指针进阶中讲到


总结

        以上就是本文的全部内容,希望对大家有所帮助,下一篇我会继续写指针的进阶篇,感谢大家的支持

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

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

相关文章

LiveGBS流媒体平台GB/T28181用户手册-服务器概览:通道信息、负载信息、CPU使用、存储使用、带宽使用(Mbps)、内存使用

LiveGBS用户手册-服务器概览&#xff1a;通道信息、负载信息、CPU使用、存储使用、带宽使用&#xff08;Mbps&#xff09;、内存使用 1、服务器概览1.1、通道信息1.2、负载信息1.2.1、信息说明1.2.2、会话列表 1.3、CPU使用1.4、存储使用1.5、带宽使用&#xff08;Mbps&#xf…

视频下载器 - 网页视频自动嗅探2.2.4

【应用名称】&#xff1a;视频下载器 - 网页视频自动嗅探 【适用平台】&#xff1a;#Android 【软件标签】&#xff1a;#Video #Downloader 【应用版本】&#xff1a;2.2.4 【应用大小】&#xff1a;33MB 【软件说明】&#xff1a;软件升级更新。支持多种格式的看片神器&am…

java入门详细教程之集合的理解与应用

一、Collenction集合 数组和集合的区别 长度 数组的长度是不可变的,集合的长度是可变的 数据类型 数组可以存基本数据类型和引用数据类型 集合只能存引用数据类型,如果要存基本数据类型,需要存对应的包装类 Collection 集合概述和使用 Collection集合概述​&#xff1a; 是单…

MacOS下载安装JDK8

一、前言 今天给苹果电脑安装JDK环境&#xff0c;后续打算把Mac系统也用起来&#xff0c;也体验一把用苹果系统开发。 JDK就不过多介绍了&#xff0c;大家都是JAVA开发&#xff0c;JDK就是JAVA开发的必要环境。目前已经更新到JDK20了&#xff0c;不过我是不会更新的&#xff0…

微服务中的鉴权怎么做?

大家好&#xff0c;我是苍何呀。 现在出去找工作&#xff0c;简历上不写上微服务的技术&#xff0c;仿佛自己跟不上时代了&#xff0c;面试官更是喜欢盯着微服务项目来提问。 但其实虽说微服务是主流&#xff0c;随着云原生架构的发展&#xff0c;微服务也是趋势&#xff0c;…

DOS学习-目录与文件应用操作经典案例-dir

欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一.前言 二.使用 三.练习 一.前言 dir是"directory"&#xff08;目录&#xff09;的缩写&#xff0c;它主要用于展示某个磁盘上的全部或特定文件目录。在DOS操作系统中&#…

ES6中数组新增了哪些扩展?

​&#x1f308;个人主页&#xff1a;前端青山 &#x1f525;系列专栏&#xff1a;JavaScript篇 &#x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来JavaScript篇专栏内容:ES6中数组新增了哪些扩展&#xff1f; 目录 一、扩展运算符的应用 二、构造函数新…

Web3与物联网:构建智能连接的数字世界

引言 随着互联网的不断发展&#xff0c;物联网&#xff08;Internet of Things, IoT&#xff09;作为一种新兴的信息技术&#xff0c;正在逐渐渗透到我们的生活和工作中。而随着Web3的兴起&#xff0c;物联网将迎来新的发展机遇。本文将探讨Web3与物联网的结合&#xff0c;如何…

全面解析防静电措施:保障工业安全,预防静电危害

静电是一种常见的物理现象&#xff0c;由于电荷的不平衡而产生。在特定的环境中&#xff0c;静电可能会带来危害&#xff0c;如损坏电子设备、引起火灾等。因此&#xff0c;采取适当的防静电措施是非常重要的。以下是一些常见的防静电方法&#xff1a; 增加环境湿度&#xff1a…

崆峒酥饼:佳节馈赠的美味之选

崆峒酥饼&#xff1a;佳节馈赠的美味之选 在即将到来的端午节&#xff0c;人们开始忙碌地准备着走亲访友的礼物。而崆峒酥饼&#xff0c;作为一种传统的美食&#xff0c;不仅是节日里的美味享受&#xff0c;更是传递情谊的佳品。 崆峒酥饼&#xff0c;以其酥脆的口感和独特的风…

使用httpx异步获取高校招生信息:一步到位的代理配置教程

概述 随着2024年中国高考的临近&#xff0c;考生和家长对高校招生信息的需求日益增加。了解各高校的专业、课程设置和录取标准对于高考志愿填报至关重要。通过爬虫技术&#xff0c;可以高效地从各高校官网获取这些关键信息。然而&#xff0c;面对大量的请求和反爬机制的挑战&a…

【Python】语句与众所周知【自我维护版】

各位大佬好 &#xff0c;这里是阿川的博客 &#xff0c; 祝您变得更强 个人主页&#xff1a;在线OJ的阿川 大佬的支持和鼓励&#xff0c;将是我成长路上最大的动力 阿川水平有限&#xff0c;如有错误&#xff0c;欢迎大佬指正 本篇博客是在之前的基础上进行的维护 目录 条…

HP6V18、HP6V65电比例功率控制泵放大器

HP6V18、HP6V65电比例压力负控制&#xff0c;电比例功率控制泵通过由BEUEC比例放大器控制改变阀的输入电流&#xff0c;将泵设置为一定压力。改变负载压力&#xff0c;为使调订压力恒定&#xff0c;泵摆角会增大或减小&#xff0c;从而改变流量。因此泵只能输出执行器可以接受的…

ubuntu下gcc编译器的安装

.gcc编译器的安装 一般linux下是覆盖含有的&#xff0c;如果没有执行更新命令 sudo apt update gcc安装成功&#xff0c;可以检查一下版本 可以看出我的gcc是9.4.0版本的

地表最强ChatGPT爆了!我来告诉你,它都有什么用

OpenAI刚刚发布了全新的 "GPT-4o"&#xff0c;它不仅可以通过语音、视觉和文本进行推理&#xff0c;还在速度和价格上有了巨大的突破。它的速度提高了2倍&#xff0c;价格却降低了50%&#xff0c;而且生成速率比GPT-4 Turbo高出5倍。最令人惊喜的是&#xff0c;它将对…

Verilog基础语法——条件语句if-else与case

Verilog基础语法——条件语句case、if-else 写在前面一、if-else语句二、case语句2.1 case语句2.2 casez语句2.3 casex语句 写在后面 写在前面 在Verilog语法中&#xff0c;常用的条件语句有if-else语句和case语句&#xff0c;用于判断条件是否为真&#xff0c;并执行判断条件后…

【NLP】文本分类

n-gram 的局限性 n-gram 只能对于填空这样的通顺性问题做出推测&#xff0c;但是没有办法完全解决句子的语义问题&#xff0c;从而无法实现文本的分类 文本的分类&#xff0c;就是将文本在语义的理解下划分到特定的主题下 手工规则 如一些垃圾过滤系统&#xff0c;需要人工制…

PHP开发中的不安全反序列化

序列化是开发语言中将某个对象转换为一串字节流的过程&#xff0c;转换后的字节流可以方便存储在数据库中&#xff0c;也可以方便在网络中进行传输。而反序列化则是将数据库取出的字节流或从网络上接收到的字节流反向转换为对象的过程。概念虽如此&#xff0c;但不同的开发语言…

【JavaEE 初阶(七)】网络原理 TCP与UDP协议

❣博主主页: 33的博客❣ ▶️文章专栏分类:JavaEE◀️ &#x1f69a;我的代码仓库: 33的代码仓库&#x1f69a; &#x1faf5;&#x1faf5;&#x1faf5;关注我带你了解更多网络知识 目录 1.前言2.应用层2.1xml2.2json 3.传输层3.1UDP协议3.2TCP协议3.2.1确认响应3.2.2超时重…

【c++】map和set的封装

1.红黑树源码 我们使用上节课的红黑树源码来封装map和set. 因为map存的是&#xff08;key,value&#xff09;,set存的是&#xff08;key&#xff09;,为了我们set和map使用同一个类模板&#xff08;红黑树&#xff09;&#xff0c;所以我们先要修改红黑树结点中存的数据类型&a…