1. 指针运算
1.1 指针 +- 整数
以数组举例:因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸瓜就能找到后面的所有元素。这时就会用到指针加减整数。
1.2指针-指针
指针 - 指针可以得到两个指针之间的数据个数。但是,要注意这种运算前提是,两个指针指向同一块空间。如:数组空间,字符串,动态开辟的一块空间等。
例如:对于字符串类型,两个指针采用左闭右开的形式相减,就能得到字符串内的字符个数。
1.3指针的关系运算
指针之间支持各类关系运算符(< , > , == , != 等)
2. 野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的
1. 野指针成因(举例)
1.1 指针未初始化
1.2. 指针越界访问
当指针指向的范围超出数组arr的范围时,p就是野指针。
1.3 指针指向的空间已释放
在这段代码里,我们调用 test 函数,为其创建函数栈帧,函数调用结束时栈帧销毁。临时变量 n 也销毁,这时候为 n 分配的空间就已经被操作系统回收,这时候操作系统有可能将这段空间另做处理,如果我们将其作为返回值并使用, 就会导致野指针。
2. 如何规避野指针
2.1指针初始化
定义指针变量时,如果明确知道指针指向哪里就直接赋值地址,如果不知道指针应该指向哪里,可以给指针赋值NULL.
NULL 是C语言中定义的⼀个标识符常量,也就是空指针,它值是0,0也是地址,并且这个地址是无法使用的,读写该地址会报错。
2.2 小心指针越界
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。对于越界访问,虽然编译器有时候不会报错,但这无疑是一种危险行为。
2.3 合理处理指针变量
指针变量不再使用时,及时置NULL,指针使用之前检查有效性。
所以我们默认被赋予NULL的指针变量不可被使用,如果要使用需另做处理。
2.4 避免返回局部变量的地址
如造成野指针的第3个例子,不要返回局部变量的地址。
3. assert 断言
3.1简介
assert.h 头文件定义了宏 assert() ,用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”。
3.2 简单例子
上面代码在程序运行到这一行语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序继续运行,否则就会终止运行,并且给出报错信息提示。
如果已经确认程序没有问题,不需要再做断言,就在 #include 语句的前面,定义⼀个宏 NDEBUG 。
然后,重新编译程序,编译器就会禁用文件中所有的 assert() 语句。
3.3使用小结
总结:assert() 的缺点是,因为引入了额外的检查,增加了程序的运行时间。
一般我们可以在 Debug 中使用,在 Release 版本中选择禁用assert 就行,在 VS 这样的集成开 发环境中,在 Release 版本中,直接就是优化掉了。这样在 debug 版本写有利于程序员排查问题, 在 Release 版本不影响用户使用时程序的效率。
4. 传值调用和传址调用
学习指针的目的是使用指针解决问题的,在C语言中,有时候就不得不用到指针。
4.1 传值调用
在实践传值调用有可能达不到我们的目的。
例如:写⼀个函数,交换两个整型变量的值。
在main函数内部,创建了a和b,在调用 swap 函数时,将实参a,b传给了形参x,y。
但x的地址和a的地址不一样,y的地址和b的地址不⼀样,相当于x和y是独立的空间,那么在swap函数内部交换x和y的值, 就不会影响a和b。
结论:实参传递给形参的时候,形参会单独创建一份临时空间来接收实参,对形参的修改不影响实 参。
4.2 传址调用
想要在 swap 函数中交换a,b,就需要在main函数中将a和b的地址传递给swap函数,在 swap 函数里通过地址间接的操作main函数中的a和b,达到交换的效果。
所以,传址调用可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主函数中的变量。