深挖C语言:深入理解指针(2)
目录
1.const修饰指针
2.野指针
2.1 野指针的成因:
2.2 规避野指针
3.assert断言
4.指针的使用和传值调用
4.1 库函数strlen的实现
4.2 传值调用和传址调用
1.const修饰指针
作用:使操作对象不能被随意修改。
操作对象:普通变量,指针,函数形参,全局常量。
int m = 10; m = 20; // m 能被修改 const int n = 20; n = 10;// n 不能被修改这里虽然 n 被 const 修饰,强行修改会报错,但是我们可以通过指针的方式绕过const的限制修改变量n,代码如下:
const int n = 10; int* pn = &n; *pn = 20;// n 成功被修改这种行为显然打破了语法规则,如果我们不想让变量通过指针被修改,那么就对指针也进行 const 修饰,代码如下:
const int n = 10; const int* pn = &n; * pn = 20;// n不能被修改,代码报错 int* const pn = &n; *pn = 20;// n可以被修改虽然说对指针变量进行了const修饰,但是const放在 * 的前面或者后面,修饰的对象不相同,所加的范围限制也不相同。
const int n = 10; int m = 20; int* const pn = &n; *pn = 30;//没问题 pn = &m;//报错,指针变量pn不能被修改 const int* pm = &m; *pm = 30;//报错,*pm不能被修改 pm = &n;//没问题结论:const修饰指针变量时:
1.如果放在 * 的左边,修饰的是所存指针指向的内容,使指向的内容不能被修改。
2.如果放在 * 的右边,修饰的是指针变量所存的内容,指针变量的内容不能被修改。
2.野指针
概念:指针指向的位置是不可知的(随机的,不正确的,没有明确限制的),称为野指针。
2.1 野指针的成因:
(1)指针未初始化
int n; int* pn = &n;// pn因变量未初始化成为野指针(2)指针越界访问
int arr[10] = { 0 }; int* parr = &arr[10];// parr越界访问成为野指针(3)指针指向的空间被释放
int* test() { int n = 100; return &n; } int main() { int* p = test();// n的空间被释放,p成为野指针 printf("%d\n",*p); return 0; }注意:一个程序向内存申请了哪些空间,通过指针也就只能访问这些空间,超出这个范围了就是越界访问。
2.2 规避野指针
(1)指针使用之前检查指针的有效性,指针变量不再使用时,及时置NULL。
(2)不要返回局部变量的地址。
野指针就好比是一条没有拴绳的野狗,对其放任不管是很危险的,对指针及时赋予NULL,相当于把这条野狗给栓了起来,对NULL约定俗成的一个规矩就是:只要是NULL指针就不去访问,规避了野指针的形成。
3.assert断言
使用代码如下:
#include<assert.h> //使用assert不要忘记带上头文件 assert(p != NULL);代码运行时,assert()宏接受一个表达式作为参数,如果该表达式为真(返回结果非零),assert()就不会产生任何作用,程序继续执行。如果表达式为假(返回结果为零),assert()就会中止代码的运行,并进行报错。
如果已经确定程序没有任何问题,不需要再进行断言,此时在#include<assert.h>前定义一个宏NDEBUG即可,代码如下:
#define NDEBUG //消除assert() #include<assert.h>注意:(1)assert因为对程序有了检查,会增加程序运行的时间。
(2)assert一般是在Debug中使用,在Release中选择禁用assert,这样在Debug版本中有利于程序员排查问题,在Release版本中不影响用户使用程序的效率。
4.指针的使用和传值调用
4.1 库函数strlen的实现
作用:求字符串的长度。代码如下:
int my_strlen(const char * str) { int count = 0; assert(str); while(*str) { count++; str++; } return count; } int main() { int len = my_strlen("abcdef"); printf("%d\n",len); return 0; }4.2 传值调用和传址调用
为了区分传值调用和传址调用,简写交换整型变量的函数便于理解:
传值调用代码:
#include<stdio.h> void Swap1(int x,int y) { int tmp = x; x = y; y = tmp; } int main() { int a = 10; int b = 20; Swap1(a,b); printf("%d %d",a,b);//a,b并未被交换 return 0;传址调用代码:
#include<stdio.h> void Swap2(int* x,int* y) { int tmp = *x; *x = *y; *y = tmp; } int main() { int a = 10; int b = 20; Swap2(&a,&b); printf("%d %d",a,b);//a,b的值成功被交换 return 0; }由此我们可知,实参传递给形参的时候,形参会单独创建一份临时空间变量来接受实参,对形参的修改不影响实参。
这是Swap1失败的原因,在上述函数中,只能使用传址调用。
结论:传址调用可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数的变量;所以函数只需要主调函数中的变量值来进行计算,就可以使用传值调用;如果函数内部要修改主调函数中的变量值,就需要使用传址调用。
最后,感谢阅读,欢迎大家交流与指正。
