C语言(指针)3

发布于:2024-05-10 ⋅ 阅读:(24) ⋅ 点赞:(0)

                       Hi~!这里是奋斗的小羊,很荣幸各位能阅读我的文章,诚请评论指点,关注+收藏,欢迎欢迎~~     

                        💥个人主页小羊在奋斗

                        💥所属专栏C语言   

        本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为同样是初学者的学友展示一些我的学习过程及心得。文笔、排版拙劣,望见谅。 

                                5、指针运算

                                                5.1指针 +- 整数

                                                5.2指针 - 指针

                                                5.3指针的关系运算

                                6、野指针

                                                6.1野指针成因

                                                6.2如何避免野指针

                                7、assert 断言

                                8、指针的使用

                                                8.1strlen函数的模拟实现

                                                8.2传值调用和传址调用

5、指针运算

        5.1指针 +- 整数

        在 C语言(指针)1中,我们已经了解过了指针 +- 整数的情况,知道了指针 +- 整数的结果取决于它所指向的对象的类型,这里再来看一种指针 +- 整数的用法。

        我们知道数组内的元素在内存中的存储是连续的,所以我们就可以用指针 +- 整数的方法来遍历数组:

        上面几种写法实际上表达的是一个意思。 

         当然也能逆序打印:

        这里只演示了一种方法,其他方法都是一样的。

        我们知道,上述内容中的原理为:arr数组中的元素为int型,指针变量p的类型为int *型,所以指针变量p+1跳过4个字节,刚好访问到数组中的下一个元素。那如果我们将数组首元素的地址存到char *类型的指针变量p中,因为char类型大小为1个字节,所以我们给指针变量p一次+4,是否也能实现遍历数组呢? 

        可以看到确实实现了遍历数组的目的,那上面的方法就是正确的吗?答案是否定的。在上面的代码中能成功实现遍历数组是因为特殊情况

        当指针变量p是int *类型的时候, *p访问的是下面红色方框内的四个字节的内容,p+1是由第一个红色箭头跳到了第二个红色箭头的位置;

         当指针变量p是char *类型的时候,作为int型的数组,其内部的一个元素存储在下面的一个红色方框中,但是*p访问的只是下面蓝色方框内的一个字节的内容,不过p+4确实是从第一个蓝色箭头跳到了第二个蓝色箭头的位置。

        如上所说,当数组中的元素是更大的值的时候,它的值就会 “分布” 在一个红色方框内的几个小格子中,这时候作为char *类型的指针变量p在*p解引用操作的时候访问的还是一个蓝色方框内的内容,其它小格子中的值访问不到了。说到这里相信你就明白了上面所说的答案是否定的的原因

        5.2指针 - 指针

        我们这里直接说结论:指针 - 指针的绝对值是指针和指针之间元素的个数

        但前提是这两个指针指向的是同一块空间

        之所以要说指针 - 指针的绝对值,是因为数组内元素的地址随着下标的增大而增大,如果前面元素的地址 - 后面元素的地址,得到的值就是负数:

        那这有什么用呢? 

        我们可以用上面学到的东西来模拟实现strlen函数,关于strlen函数在之前的文章 —> C语言基础 中已经介绍过,strlen函数的作用是计算字符串的长度,统计的是字符串中 “\0” 之前的字符个数。

        先来复习一下strlen函数的用法:

        接着我们用指针来实现一下,做个铺垫:

        有了上面的铺垫后,我们就来用指针 - 指针的方式实现:

        既然有指针 - 指针,那有么有指针 + 指针的运算呢?没有,指针 + 指针是没有什么意义的。

        5.3指针的关系运算

         指针之间也是可以比较大小的,下面我们就用这个性质来实现遍历数组的效果:

