小白是如何学习指针的(二)

前言:

学习很苦,鸡汤来补!!

野指针

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

1.第一种野指针就是指针未初始化

站在计算机的角度上看,由于p中的值是随机值,我们对p解引用取它存储的地址的值是毫无意义甚至是错误的,因为计算机有些地址的位置是无法被我们访问的(至少C不行,其它的如汇编语言可以),这也是对我们计算机的保护,防止我们误改了重要的数据。

2.指针越界访问

思考以下代码

在上一节中,我们知道了p中存储了arr[0]位置的地址,并且每次p++都会跳过一个int类型大小的字节,按照我们程序执行,我们访问并改变了数组外的数据,这是错误的。

3.指针指向的空间释放

在我们解决这个问题前,我们先思考以下代码,了解一下空间释放的知识

我们写了一个交换数字的函数,但是我们在输出的时候发现它们的值压根没发生交换,这是为什么呢?在这里,我用调试的方法来看看哪里出了问题(不会调试的可以看看我之前的博客)

为了避免形参和实参名字一样而不好观察,我将形参改了个名儿

我们可以看到a和b的值是确确实实的进行了交换的(传进来的时候是a=3,b=0),但是为什么x和y就是不变呢?我们对这几个变量取地址,发现他们的地址都不一样,而我们知道,我们定义一个变量,其实就是给这个变量一个地址,然后我们通过操作这个变量来操作这个地址上的数据(这也是为什么我们定义变量时要给它一个类型,计算机通过这个类型来决定访问几字节空间)

那我们可以清楚看到我们对a和b进行了值的交换,但对于x和y没有影响,因此x和y不会交换大小,解决方法也很简单,我们只要传地址就ok了

我们在了解了变量和地址之间的关系后,我们来看看执行代码时栈中空间的变化情况(这里只简单的讲一下大致过程,对于其中的细节建议搜《函数栈帧的创建与销毁》,有很多博主都进行了详细的介绍)

我们在Debug的时候发现当我们没有进入到Exchange函数时

说明在进入Exchange函数前,我们并没有给z,a,b分配空间,事实上,还未给Exchange函数开辟空间,当我们走进Exchange函数时,我们才开始为它分配内存(main也是一个函数)

在进入函数后,当我们执行完函数中的代码,将要出函数时我们发现x和y的值似乎还没有改变

但当我们出函数时发现x和y的值进行了交换,这就很奇怪了,其实并不是没有改变,而是我们进入Exchange函数时就不能对x和y进行观察了

同理,当我们出了函数后会发现我们不能观察到z,a,b这几个变量了,总结函数创建过程:

我们还不能知道出函数后栈区的内存分配情况,这里无非两种情况,一是不销毁空间,二是销毁空间,站在计算机的角度上和从逻辑上看显然我们要销毁空间,不然如果我们有很多个函数,但空间是有限的,会导致空间泄露问题(递归函数多次递归就可能有这个报错),但是我们还是去证明一下(主要是训练计算机思维)。

当我们执行代码时发现,好像并没有销毁,我还是得到了n的值??再看一个代码

我们发现仅仅是改变了一行代码,它的值就变成了一个随机值,这是为什么呢?

首先我们要知道计算机即使销毁了一个空间,它并不会对这个空间存储的数据进行修改,也就是说在这个地址上的数据仍然保留着,只是不对它进行维护,等待后面的数据覆盖,这也就是数据可以恢复的原理。但我们新开辟了一个test的函数的空间时,test函数重新对这个空间利用时会改变一些值。这也就证明了出函数后会对函数进行销毁。

这也就是为什么我们通过p接受到的n的地址,但我们仍然认为它是野指针的原因。

assert 断言(在之后指针代码中很常用)

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

语法 :assert(表达式),如果表达式为真就继续执行下面代码

assert()的使用对程序员是非常友好的,使用assert()有几个好处:它不仅能自动标识文件和出问题的行号,还有一种无需更改代码就能开启或关闭assert()的机制。如果已经确认程序没有问题,不需要再做断言,就在#include <assert.h>语句的前面,定义一个宏NDEBUG。
然后,重新编译程序,编译器就会禁用文件中所有的assert()语句。如果程序又出现问题,可以移除这条'#define NDEBUG指令(或者把它注释掉),再次编译,这样就重新启用了assert()语句。
assert()的缺点是,因为引入了额外的检查,增加了程序的运行时间。
一般我们可以在(Debug 中使用,在Release版本中选择禁用assert就行,在vs,这样的集成开发环境中,在Release版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题,在Release版本不影响用户使用时程序的效率。

传值调用和传址调用

对于这两个的调用,我们在上面交换变量值的函数中已经使用过了,这里对它进行一个总结:

如果我们只是想要实现一个功能,不必要改变主函数中变量的值,那么我们使用传值调用就行了,但是如果我们要改变主函数中变量的值,我们就必须传址调用。当然,我们不要陷入一个误区,认为只要这里传了一个指针就可以了,在后面的学习中,我们还可能会传二级指针的,不过无论怎么变化,只要我们清晰的知道每一个变量代表的含义,改变的是哪个的地址的内容,我们都能写出正确的代码!!

总结

本篇分享到此就结束了,我们并没有学习很多语法知识,但重在加深对计算机的理解,打好基础,我们在学习后面的指针内容就会感到水到渠成了,祝愿每一个奋发向上的人都可以实现自己的梦想!!

  • 46
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值