拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C语言入门,深入理解指针(2)

C语言入门,深入理解指针(2)

目录

  • 1. const修饰指针变量
    • 1. const修饰变量
    • 2. const修饰指针变量
  • 2. 野指针
    • 1. 野指针成因
    • 2. 如何规避野指针
  • 3. assert断言
  • 4. 指针的使用和传址调用
    • 1. strlen的模拟实现
    • 2. 传值调用和传址调用

前言

本文主要讲解C语言中指针相关的进阶知识,包括const修饰指针变量、野指针的成因与规避、assert断言的用法,以及传值调用与传址调用的区别。通过本文的学习,你将能更安全、规范地使用指针。

1.const修饰指针变量

1.const修饰变量

首先我先去创建一个变量a并修改它,最后输出a:

现在我不想让这个a被修改,那么就要使用一种方式去限制它的修改,这时候,我学习到了const可以去发挥这种作用,先介绍const的基本概念:const表示常属性的意思,就是不能被改变的意思,限制变量的修改,知道它的作用之后,我就要去使用它了,改进代码如下:

此时系统爆出了警告,表示我已经限制了a的修改:

在这里,a本质上还是变量,只是在语法层面上限制了a的修改,这里a被称为常变量,是最合适的。

拓展:在之后要学的C++里,const修饰的就不是变量,直接就是常量了。

但是现在又换了另一种思路,说我不直接修改你的a,我通过指针变量得到a的地址,再通过地址去找到那块空间,去修改那块空间的存放的数据,这个方法好像有点道理,我尝试去改进代码:

运行一下,发现修改成功了:

这就有点像这么一个例子,如下图:


我有一间房子,我每次回家都要从门进去,每次出门都要锁门,但是有一天我忘带钥匙了,此时门打不开,那我为了进入家里,我被迫翻越窗户进到家中,才达到我回家的目的,这个场景是不是有点像上面的情况了。

但是我最初的目的是想要a不被修改,而上面是拿到地址才能去修改a的,如何让p拿到a的地址也不能成功去修改a呢?这就引出了我接下来要讲的知识,继续往下看

2.const修饰指针变量

我的解决办法就是用const修饰指针变量,限制指针变量p指向的对象a被修改,但是在实现这个代码之前,还是得先来讲一下这个const修饰指针变量的基本用法及意义吧。

const修饰指针变量,一般可以放在星号的左边,也可以放在星号的右边,如下:
那么const的位置不同,表达的含义又有什么不同呢?
首先说明一下pa和pa的区别,如下图:

**const在星号的左边限制的是
p,也就是p指向的对象不能被修改,但是指针变量p的指向可以被修改**

const在星号的右边限制的是p,也就是指针变量p的指向不能被修改,但是p指向的对象可以被修改

这时候我又要举个例子帮助大家理解记忆了,吃凉皮的例子:

首先创建2个变量,名为男孩a和男孩b,分别有10块和100块,再创建一个指针变量p,名为女孩p,取地址a赋给p,说明男孩a和女孩p是男女朋友,当有一天女孩想吃凉皮了,她想要男孩请他吃凉皮,但是男孩不想要花钱,所以在星号的左边加上const,限制他兜里的10块被修改,此时女孩说你不请我吃凉皮,但是我有权利换男朋友啊,男孩a明显慌了,他告诉女孩:“我可以请你吃凉皮,但是你不能换男朋友,此时在星号的右边加上了const,限制女孩不能换男朋友,他兜里的10块可以被修改,这个故事很好的说明了const在星号的不同位置上所起到的作用,提醒一嘴,如果既不愿意给你花钱也不愿意让你换男朋友,你大抵是遇上渣男了。

2.野指针

学习完const,接下来,我们要去讲一类指针,叫做野指针。先来了解一下它的概念吧!
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
说白了,就是指针指向的空间不属于当前的程序。

1.野指针成因

1.指针未初始化


原因:指针变量pa未初始化时,pa属于局部变量,局部变量不初始化,它里边存放的是1个随机值,指针变量pa没有明确指向的是内存的哪一块空间。

2.指针的越界访问


原因:p存放首元素的地址,我们故意让它循环11次,p++,在前10次循环时,p都是一个合法的指针,但当加到10时,p+10所指向的空间已经不属于当前程序向内存申请的空间了,此时指针变成了野指针。

3.指针指向的空间释放


