本篇会加入个人的所谓‘鱼式疯言’
❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言
而是理解过并总结出来通俗易懂的大白话,
我会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的.
可能说的不是那么严谨.但小编初心是能让更多人能接受我们这个概念 !!!
在上一篇的指针专题中我们主要学习了
1.指针变量和地址中,内存是什么以及其大小转化关系,指针和地址的区别 ,& 和 * 的运用。
2.我们还说明了,指针可以加上或减去一个整数,指针之间可以想减,指针可以关系判断,
还有一个比较特殊的指针类型 void* 的解释
3最后我们提了一嘴我们的关键字 const 的在普通变量和指针变量的用法。
原文链接:https://blog.csdn.net/mgzdwm/article/details/134691818
这次小编又带来了指针的第二篇内容的学习了,虽说 指针 真的有点小难度
但小编相信只要宝子们认真去学习并配上 小编文章 的 小小的总结
咱们一定可以拿捏 这小小指针哒
下面请宝子们移步 目录区 观赏哦
精 彩 马 上 开 始
野指针 就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
出现野指针的情况很多,小编的收集了以下三种情况供友友们参考
//野指针情况一:
// 指针未初始化
#include
int main()
{
//局部变量指针未初始化,默认为随机值
int* p;
*p = 20;
return 0;
}
//野指针情况一:
//指针越界访问
#include
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;
}
#include
int* test()
{
int n = 100;
//取出 n 的地址返回
return &n;
}
int main()
{
//用 p 来接收
int* p = test();
printf("hehe\n");
//观察验证
printf("%d\n", *p);
return 0;
}
小编用大白话说野指针就是
用 没有存放的地址的指针,用 别人的地址,用 销毁的地址
有那么多情况会出现 野指针,友友们该怎么避免出现 野指针 呢
请 宝子们 继续往下看哦
如果是未初始化的指针呢,那 好办啊,直接初始化不就完了,那宝子们该如果初始化呢 ? ? ?
如果明确知道指针指向哪里就直接赋值地址
如果 不知道指针应该指向哪里,可以给指针赋值NULL.
NULL 是C语言中定义的一个标识符常量,值是 0,0 也是地址,这个地址是无法使用的,读写该地址会报错。
//指针初始化
#include
int main()
{
//初始化为 NULL
//防止出现野指针
int* p2=NULL ;
//如果为 NULL 就会报错
printf("%d", *p2);
return 0;
}
如果加个判断呢就不会报错了
//指针初始化
#include
int main()
{
int num = 10;
int* p1 = #
//初始化为 NULL
//防止出现野指针
int* p2=NULL ;
//当我们需要用 p2 指针时我们就判断是否为 NULL
if (p2==NULL)
{
// 一旦为 NULL 就退出程序
return 1;
}
//如果不为 NULL 我们就可以避免野指针了
printf("%d", *p2);
return 0;
}
越界了怎么办 ? ? ?
一个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。
及时置NULL,指针使用之前检查有效性
鱼式疯言
我们可以把野指针想象成野狗,野狗放任不管是非常危险的,所以我们可以找一棵树把野狗拴起来,就相对安全了
给指针变量及时赋值为 NULL,其实就类似把野狗栓前来,就是把野指针暂时管理起来。
不过野狗即使拴起来我们也要绕着走,不能去挑逗野狗,有点危险;
对于指针也是,在使用之前,我们也要判断是否为 NULL
看看是不是被拴起来起来的野狗
如果是,那就不能直接使用
如果不是,我们再去使用。
#include
int* test()
{
//将原本是栈区的局部变量转化为静态区的静态变量
static int n = 100;
//取出 n 的地址返回
return &n;
}
int main()
{
//用 p 来接收
int* p = test();
printf("hehe\n");
//观察验证
printf("%d\n", *p);
return 0;
}
小编就简单说下我们的局部变量
我们函数内部的局部变量是放在栈区的,会被 销毁
当我们加上 static 放在静态区就不会被销毁
自然就可以继续访问我们的 n 的 地址 了
小爱同学又来发问了,如果我们每次都要 if 来判断指针是否为 NULL 是不是太麻烦了 ? ? ?
我们有没有一种函数或者宏定义来给出提示呢,答案是有哒,那就只有我们的 assert 断言能办到了
在C语言中,assert 是一个预处理宏,用于在代码中插入断言语句。
断言用于检查程序运行过程中的一些条件是否满足
如果断言失败,即条件不满足,则会触发一个错误。
assert 宏的定义如下:
#include
void assert(int expression);
就拿上一个代码举个栗子吧
//assert 所需的头
#include
//不用该指针时置为NULL
int main()
{
int arr[10] = { 1,2,3,4,5,67,7,8,9,10 };
int* p = &arr[0];
for (int i = 0; i < 10; i++)
{
*(p++) = i;
}
//此时p已经越界了,可以把p置为NULL
p = NULL;
//下次使用的时候,判断p不为NULL的时候再使用
//...
assert(p != NULL); //判断
p = &arr[0];//重新让p获得地址
//...
return 0;
}
这时有个宏定义 闪亮登场
//关掉 assert 断言的宏定义
#define NDEBUG
//assert 所需的头
#include
//不用该指针时置为NULL
int main()
{
int arr[10] = { 1,2,3,4,5,67,7,8,9,10 };
int* p = &arr[0];
for (int i = 0; i < 10; i++)
{
*(p++) = i;
}
//此时p已经越界了,可以把p置为NULL
p = NULL;
//下次使用的时候,判断p不为NULL的时候再使用
//...
assert(p != NULL); //判断
p = &arr[0];//重新让p获得地址
//...
return 0;
}
上面的栗子充分说明了我们可以
assert() 的缺点是,因为引入了额外的检查,增加了程序的运行时间。
一般我们可以在Debug 中使用
在Release 版本中选择禁用 assert 就行
在VS 这样的集成开发环境中,
在Release 版本中,直接就是优化掉了。
这样在debug版本写有利于程序员排查问题
在Release 版本不影响用户使用时程序的效率。
如果大白话说就是
assert 只有在 debug 环境下才能使用,在 release 的环境下无法使用
前提 : 说到 调用,那必须是我们 函数的调用
那我们的 传值调用,顾名思义肯定是传他的值。
传址调用,当然是传他的地址,竟然是传他的地址必然和我们的指针有关
//传值调用
//加法器
int add(int x,int y)
{
return x + y;
}
int main()
{
int a = 0, b = 0;
scanf("%d%d", &a, &b);
int sum=add(a, b);
printf("%d+%d=%d",a,b, sum);
return 0;
}
可有时候我们传值调用却没有达到我们想要的预期的效果 ! !!
//传值调用
//加法器
void swap(int x,int y)
{
//定义临时变量
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 0, b = 0;
scanf("%d%d", &a, &b);
swap(a, b);
printf("交换前\n");
printf("a=%d,b=%d\n",a,b);
printf("交换后\n");
printf("a=%d,b=%d\n", a, b);
return 0;
}
发现他们的地址居然没改变
是的,结果发现 a 和 b 都没有交换
我们发现在main函数内部,创建了 a 和 b ,a的地址是 0x0093f824,b的地址是 0x0093f818
在调用 Swap 函数时,将a和b传递给了Swap1函数,在Swap1函数内部创建了形参x和y接收a和b的值,但是x的地址是
0x0093f740,y的地址是 0x0093f744
x 和 y 确实接收到了 a 和 b 的值,不过x的地址和a的地址不一样,y的地址和b的地址不一样,相当于 x 和 y 是独立的空间,那么在
Swap 函数内部交换x和y的值
自然不会影响a和b,当 Swap 函数调用结束后回到 main 函数, a 和 b 的没法交换。
Swap 函数在使用的时候,是把变量本身直接传递给了函数,这种调用函数的方式我们之前在函数的时候就知道了,这种叫传值调用。
那我们试试传址调用吧 `
//传址调用
//俩数交换
void swap(int *x,int* y)
{
//定义临时变量
int tmp = *x;
*x = *y;
*y = tmp;
}
int main()
{
int a = 0, b = 0;
scanf("%d%d", &a, &b);
printf("交换前\n");
printf("a=%d,b=%d\n",a,b);
swap(&a, &b);
printf("交换后\n");
printf("a=%d,b=%d\n", a, b);
return 0;
}
当我们 调试 的时候就会发现,这俩地址居然相同
我们可以看到实现成 Swap 的方式,顺利完成了任务,这里调用 Swap 函数的时候是将变量的地址传
递给了函数,这种函数调用方式叫:传址调用。
传址调用,可以让函数和主调函数之间建立真正的联系
在函数内部可以修改主调函数中的变量;所以未来函数中只是需要主调函数中的变量值来实现计算
就可以采用传值调用。如果函数内部要修改主调函数中的变量的值,就需要 传址调用
小编有两点总结:
- 当我们传值调用时,只需记住一点,形参只是实参的一份 临时拷贝,我们只能得到数据而不能真正改变其原先的数据
- 小编的认为的使用场景是: 当我们需要改变其原值是,我们就用 传址调用,当我们只需要他的一个数值的时候,我们只需要 传值调用即可
//assert 需要的头
#include
#include
//const 修饰防止 str 被修改
size_t my_strlen(const char* str)
{
int count = 0;
//assert 断言防止传NULL
assert(str);
while (*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
//返回的是无符号整型
//size_t = unsigned
size_t len = my_strlen("abcdef");
printf("%zd\n", len);
return 0;
}
在这个实际运用中,我们重复让 指针右移 ,直到我们找到 ‘\0’ 就停止,从中算出我们想要的字符串的长度
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
二级指针就由此诞生了 ! ! !
C语言中的二级指针是指指向指针的指针。在C语言中,指针是一个变量,它存储了内存地址。而一个二级指针就是存储了指针变量的地址的变量。通过使用二级指针,我们可以间接地访问或修改指针的值。
二级指针的声明需要在前面加上两个 “*” 号。例如,** “int ** pp” ** 表示一个二级指针,它指向一个指针,用两个 * 对其解引用时,指针指向一个整数。
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
return 0;
}
#include
int main()
{
//整型变量
int a = 10;
//一级指针
int* pa = &a;
//二级指针
int** ppa = &pa;
printf("a=%d\n", a);
//对一级指针解引用
printf("*pa=%d\n", *pa);
//对二级指针解引用
printf("**ppa=%d\n", **ppa);
return 0;
}
一级指针 的指针才叫 二级指针
一级指针一颗 * ,二级指针两颗 * 。
一级指针解引用 一颗 * ,二级指针解引用 两颗 *
野指针:小编总结了野指针出现的情况并说明其应对对策
assert 断言:小编带着大家试着怎么用 assert 并体会了 assert 防止野指针的益处
传值调用与传址调用: 理解了 传值 和 传址 调用的不同,并说明分别用于哪些 使用场景 更好
二级指针:二级指针的理解,同时并知晓如何 定义 和 解引用
如果觉得小编写的还不错的咱可支持三关下,不妥当的咱评论区指正
希望我的文章能给各位宝子们带来哪怕一点点的收获就是 小编创作 的最大动力