深入理解指针(2)

文章目录

这里是think的博客

希望可以一起交流知识,一起think

今天我们来学习**指针(2)**吧

一起来think吧

const修饰指针

const修饰变量

c 复制代码
#include <stdio.h>
int main()
{
int m = 0;
m = 20;//m是可以修改的
const int n = 0;
n = 20;//n是不能被修改的
return 0;
}

加了const,n就不能修改了,此时的n是变量,但是不能修改,具有常性,有被叫做常变量,即具有常性的变量,const是在语法上限制了我们对变量的修改。

但是如果你用指针找到了n的值,是可以改变的,但是这是未定义行为,最好不要这样。

const修饰指针变量

int * p;//没有const修饰?

int const * p;//const 放在*的左边做修饰

int * const p;//const 放在*的右边做修饰
很好理解的,1.const在*之前就是对*p进行限制,即p指向的那个变量的值不可改变。

2.const在*之后就是对p进行限制,即p的指向不可改变。

我们可以看到p1和p3中指向的变量值不可改变,而p2是指向不可改变。

野指针

概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

野指针成因

指针未初始化

c 复制代码
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}

指针越界访问

c 复制代码
#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;
}

指针指向的空间释放

c 复制代码
#include <stdio.h>
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}

如何规避野指针

指针初始化

当创建一个指针的时候就给他初始化,如果现在创建的指针,现在还用不到,就int *p =NULL;即可

NULL是什么?

#ifdef __cplusplus

#define NULL 0

#else

#define NULL ((void *)0)

#endif

在C++中就是0,在C语言中是被强制类型转换为void*的0。

c 复制代码
#include <stdio.h>
int main()
{
int num = 10;
int*p1 = &num;
int*p2 = NULL;
return 0;
}

小心指针越界访问

一个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问。

指针使用后置为NULL,使用前检验是否为NULL

c 复制代码
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置为NULL
p = NULL;
//下次使⽤的时候,判断p不为NULL的时候再使⽤
//...
p = &arr[0];//重新让p获得地址
if(p != NULL) //判断
{
//...
}
return 0;
}

不要返回局部变量的地址

看看上面的第三个例子即可,局部变量是出来作用域就会被销毁的。

assert断言

assert.h 头文件定义了宏 assert() ,用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为"断言"。

assert(p != NULL);
if(p!=NULL){

...

}

else{

...

}

这里有两种验证方式,assert是比较暴力的,直接报错,并将assert中的内容,当前文件的绝对目录和行号都打印出来,非常便于寻找bug。

if,else显然是比较温和的,出错了,程序也不会终止,但是如果不在else中写提示信息的话,代码量如果多了,bug就会比较难找。


并且assert是可以关闭的,当bug被排完了以后,assert又写了很多个的时候可以在#include <assert.h>之前加上这句代码

#define NDEBUG

#include <assert.h>

即可直接关闭assert断言,这句的意思就是不要debug了,bug都找到了,不用assert了。

有了这一句代码,编译器就直接跳过了这个assert,不会执行了,当然release的时候assert也是不管用的,就算不加#define NDEBUG ,因为release的时候就代表debug已经完成了。

为什么要禁用?

因为使用assert引入了额外的检查,增加了程序的运行时间。

指针的使用和传址调用

strlen的模拟实现

库函数strlen的功能是求字符串长度,统计的是字符串中 \0 之前的字符的个数。

函数原型如下:

size_t strlen ( const char * str );

c 复制代码
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;
}

这里我们在参数上加了const,防止函数内部修改我们的str,这个函数逻辑上是不能修改我们的str的,又加了断言,这两步增强了我们程序的健壮性。

传值调用和传址调用

有没有什么情况是必须传地址的呢?

c 复制代码
#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;
}

这里如果传的是值的话,形参的改变是影响不了实参的,形参是实参的临时拷贝。

如果想深入了解一下为什么是这样的,可以看看我的之前的博客函数栈帧的创建和销毁

如果传的是指针的话,就可以通过指针来访问要交换的值,以达到交换的目的。

总结

谢谢观看!

相关推荐
iiiiyu3 小时前
面向对象和集合编程题
java·开发语言·前端·数据结构·算法·编程语言
geovindu3 小时前
go: Read-Write Lock Pattern
开发语言·后端·设计模式·golang·读写锁模式
Full Stack Developme4 小时前
JDK 发展历史
java·开发语言
我不是懒洋洋4 小时前
从零开始实现一个简单的神经网络:C语言版
c语言
程序员榴莲5 小时前
Python 中的 @property:像访问属性一样调用方法
开发语言·前端·python
百万老师5 小时前
自然语言编程时代,如何零基础学习掌握嵌入式编程
c语言·单片机·嵌入式硬件·学习·ai全流程闭环开发
sycmancia5 小时前
Qt——拖放事件深度剖析
开发语言·qt
坐吃山猪5 小时前
【Nanobot】README09_LEVEL4 添加新聊天渠道
开发语言·网络·python·源码·nanobot
shehuiyuelaiyuehao5 小时前
算法27,二维前缀和
开发语言·python·算法