指针 (三)

一 . 指针运算

在指针中有三种基本运算:

1 . 指针 + - (加减)整数

2 . 指针 - (减)指针

3 . 指针的关系运算

(1)指针 + -(加减)整数

前面讲提到过,因为数组在内存中是连续存放的,我们只要知道了第一个元素的地址,自然可以顺着推算出后面的所有元素

忘了数组概念的可以点击后方链接查看回顾:一维数组 和 关键字 sizeof-CSDN博客

那么指针 + - (加减)整数是什么呢,咱们直接实操理解:

此处演示指针 + 整数(指针 - 整数也是同理)

(2)指针 -(减)指针

指针 - 指针我们一般用来计算两个指针地址所相隔的元素个数,但使用前提是这两个指针必须指向的是同一块内存地址。这是为什么呢?大家可以设想一下,我们之所以能够使用指针的方式去遍历数组,本质就是因为数组中各元素在内存中是连续存放的,那么我们使用两个指向不通的指针相减,得到结果全看这两个数组在内存中的分配,这是无法预知的,也是没有意义的

在这里我们运用指针 - (减)指针的这一特性,可以模拟实现 strlen 函数对数组进行计数:

这个时候有小伙伴们就要问呢?指针可以加减整数,指针也可以减去指针,为什么唯独少了指针加指针呢?Good question ! 这是因为指针加指针是无意义的,所以这种算法并不被系统认可,文字说来可能显得有些许生硬,我给大家举一个例子:指针就相当于我们的日期,指针加减整数就相当于我们的日期加减天数,得到的还是一个日期(有意义),指针减指针就相当于我们的日期减去日期,得到的自然是两个日期之间相隔的天数(有意义),那我们的指针加指针就相当于日期加上日期(无意义),这就是毫无意义的,所以没有这种算法

(3)指针的关系运算

我们可以通过指针之间在内存中存放地址高低的大小关系,推算出数组中的各个元素,其原理跟上方的例子大同小异,咱们就不过多赘述了,直接上图理解吧

二 . 野指针

野指针的概念很好理解,就是说我们的指针指向的位置是不可预知的,是随机的,没有明确限制的,不正确的等等。造成野指针的情况有许多种:

(1)指针未初始化

如图,当我们的指针未初始化时,系统就会报错。严格来说,未初始化的指针没有任何限制,因该是默认为随机值的,如果我们此时将 p 中所存放的这个随机值当做地址,当我们对其解引用时,就会造成非法访问。所以这里系统直接报错,不能打印

(2)指针的越界访问

如图,我们设立数组时,只给它了 10 个元素空间的内存,我们通过指针指向的数组中的地址,一个元素一个元素地给其赋值,前 10 个都没有问题,但是我们设立 for 的条件是 “ i < 11 ”,这个时候我们的指针就成了野指针,并且还对下标为 10 的地址位置进行了访问,这就成了非法访问,就会报错,这一点我们可以调出监视窗口进行一步一步的观察:

此时访问前 10 个元素未出现任何异常,再往下进一步访问便会越界,形成野指针

(3)指针指向的空间释放

如图,这段代码的意义就是:我们先进入到 main 函数当中,创建一个指针 *p ,这个 *p 就是 test 的返回值,然后哦我们进入到 test 函数当中,创建一个变量 a 并给其赋值为 100 ,然后返回 a 的地址,再次回到 main 函数当中,打印 *p ,按理说我们应该是通过 *p 的地址找到 a ,最后打印出100,这看起来好像并没有任何问题,但是请大家注意, test 这里的 a 是一个局部变量,进函数创建,出函数销毁,我们并不能通过 test 的返回值打印出 a 

三 . 如何规避野指针

我们知道了也野指针的危害,那么我们怎么去规避野指针呢?正所谓有阴必有阳,我们上面列出了三种造成野指针的情况,我们自然而然就应该一 一对应寻求解决方法

(1)初始化指针:如果不知道指针应该指向哪里,我们可以给其赋值NULL

(NULL是C语言中定义的一个标识符常量,值为 0,0 也是地址,但这个地址是无法使用的,读写该地址时会报错)

(2)小心指针越界:程序向内存申请了哪些空间,我们的指针也就只能访问哪些空间,超出这个限定的空间范围就会造成越界访问

(3)指针在使用前,检查其有效性;当指针不再使用时,及时对其置 NULL

NULL的含义:当指针变量指向一块区域的时候,我们可以通过指针访问该区域,后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL。因为约定俗成的一个规则就是:只要是NULL指针就不去访问,同时使用指针之前可以判断指针是否为NULL。我们可以把野指针想象成野狗,野狗放任不管是非常危险的,所以我们可以找一棵树把野狗拴起来,就相对安全了,给指针变量及时赋值为NULL,其实就类似把野狗栓起来,就是把野指针暂时管理起来。不过野狗即使拴起来我们也要绕着走,不能去挑逗野狗,有点危险;对于指针也是,在使用之前,我们也要判断是否为NULL,看看是不是被拴起来起来的野狗,如果是不能直接使用,如果不是我们再去使用

四 . assert 断言

宏 assert 我们称之为断言,用于在运行时确保程序符合指定的条件,如果不符合,就报错终止运行。使用宏 assert 需要包含头文件 < assert . h >

使用格式如下:

如图,当程序运行到上面这行代码时,系统会验证变量 p 是否等于 NULL ,若不等于,则程序继续运行,否则将会终止程序运行,并且给出报错信息提示

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

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

这样的话,重新编译程序,编译器就会禁用程序中所有的assert()语句,如果程序又出现问题,可以移除掉这条#define NDEBUG 指令(或者注释掉),再次编译,这样就重新启用了assert()语句

assert()的缺点就是:引入了额外的检查,增加了程序的运行时间

一般我们可以在 Debug 版本中使用,在 Release 版本中选择禁用即可。在我们的VS这样比较严谨的IDE(集成开发环境)中,在 Release 版本中,直接就是优化掉了。这样在 Debug 版本中有利于程序员排查问题,而在使用 Release 版本时就不会影响用户使用程序的效率

OKK,有关指针的知识点的了解,今天就到此为止吧,咱们下期再见。与诸君共勉!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值