原因:虽然说test函数返回让p接受到了n的地址,但是函数的调用结束了,局部变量n已经被销毁了。n已经把那块空间还回去了,然后你想要通过接受到的n的地址去找到n,p所指向的那块内存空间已经不属于当前程序了,即使你找到了那块地址,也只能获得1个随机值。

2.如何规避野指针

1.指针初始化

情况一:明确知道,指针变量要初始化为什么值。

inta=10;int*pa=&a;

情况二:不知道指针变量应该给什么值,可以给指针赋值为NULL

int*p=NULL;

关于NULL的解释:NULL表示空指针的意思,空指针就是没有指向任何有效的空间,暂时不要使用它。
NULL 作为C语言中定义的一个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址
会报错。

举个形象的例子,大家就明白了,将野指针看作是一条野狗,野狗没拴绳很危险,此时NULL相当于一条狗绳,将野狗暂时拴起来,也就是指针变量防止变成野指针。

2.小心指针越界

当前程序向内存申请了哪些空间,只能允许指针访问哪些空间,不能超出范围访问,不属于你的空间不访问,只访问自己的空间。

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

如下图:

4.避免返回局部变量的地址(返回栈空间的地址)

不返回局部变量的地址就可以了

与返回局部变量的区别:
函数返回局部变量时,将局部变量的数据暂时存放在寄存器内,函数调用结束,局部变量被销毁,主函数内的变量通过关联寄存器,得到局部变量的数据。

3.assert断言

我们前面不是通过if条件去判断指针变量是否为空指针吗?而在这里,其实还有1种更好的方法去判断,就是assert断言。

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

assert()宏,在括号里可以接受一个你想判断的表达式作为参数,若表达式为真(即返回值为非0),assert不会产生任何作用,程序继续运行;若表达式为假(即返回值为0),assert发生报错,控制台会显示一条错误的信息,包含没有通过的表达式,以及包含这个表达式的文件名和行号,如下图所示:

当然使用assert()也要包含对应的头文件:#include <assert.h>

如果使用assert()后,确认程序没有问题,我们可以把它给关掉,如果不关掉,程序始终会默认执行assert()这条语句,增加程序运行的时间,我们在头文件前面定义一个宏NDEBUG,就可以关掉断言,
如果下次还想使用,直接将#define NDEBUG注释或移除

还有一点要注意的就是在VS这样的集成开发环境下,我们可以在Debug中使用,在Release版本直接就是把assert()优化掉了,就是不起作用了。

4.指针的使用和传址调用

1.strlen的模拟实现

在上节课我们已经对strlen函数通过一个函数实现了功能的模拟,今天我们在重新地去梳理一下:

当我们学过const和assert断言后,我们可以把代码去变得更加安全一些:

2.传值调用和传址调用

学习指针的目的是为了用指针解决问题,那么有什么问题,非指针不可呢?

例如:写一个函数,交换2个整型变量的值
你的第一反应可能会想创建一个临时变量,去帮助这2个整型变量的交换,我们根据这个思路,写出下面的代码:

运行结果:

我们发现a和b的值未发生交换,这是为什么呢?我们不妨调试一下:

调试过程中,我们可以看到当函数将实参a、b传递给形参时,形参x、y单独向内存申请了空间,这也导致了它们内存空间不一样,在交换过程中,形参的改变对实参没有影响,交换x、y不影响a、b的值,自然就达不到我们想要的结果了。这种方式叫做**“传值调用”**。

结论:实参传递给形参的时候,形参会单独创建一份临时空间来接收实参,对形参的修改不影响实
参。

所以Swap1是失败的了。

那么怎么样将实参和形参的改变联系起来呢?
我们想让函数内部操作的参数就是主函数中的参数,那这时候我们就要用到指针了,通过main函数对swap函数的调用,将a、b的地址传递给swap函数,swap函数在间接使用该地址,从而实现对a、b的交换。
改进代码如下:

这种方式叫做**“传址调用”**,可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量。

所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用。如果函数内部要修改主调函数中的变量的值,就需要传址调用。

总结

本文围绕C语言指针的进阶使用展开:首先介绍了const修饰指针变量的两种形式及其区别;接着分析了野指针的成因,并给出了初始化、防越界、置NULL等规避方法;然后介绍了assert断言这一更安全的指针检查方式;最后通过交换变量的例子,清晰区分了传值调用与传址调用的适用场景。掌握这些内容,能帮助你在实际编程中写出更健壮、更安全的指针代码。

返回列表