6、野指针

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

        6.1野指针成因

         (1)指针未初始化;

        如果将p中存放的值当作地址,解引用操作就会非法访问。 

        (2)指针越界访问;

          我们只申请了数组内10个元素大小的地址,当指针变量指向的地址超过了数组内下标最大元素的地址,此时指针变量就成了野指针。

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

         上面的代码是一个非常典型的例子,大家觉得上面的代码有什么问题?

        我们在自定义函数里定义了一个局部变量a,然后把a的地址作为函数返回值,在main函数中用指针变量p接收传过来的地址,在通过解引用打印变量a的值。表面看并没有什么问题,但是问题隐藏在我们看不见的地方。

        我们确实把a的地址当作函数返回值由指针变量p成功地接收了,但是不要忘了a是一个局部变量,之前的文章 static 和 extern 中说过:局部变量进入作用域变量创建,生命周期开始;出作用域变量销毁,生命周期结束。所以,当我们的自定义函数结束后,局部变量a就销毁了,向内存申请的空间就会释放还给内存,此时即使p中存了a的地址也没有用了,拿着这个地址找过去也找不到a,就像过期了一样。

        打个比方:我们今晚去住酒店,住下之后把酒店的位置和房间号告诉了我们的好朋友,好朋友记下后第二天就来了这个酒店找我们,但是我们并不知道他要来找我们,就把房给退了回家了,这时候好朋友来酒店找我们还找得到吗?他连酒店房门都进不去,如果他是一根筋非要进房间找我们并说我们明明告诉他我们就住在这个房间里,这时候酒店保安肯定会把他叉出去,并且报警。

        野指针在C语言中是非常可怕的,可能会导致内存泄漏、程序崩溃、安全漏洞、数据损坏等,我们一定要避免野指针的出现。 

        6.2如何避免野指针

        避免野指针的方法就是解决掉野指针的成因,对症下药。

        (1)指针初始化;

        如果明确知道指针需要指向哪里就直接赋地址值,如果还不明确指针需要指向哪里就先赋NULL。NULL是C语言中定义的一个标识符常量,值是0,0也是地址但这个地址(空指针)是无法使用的,读写地址会报错,但不会构成野指针。

        空指针是无法访问的,这么做是为了避免出现野指针,等我们需要给指针变量赋地址的时候再给它赋相应的地址。我们要养成给指针变量赋NULL的习惯,这叫防患于未然。 

         (2)小心指针越界;

        一个程序向内存申请了多大的空间,通过指针就访问多大的空间,千万不能超出范围访问,否则就是越界访问,是非法的。

        (3)指针变量不再使用时,及时置NULL,指针使用之前检查有效性; 

        (4)避免返回局部变量的地址。 

7、assert 断言 

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

        assert()宏接受一个表达式作为参数,如果该表达式为真(非0),assert()不会产生任何作用,程序继续运行;如果该表达式为假(0),assert()就会报错终止程序,并且给出错误信息。

        使用 assert()有几个好处:它不仅能自动标识文件和出问题的行号,还有一种无需更改代码就能开启或关闭 assert()的机制。如果已经确定程序没有问题,不需要再做断言,就在#include <assert.h> 语句的前面定义一个宏 NDEBUG。如果还不知道宏是什么也没有关系,先理解本节的内容,后面会有相应的文章。 

        然后,重新编译程序,编译器就会禁用文件中所有的 assert()语句。 

        可以看到,在 #include <assert.h> 前面定义了宏NDEBUG后,再次运行程序就不会报错。 

         如果程序又出现了问题,可以移除 #define NDEBUG 这条指令(或者把它注释掉),再次编译,这样就重新启用了 assert()这条语句。

        assert()的缺点是,引入了额外的检查,增加了程序的运行时间,当然肯定是利大于弊的。

        一般在 Debug 中使用,在 Release 版本中选择禁用就行,不过在VS这样的集成开发环境的 Release 版本中,直接就优化掉了。这样的好处是不仅在 Debug 版本中有利于程序员检查问题,而且在 Release 版本中也不影响用户使用程序时的效率。

8、指针的使用

        8.1strlen函数的模拟实现

        前面我们已经实现过模拟strlen()函数,这里再来做一个优化版:

        来解读一下上面的代码:

        首先我们要搞清楚我们的目的,只是把一个字符串传过去求它的长度,因此并不想让这个字符串发生改变,所以我们用 const “训练” 了形参,让它变得抗造不会被改变(健壮性 / 鲁棒性),可以防止自己或别人不小心改变字符串; 然后我们还加了 assert 断言,确保指针不是空指针,提高了代码的可靠性。

        通过上面的代码我们不难发现,随着学习地不断深入,我们写出的代码质量会越来越高。 

        8.2传值调用和传址调用 

        对于传值调用相信大家都非常熟悉,没什么可说的。关于传址调用,在我之前的文章中已经介绍过,还请跳转阅读另一篇文章 —>  指针的简单应用。这篇文章是很早写过的,写的可能不是那么好,还请见谅。

        传址调用,可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量。所以如果只是需要主调函数中的变量值来实现计算,就用传值调用;如果需要在函数内部修改主调函数中变量的值,就用传址调用。 

        如果觉得我的文章还不错,请点赞、收藏 + 关注支持一下,我会持续更新更好的文章。

                                          点击跳转下一节 —> C语言(指针)4


网站公告

今日签到

点亮在社区的每一天
去签到