尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深入了解C语言中的指针(2)

深入了解C语言中的指针(2) 深入了解指针21. const 修饰指针1.1 const修饰变量变量是可以修改的如果把变量的地址交给一个指针变量通过指针变量也可以修改这个变量。针对上面的情况当我们希望一个变量加上一些限制来让变量不能够被修改我们就要用到const#includestdio.hintmain(){intm0;m20;//m是可以修改的constintn0;n20;//n是不能被修改的return0;}上述代码中n是不能被修改的其实n本质是变量只不过被const修饰后在语法上加了限制只要我们在代码中对n进行修改就不符合语法规则就报错致使没法直接修改n。但是当我们绕过n通过n的地址来修改就能对n进行修改如下#includestdio.hintmain(){constintn0;printf(n %d\n,n);int*pn;*p20;printf(n %d\n,n);return0;}输出结果通过运行可以看到n确实被修改了但还是要思考⼀下为什么n要被const修饰呢就是为了不能被修改如果p拿到n的地址就能修改n这样就打破了const的限制这是不合理的所以应该让p拿到n的地址也不能修改n那接下来怎么做呢接下里1.2的知识点就是针对这个问题。1.2 const 修饰指针变量一般来讲const修饰指针变量可以放在的左边也可以放在的右边两种情况的用途不一样。int * p;没有const修饰int const * pconst 放在*的左边做修饰int * const pconst 放在*的右边做修饰接下来通过实操来具体分析一下两种情况的不同用途#includestdio.h//代码1 - 测试⽆const修饰的情况voidtest1(){intn10;intm20;int*pn;*p20;pm;}//代码2 - 测试const放在*的左边情况voidtest2(){intn10;intm20;constint*pn;*p20;pm;}//代码3 - 测试const放在*的右边情况voidtest3(){intn10;intm20;int*constpn;*p20;pm;}//代码4 - 测试*的左右两边都有constvoidtest4(){intn10;intm20;intconst*constpn;*p20;pm;}intmain(){//测试⽆const修饰的情况test1();//测试const放在*的左边情况test2();//测试const放在*的右边情况test3();//测试*的左右两边都有consttest4();return0;}通过测试可以得出结论const如果放在*的左边修饰的是指针指向的内容保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变。const如果放在*的右边修饰的是指针变量本⾝保证了指针变量的内容不能修改但是指针指向的内容可以通过指针改变。2. 野指针概念野指针就是指针指向的位置是不可知的随机的、不正确的、没有明确限制的2.1 野指针成因指针未初始化#includestdio.hintmain(){int*p;//局部变量指针未初始化默认为随机值*p20;return0;}指针越界访问#includestdio.hintmain(){intarr[10]{0};int*parr[0];inti0;for(i0;i11;i){//当指针指向的范围超出数组arr的范围时p就是野指针*(p)i;}return0;}指针指向的空间释放#includestdio.hint*test(){intn100;returnn;}intmain(){int*ptest();//当test函数结束后n内存空间的使用权限被收回printf(%d\n,*p);return0;}2.2 如何规避野指针2.2.1 对指针进行初始化如果明确知道指针指向哪里就直接赋值地址如果不知道指针应该指向哪里可以给指针赋值NULL。NULL是C语言中定义的一个标识符常量值是00也是地址这个地址是无法使用的读写该地址会报错。#ifdef __cplusplus#define NULL 0#else#define NULL ((void* )0#endif初始化如下#includestdio.hintmain(){intnum10;int*p1num;int*p2NULL;return0;}2.2.2 关注指针是否越界⼀个程序向内存申请了哪些空间通过指针也就只能访问哪些空间不能超出范围访问超出了就是越界访问。2.2.3 指针变量不再使用时及时置NULL指针使用之前检查有效性当指针变量指向⼀块区域的时候可以通过指针访问该区域后期不再使⽤这个指针访问的空间的时候就可以把该指针置为NULL。有一个约定俗成的规则只要是NULL指针就不去访问同时使用指针之前可以先判断指针是否为NULL。有个形象的比喻可以把野指针想象成野狗野狗放任不管是非常危险的所以需要找⼀棵树把野狗拴起来就相对安全了给指针变量及时赋值为NULL其实就类似把野狗栓起来就是把野指针暂时管理起来。不过野狗即使拴起来我们也要绕着走不能去挑逗野狗有点危险对于指针也是在使用之前我们也要判断是否为NULL看看是不是被拴起来的野狗如果是不能直接使⽤如果不是我们再去使用。#includestdio.hintmain(){intarr[10]{1,2,3,4,5,6,7,8,9,10};int*parr[0];inti0;for(i0;i10;i){*(p)i;}//此时p已经越界了可以把p置为NULLpNULL;//下次使用的时候判断p不为NULL的时候再使用//...parr[0];//重新让p获得地址if(p!NULL)//判断{//...}return0;}2.2.4 避免返回局部变量的地址如造成野指针的第三个例子不要返回局部变量的地址。3. assert断言assert.h头文件定义了宏assert(), 用于在运行时确保程序符合指定条件如果不符合就报错终止运行。这个宏常常被称为“断言”。assert(p!NULL);上面代码在程序运行到这一行语句时验证变量p是否等于NULL。如果确实不等于NULL程序继续运行否则就会终止运行并且给出报错信息提示。assert()宏接受⼀个表达式作为参数。如果该表达式为真返回值非零assert()不会产生任何作用程序继续运行。如果该表达式为假返回值为零assert()就会报错在标准错误流stderr中写⼊⼀条错误信息显示没有通过的表达式以及包含这个表达式的文件名和行号。assert()的使用对程序员是非常友好的使用assert()有几个好处它不仅能自动表示文件和出现问题的行号还有⼀种无需更改代码就能开启或关闭assert()的机制。如果已经确认程序没有问题不需要再做断言就在#include assert.h语句的前面定一一个宏NDEBUG。#defineNDEBUG#includeassert.h然后重新编译程序编译器就会禁用文件中所有的assert()语句。如果程序又出现问题可以移除这条#define NDEBUG指令或者把它注释再次编译这样就重新启用了assert()语句。assert()的缺点是因为引入了额外的检查增加了程序的运行时间。一般我们可以在Debug中使用在Release版本中选择禁用assert就行在VS这样的集成开发环境中在Release版本中直接就是优化掉了。这样在debug版本写有利于程序员排查问题在Release版本不影响用户使用时程序的效率。4. 指针的使用和传址调用4.1 strlen的模拟实现库函数strlen的功能是求字符串长度统计的是字符串中\0之前的字符的个数最终返回长度。如果要模拟实现只要从起始地址开始向后逐个字符的遍历只要不是\0字符计数器就1这样直到\0就停止。参考代码如下intmy_strlen(constchar*str){intcount0;assert(str);while(*str){count;str;}returncount;}intmain(){intlenmy_strlen(abcdef);printf(%d\n,len);return0;}4.2 传值调用和传址调用学习指针的目的是使用指针解决问题那什么问题非指针不可呢例如写一个函数交换两个整型变量的值#includestdio.hvoidSwap1(intx,inty){inttmpx;xy;ytmp;}intmain(){inta0;intb0;scanf(%d%d,a,b);printf(交换前a%d b%d\n,a,b);Swap1(a,b);printf(交换后a%d b%d,a,b);return0;}当我们运行代码结果如下通过上图能发现其实没产生交换的效果这是为什么呢我们可以通过调试找原因我们发现在main函数内部创建了a和ba的地址是0x00cffdd0b的地址是0x00cffdc4在调⽤Swap1函数时将a和b传递给了Swap1函数在Swap1函数内部创建了形参x和y接收a和b的值但是x的地址是0x00cffcecy的地址是0x00cffcf0x和y确实接收到了a和b的值不过x的地址和a的地址不⼀样y的地址和b的地址不⼀样相当于x和y是独⽴的空间那么在Swap1函数内部交换x和y的值⾃然不会影响a和b当Swap1函数调⽤结束后回到main函数a和b的没法交换。Swap1函数在使⽤的时候是把变量本⾝直接传递给了函数这种调⽤函数的⽅式我们之前在函数的时候就知道了这种叫传值调用。结论实参传递给形参的时候形参会单独创建一份临时空间来接收实参对形参的修改不影响实参。所以Swap1是失败的了遇到这种情况该怎么结局呢现在要解决的就是当调用Swap函数的时候Swap函数内部操作的就是main函数中的a和b直接将a和b的值交换了。那么就可以使用指针了在main函数中将a和b的地址传递给Swap函数Swap函数里边通过地址间接的操作main函数中的a和b并达到交换的效果就好了。#includestdio.hvoidSwap2(int*px,int*py){inttmp0;tmp*px;*px*py;*pytmp;}intmain(){inta0;intb0;scanf(%d%d,a,b);printf(交换前a%d b%d\n,a,b);Swap2(a,b);printf(交换后a%d b%d\n,a,b);return0;}观察上面程序的输出结果可以看到实现成Swap2的方式顺利完成了任务这里调用Swap2函数的时候是将变量的地址传递给了函数这种函数调用方式叫传址调用传址调用可以让函数和主调函数之间建立真正的联系在函数内部可以修改主调函数中的变量所以未来函数中只是需要主调函数中的变量值来实现计算就可以采用传值调用。如果函数内部要修改主调函数中的变量的值就需要传址调用。
返回列表