1. const修饰指针
2. 野指针
3. assert断⾔
4. 指针的使⽤和传址调⽤
1. const 修饰指针
1.1 const修饰变量 变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。 但是如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作⽤.但是其实在C语言中被const给修改之后,他任然是
#include
int main()
{ int m = 0;
m = 20;//m 是可以修改的
const int n = 0;
n = 20;//n 是不能被修改的
return 0;
}
上述代码中n是不能被修改的,其实n本质是变量,只不过被const修饰后,在语法上加了限制,只要我 们在代码中对n就⾏修改,就不符合语法规则,就报错,致使没法直接修改n。
就相当于是给它加上了一把锁子,不允许你进行修改。
但是不要忘记我们学习指针了,我们还可以通过解引用来进行修改。
把n的地址给指针变量p,通过指针修改变量n,虽然说这样并不符合我们的我们的语法,这是在打破规则
我们可以看到这⾥⼀个确实修改了,但是我们还是要思考⼀下,为什么n要被const修饰呢?就是为了 不能被修改,如果p拿到n的地址就能修改n,这样就打破了const的限制,这是不合理的,所以应该让 p拿到n的地址也不能修改n,那接下来怎么做呢?
1.2 const 修饰指针变量
⼀般来讲const修饰指针变量,可以放在*的左边,也可以放在*的右边,意义是不⼀样的。
如果我们想要加上const这把锁子,不让他修改我们的变量,那么这把锁子应该放在哪里呢
我们先来看看第一种情况,这第一种情况是没有进行修饰的情况,那么这时候变量就可以被修改
接下来我们看看第二种情况,const放在了*的左边,那么结论就是,这时候限制的是,指针变量,那么我们这时候就不能通过指针变量来修改我们的n普通变量,但是我们这时候可以直接改变普通变量,不用通过指针
接下来是第三种情况,const被放在了*的右边,那么这时候限制的是普通变量,但是没有限制指针变量,我们这时候可以通过修改指针变量来修改普通的变量n,但是普通变量不能自己修改
最后的这一个就比较狠了,他在*的左右两边都放上了const,那么这时候,不论是普通变量还是指针变量,都被限制了,无论通过什么方法也不能修改变量n
结论:const修饰指针变量的时候 • const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变。
• const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指 向的内容,可以通过指针改变。
2. 野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针会出现的原因有三个
1. 指针没有进行初始化。
2. 指针越界访问了。
3. 指针指向的空间释放。
第一个指针没有进行初始化,这个相信大家都可以理解,
在上图中,我们就可以看见,我们设置的指针变量没有进行初始化,这是非常危险的行为。导致指针变量变成了野指针。
第二个就是指针越界访问了
在这个图片中,我们可以看见它存在着很明显的越界访问,就是即使你把<=10,修改成<10,也是无济于事,照样还是会越界,因为在这个循环里面p++;在最后,在他访问完i=9的元素的时候,指针变量还是会+1;这不就还是会越界。---还是会变成野指针。
第三个就是指针指向的空间被释放,
我们来看上面的图片,n作为局部变量,函数在使用完后就会被销毁,函数在完成调用之后把内存返回,销毁,那么这时候,你用指针重新找变量n,你是找不回来的,因为变量n已经被销毁了,在调用完成之后,n变量的空间就返回给了操作系统。----这时候指针就会变成野指针。
2.1. 如何规避野指针
野指针是非常危险的,野指针是不能使用的
1.指针初始化
如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL. NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址 会报错。NULL这就是一个空指针。
我们可以把野指针想象成野狗,野狗放任不管是⾮常危险的,所以我们可以找⼀棵树把野狗拴起来, 就相对安全了,给指针变量及时赋值为NULL,其实就类似把野狗栓起来,就是把野指针暂时管理起 来。 不过野狗即使拴起来我们也要绕着⾛,不能去挑逗野狗,有点危险;对于指针也是,在使⽤之前,我 们也要判断是否为NULL,看看是不是被拴起来起来的野狗,如果是不能直接使⽤,如果不是我们再去 使⽤。
1.如果指针有明确的地址,我们就直接给出明确的地址,可不敢不初始化,那问题就大了。
2. 如果指针变量目前还不知道导致向哪里,那就先把空指针赋给他,先给他初始化了再说。
但是,这个地址是⽆法使⽤的,读写该地址 会报错。
2.小心指针越界,
3.当指针不再进行使用时,我们要及时的把他变成空指针,在使用他之前要检查一下他的有效性
我们可以把野指针理解为是野狗,NULL是一条绳子,可以把他拴住,但是,即使一条野狗被绳子拴住,他也是很危险的,不过野狗即使拴起来我们也要绕着⾛,不能去挑逗野狗,有点危险;对于指针也是,在使⽤之前,我 们也要判断是否为NULL,如果他还是空的指针,那这个指针就没有用,我们就不去使用它,我们把野指针变成空指针,然后我们当我们想要使用指针的时候,我们会给指针赋予新的值,然后当我们使用这个指针的时候,我们会先进行判断,看看他还是不是空指针,不是的话我们再进行使用。
如造成野指针的第3个例⼦,不要返回局部变量的地址。
3.assert 断⾔
assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报 错终⽌运⾏。这个宏常常被称为“断⾔”
断言可以帮助我们查看这个指针是不是空指针,
上面的图片,就会验证,assert 就相当于是if函数一样,如果括号内部成立,那么就接着执行
查看指针变量p到底是不是空指针,如果他确实不是空指针,已经有了新的地址,那么程序就会继续的往下执行,但是如果他就是空指针,并不符合括号内部的表达式,那么程序就会终止执行,出现报错的消息assert() 宏接受⼀个表达式作为参数。如果该表达式为真(返回值⾮零), 任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零), 流 st assert() 不会产⽣ assert() 就会报错,在标准错误 derr 中写⼊⼀条错误信息,显⽰没有通过的表达式,以及包含这个表达式的⽂件名和⾏号。assert() 的使⽤对程序员是⾮常友好的,使⽤ assert() 有⼏个好处:它不仅能⾃动标识⽂件和 出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问 题,不需要再做断⾔,就在 #include 语句的前⾯,定义⼀个宏
假如我们的·程序现在已经没有任何的问题了,我们不想再使用断言assert,那么断言的好处就是它可以随时的开启和关闭,我们现在不想要断言的话,我们可以指直接把define定义的一行给他注释掉,只留下头文件的一行就可以,
assert() 的缺点是,因为引⼊了额外的检查,增加了程序的运⾏时间。
但是一般我们在debug环境中去使用这个函数,这确实会增加时间,但是在转换到release版本中时,断言就会直接被优化掉,加快了速度,保证了客户的体验。
4. 指针的使⽤和传址调⽤
学习指针的⽬的是使⽤指针解决问题,那什么问题,⾮指针不可呢?
在这张图里面,我们可以看见,在进行交换数字的时候,数字确实传进去了,但是结果就是没有进行交换,
在这张图中,我们看见了形参居然和实参的地址不一样,说明了什么,形参他就会开辟了新的地址给自己,这时候你修改了形参,他跟不就不会影响到实参,
结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实 参。
那么这时候就需要用到指针变量了,那么这又是什么原理呢,我们可以直接把a和b的地址传过去,让形参直接接受地址,然后通过地址(解引用)来进行值的交换,
我们直接传过去了地址,这样就不会涉及到形参会开辟新的地址的问题了,
传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;所 以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。如果函数内部要修改 主调函数中的变量的值,就需要传址调⽤
需要使用到主函数里面的值来进行计算,调用函数来返回一个值,这时候传值调用可以,但是如果我们需要修改主函数内部的值,这时候就必须要进行传地址过去解引用来修改------传址调用