🏔️山高万仞,只登一步!
文章目录
- [一. const修饰指针](#一. const修饰指针)
-
- [1.1 const修饰变量](#1.1 const修饰变量)
- [1.2 const修饰指针变量](#1.2 const修饰指针变量)
- [二. 野指针](#二. 野指针)
-
- [2.1 野指针成因](#2.1 野指针成因)
-
- 2.1.1指针未初始化
- [2.1.2 指针越界访问](#2.1.2 指针越界访问)
- [2.1.3 指针指向的空间释放](#2.1.3 指针指向的空间释放)
- [2.2 如何规避野指针](#2.2 如何规避野指针)
-
- [2.2.1 初始化指针](#2.2.1 初始化指针)
- [2.2.2 小心指针越界](#2.2.2 小心指针越界)
- [2.2.3 指针不用时要置NULL](#2.2.3 指针不用时要置NULL)
-
- [2.2.4 避免返回局部变量的地址](#2.2.4 避免返回局部变量的地址)
- [三. assert断言](#三. assert断言)
- [四. 指针的使用和传值调用](#四. 指针的使用和传值调用)
-
- [4.1 strlen的模拟实现](#4.1 strlen的模拟实现)
- [4.2 传值调用和传址调用](#4.2 传值调用和传址调用)
- 总结
前面介绍了指针的一些基本概念,本节介绍const修饰指针,野指针,assert断言,指针的调用方法
一. const修饰指针
const是常属性,不能改变
1.1 const修饰变量
变量是可以修改的,如果用const修饰变量,那么变量就不能被修改。
c
#include<stdio.h>
int main()
{
int m = 0;
m = 20;//可以修改
const int n = 0;
n = 20;//不可以修改
return 0;
}
n还是变量,只不过不能修改是在语法层面
上做了修饰是常变量
但是如果把变量的地址放在一个指针变量中,绕过n,使用n的地址对n进行修改,那么通过指针是可以改变变量的值
c
int main()
{
const int n = 0;
int* pn = &n;
*pn = 20;
printf("%d\n", n);
return 0;
}
结果发现n的值被改变了,但是我们不想把变量的值改变,那么就应该用const限制指针的修改范围。
1.2 const修饰指针变量
const修饰指针变量可以放在*
的左边,或者右边,结果是不一样的
int * p
int const *p
int * const p
int const * const p
const放在*的左边,修饰限定的是指针p所指向的对象的值不能修改,但是可以修改所指的对象
c
int main()
{
int n = 20;
int const* pn = &n;
*pn = 10;//err
printf("%d\n", *pn);
return 0;
}
const放在*的右边,const修饰限定的是指针p,不能改变所指向的对象,但是所指对象的值可以改变。
c
int main()
{
int n = 20;
int m = 300;
int * const pn = &n;
*pn = 10;
printf("%d\n", *pn);
pn = &m;//err
printf("%d\n", *pn);
return 0;
}
两边都有const
那么结果显现既不能改变指针所指对象,也不能改变所指对象的值
画图解释
二. 野指针
野指针就是指针所指向的空间不确定
2.1 野指针成因
2.1.1指针未初始化
c
int main()
{
int* p;//err 局部变量指针不初始化为随机值。
*p = 20;//p为野指针
return 0;
}

2.1.2 指针越界访问
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 < 11; i++)
{
printf("%d\n", *p);
p++;
}
return 0;
}

2.1.3 指针指向的空间释放
c
int* test()
{
int n = 100;
return &n;
}
int main()
{
int* p = test();
printf("hehe\n");
printf("%d\n", *p);
return 0;
}

局部变量在函数调之后会销毁,当*p找到n的空间的时候,里面的内容是不对的,此时p为野指针
2.2 如何规避野指针
2.2.1 初始化指针
如果知道指针指向的哪里的空间就直接赋值地址,如果不知道指向哪里就赋值NULL
,NULL是C语言中定义的一个标识符常量,值为0,0也是地址但是这个地址是无法使用的,读这个地址会报错
c
int *p=NULL;
初始化:
c
int main()
{
int num = 1000;
int* p1 = #
int* p2 = NULL;//p2是野指针
return 0;
}
2.2.2 小心指针越界
程序申请哪些空间就访问哪些空间,不能超出访问空间范围,即不能越界访问
2.2.3 指针不用时要置NULL
当指针指向一块空间的时候,我们可以通过指针访问该空间,当不再访问这片空间的时候,及时把该指针置NULL。
只要是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++)
{
printf("%d ", *(p++));
}
p = NULL;
p = &arr[0];//让p重新获得地址
if (p==NULL)
{
printf("是空指针\n");
}
else
{
printf("不是空指针\n");
}
return 0;
}
2.2.4 避免返回局部变量的地址
局部变量在函数调用结束后就会销毁,如果返回局部变量的地址就会导致指针指向错误的空间,return返值其实是先放在寄存器中,等到回到主函数时再把寄存器中的值,赋值给要打印的变量。
c
int* test()
{
int n = 120;
return &n;
}
int main()
{
int r = test();
printf("%d\n", r);
return 0;
}
三. assert断言
assert.h
文件定义宏
,在运行时符合条件就运行,不符合条件就报错,这个 宏 称为断言
assert(p!=NULL);
判断是否等于NULL,如果不等于NULL就继续运行,如果等于就终止运行,并且给出报错信息。
c
#include<assert.h>
int main()
{
int a = 10;
int* pa = NULL;
assert(pa != NULL);
printf("%d\n", *pa);
return 0;
}

c
#include<assert.h>
int main()
{
int a = 10;
int* pa = NULL;
pa = &a;
assert(pa != NULL);
printf("%d\n", *pa);
return 0;
}

assert()宏接受一个表达式 作为参数,如果表达式为真,返回非零值,程序继续运行,如果表达式为假,返回值为0,assert()就会报错,错误会在屏幕上输出一条错误信息:显示没有通过的表达式,以及包含这个表达式的文件名和行号 。
assert()好处:
1.能自动识标识文件和出问题的行号
2.有一种无需修改就能开启和关闭assert()的机制
#define NDEBUG
#include<assert.h>
在头文件前面加一句#define NDEBUG
作用相当于不在断言,把assert关掉了
但是,必须确定程序没有问题。
如果程序有问题可以把#define NDEBUG
注释掉,这用就可以重新启动assert()
assert()缺点
引入了额外审查,会导致程序运行时间增加
一般在Debug环境中使用,在Release版本中assert()是不起作用的。
四. 指针的使用和传值调用
4.1 strlen的模拟实现
函数原型
size_t strlen(const char* str)
str接受字符串的起始地址,然后统计字符串中\0
之前的个数,返回最终长度
模拟实现:
c
#include<stdio.h>
int my_strlen(char* str)
{
int count = 0;
while (*str)
{
count++;
str++;
}
return count;
}
int main()
{
char arr[10] = { "abcdefg" };
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
优化
c
#include<stdio.h>
#include<assert.h>
int my_strlen(const char* str)
{
int count = 0;
assert(str);
while (*str)
{
count++;
str++;
}
return count;
}
int main()
{
char arr[10] = { "abcdefg" };
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
4.2 传值调用和传址调用
有一些问题必须要用指针解决
例:
c
void Swap(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和b的值:a=%d b=%d\n", a, b);
Swap(a, b);
printf("交换之后a和b的值:a=%d b=%d\n", a, b);
return 0;
}
结果:
x,y是独立的空间,形参的改变不会影响实参的结果
具体参考C语言------函数(超详细分析)中,实参和形参的关系
想要通过形参来改变实参可以借助指针
c
void Swap(int* px, int* py)
{
int tmp = *px;
*px = *py;
*py = tmp;
}
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
printf("交换之前a和b的值:a=%d b=%d\n", a, b);
Swap(&a, &b);
printf("交换之后a和b的值:a=%d b=%d\n", a, b);
return 0;
}
传址调用 可以把函数和主函数真正的建立联系
只是需要主调函数中的变量实现计算,可以用传值调用
函数内部需要修改主调函数的值,就需要传址调用
总结
