这里要学习的有以下内容
1. const修饰指针
2. 野指针
3. assert断⾔
4. 指针的使⽤和传址调⽤
那么从这里开始
1. const 修饰指针
const修饰变量
首先我们知道变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。 但是如果我们不希望这个变量被修改,那我们该怎么做呢?
那么在这里就可以使用const。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修改所以给n加上const,但是我又需要对n进行修改那么给n加上const就一定写死无法更改了吗?
当然不是,这时我们可以绕过n,使⽤n的地址,去修改n就能做到了,虽然这样做是在打破语法规则。
#include int main()
{ const int n = 0;printf("n = %d\n", n); int* p = &n; *p = 20; printf("n = %d\n", n); return 0;
}
输出结果:
程序运⾏结果 我们可以看到这⾥⼀个确实修改了,但是我们还是要思考⼀下,为什么n要被const修饰呢?就是为了 不能被修改,如果p拿到n的地址就能修改n,这样就打破了const的限制,这是不合理的,所以应该让 p拿到n的地址也不能修改n,那接下来怎么做呢?
const 修饰指针变量 ⼀般来讲const修饰指针变量,可以放在*的左边,也可以放在*的右边,意义是不⼀样的。 int * p;//没有const修饰?
int const * p;//const 放在*的左边做修饰
int * const p;//const 放在*的右边做修饰
我们看下⾯代码,来分析具体分析⼀下:
#include<stdio.h>//代码1 - 测试⽆const修饰的情况
void test1()
{ int n = 10; int m = 20; int* p = &n; *p = 20;//可更改 p = &m; //可更改
}
//代码2 - 测试const放在*的左边情况
void test2()
{ int n = 10; int m = 20; const int* p = &n; *p = 20;//不可更改p = &m; //可更改
}
//代码3 - 测试const放在*的右边情况
void test3()
{ int n = 10; int m = 20; int * const p = &n; *p = 20; //可更改p = &m; //不可更改
}//代码4 - 测试*的左右两边都有const
void test4()
{ int n = 10; int m = 20; int const * const p = &n; *p = 20; //不可更改 p = &m; //不可更改
}int main()
{ //测试⽆const修饰的情况 test1(); //测试const放在*的左边情况 test2(); //测试const放在*的右边情况 test3(); //测试*的左右两边都有const test4(); return 0;
}
结论:const修饰指针变量的时候
• const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变。
• const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指 向的内容,可以通过指针改变。
2. 野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
2.1 野指针成因
1. 指针未初始化
#include <stdio.h>int main()
{ int *p;//局部变量指针未初始化,默认为随机值 ,这时p就为野指针*p = 20; return 0;
}
2. 指针越界访问
#include <stdio.h>
int main()
{int arr[10] = {0};int *p = &arr[0];int i = 0;for(i = 0; i <= 11; i++)
{//当指针指向的范围超出数组arr的范围时,p就是野指针*(p++) = i;
}return 0;
}
2.2 如何规避野指针
2.2.1 指针初始化
如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL. NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址 会报错。
初始化如下:
#include <stdio.h>
int main()
{ int num = 10; int*p1 = #int*p2 = NULL; return 0;
}
2.2.2 ⼩⼼指针越界
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是 越界访问,也就成为了野指针
2.2.3 将指针置为空
在指针使用结束后要将指针置为NULL,避免他对代码以后的使用的过程中造成影响。
int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10};int *p = &arr[0];int i = 0;for(i=0; i<10; i++)
{*(p++) = i;
}//此时p已经越界了,可以把p置为NULLp = NULL;//下次使⽤的时候,判断p不为NULL的时候再使⽤//...p = &arr[0];//重新让p获得地址if(p != NULL) //判断{//...}return 0;
}
2.2.4 避免返回局部变量的地址
如造成野指针的第3个例⼦,不要返回局部变量的地址。
3. assert 断⾔
assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报 错终⽌运⾏。这个宏常常被称为“断⾔”。
assert(p != NULL);
上⾯代码在程序运⾏到这⼀⾏语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序 继续运⾏,否则就会终⽌运⾏,并且给出报错信息提⽰。
assert() 宏接受⼀个表达式作为参数。
如果该表达式为真(返回值⾮零), assert() 不会产⽣ 任何作⽤,程序继续运⾏。
如果该表达式为假(返回值为零), assert() 就会报错,在标准错误 流 stderr 中写⼊⼀条错误信息,显⽰没有通过的表达式,以及包含这个表达式的⽂件名和⾏号。
使⽤ assert() 有⼏个好处:它不仅能⾃动标识⽂件和 出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。
当我们已经确认程序没有问题,不需要再做断⾔,只需要在 #include 语句的前⾯,定义⼀个宏 NDEBUG 就可以了,无需再进行其他操作。
#define NDEBUG
#include <assert.h>
然后,重新编译程序,编译器就会禁⽤⽂件中所有的 assert() 语句。
当程序⼜出现问题,只需要移除这条 #define NDEBUG 指令(或者把它注释掉),再次编译,这样就重新启⽤了 assert() 语句。
然而assert也是有缺点的
assert() 的缺点是,因为引⼊了额外的检查,增加了程序的运⾏时间。
为了解决这个问题,⼀般我们可以在 Debug 中使⽤,在 Release 版本中选择禁⽤ assert 就⾏,
在 VS 这样的集成开发环境的 Release 版本中,assert就直接被优化掉了。这样在debug版本写有利于程序员排查问题, 在 Release 版本不影响⽤⼾使⽤时程序的效率。
4. 指针的使⽤和传址调⽤
4.1 传值调⽤和传址调⽤
学习指针的⽬的是使⽤指针解决问题,那什么问题,⾮指针不可呢?
例如:写⼀个函数,交换两个整型变量的值 ⼀番思考后,我们可能写出这样的代码:
#include <stdio.h>
void Swap1(int x, int y)
{int tmp = x;x = y;y = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换前:a=%d b=%d\n", a, b);Swap1(a, b);printf("交换后:a=%d b=%d\n", a, b);return 0;
}
我们发现在main函数内部,创建了a和b,(这里假设地址) :a的地址是0x00cffdd0,b的地址是0x00cffdc4。在调⽤ Swap1函数时,将a和b传递给了Swap1函数,在Swap1函数内部创建了形参x和y接收a和b的值,但是 x的地址是0x00cffcec,y的地址是0x00cffcf0,x和y确实接收到了a和b的值,不过x的地址和a的地址不 ⼀样,y的地址和b的地址不⼀样,相当于x和y是独⽴的空间,那么在Swap1函数内部交换x和y的值, ⾃然不会影响a和b,当Swap1函数调⽤结束后回到main函数,a和b实际上没有交换。Swap1函数在使⽤ 的时候,是把变量本⾝直接传递给了函数,这种调⽤函数的⽅式我们之前在函数的时候就知道了,这 种叫传值调⽤。
结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实 参。
在这里可以使用传址调用
#include <stdio.h>
void Swap2(int*px, int*py)
{int tmp = 0;tmp = *px;*px = *py;*py = tmp;
}
int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);printf("交换前:a=%d b=%d\n", a, b);Swap2(&a, &b);printf("交换后:a=%d b=%d\n", a, b);return 0;
}
我们可以看到实现成Swap2的⽅式,顺利完成了任务,这⾥调⽤Swap2函数的时候是将变量的地址传 递给了函数,这样就直接改变了原地址处的数据。
这种函数调⽤⽅式叫:传址调⽤。
在这里可以使用这个方法来实现strlen函数的实现这里可以参看以前的博客:C语言学习:速通字符串函数-CSDN博客文章浏览阅读880次,点赞68次,收藏14次。在编程的过程中我们不免会对字符穿进行操作,那么我们就会经常使用字符串函数。那么这里我就给分享四个字符串函数。https://blog.csdn.net/2402_87907999/article/details/143661229?fromshare=blogdetail&sharetype=blogdetail&sharerId=143661229&sharerefer=PC&sharesource=2402_87907999&sharefrom=from_link
感谢观看,有兴趣的话,点赞,收藏,关注,来一波吧。制作过程中如有错误希望可以慷慨指出。最后有更多想法可以一起在评论区聊一聊,私信我也可以哦