首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >#C语言——学习攻略:深挖指针路线(二)--const修饰、野指针分析、断言和指针的作用

#C语言——学习攻略:深挖指针路线(二)--const修饰、野指针分析、断言和指针的作用

作者头像
雾忱星
发布2025-12-23 13:20:48
发布2025-12-23 13:20:48
1380
举报
文章被收录于专栏:C++C++

1. const修饰指针

1.1 const修饰变量

--变量是可变的,指针变量也是如此;想要限制变量的修改,就要加上限制,就用到了const修饰;

代码语言:javascript
复制
int main()
{
    const int n = 10;
    n = 20;
	printf("%d\n", n);
	return 0;
}

--可见,const修饰后,变量n无法修改。

--本质上,n还是变量,只是语法层面上限制了n的修改,n变成常变量;

--如果我们绕过n,使用n的地址,就能修改n,但是在打破语法规则

代码语言:javascript
复制
int main()
{
	const int n = 10;
	int* p = &n;
	*p = 20;
	printf("%d\n", n);
	return 0;
}

1.2 const修饰指针变量

--const修饰指针变量可以放在"*"的左边或右边,但起的作用不尽相同;

代码语言:javascript
复制
int* p//无const修饰
const int* p//在左
int* const p//在右

--下面,看代码进行理解:

代码语言:javascript
复制
/代码块1 —— 测试⽆const修饰的情况
void test1()
{
	int n = 10;
	int m = 20;
	int* p = &n;
	*p = 20;//ok
	p = &m; //ok
}

//代码块2 —— 测试const放在*的左边情况
void test2()
{
	int n = 10;
	int m = 20;
	const int* p = &n;
	*p = 20;//no
	p = &m; //ok
}

//代码块3 —— 测试const放在*的右边情况
void test3()
{
	int n = 10;
	int m = 20;
	int* const p = &n;
	*p = 20; //ok
	p = &m; //no
}

//代码块4 —— 测试*的左右两边都有const
void test4()
{
	int n = 10;
	int m = 20;
	int const* const p = &n;
	*p = 20; //no
	p = &m; //no
}

——结论:

  • const在*的左边,修饰指针指向空间中的内容,保证指针指向的内容(*p)无法由指针来改变,但指针本身内容(地址)可变;
  • const在右边,修饰指针变量本身,保证指针变量本身内容(地址)不变,但指针指向的内容(*p)可由指针改变;

2. 野指针

--野指针:指指针指向的位置是不可知的(随机的、不确定的、没有明确限定的),即指向的空间不属于当前程序。

2. 1野指针成因

2.1.1 指针未初始化
代码语言:javascript
复制
int mian()
{
	int* p;//局部变量指针未初始化,默认随机值
	*p = 20;
	return 0;
}
2.1.2 指针越界访问
代码语言:javascript
复制
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i <= sz; i++)
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

--看结果,指针访问超过了数组本身长度,在arr[10]时,指针返回随机值,即野指针。

2.1.3 指针指向的空间释放

--意味着该指针所指向的内存空间已被销毁(或归还给系统),指向对象不存在(生命周期结束)。

代码语言:javascript
复制
int* test()
{
	int n = 100;
	return &n;
//函数返回的是地址
}
int main()
{
	int* p = test();
	printf("%d\n", *p);//无法打印n,函数返回后,局部变量n的生命周期结束
	return 0;
}

--函数调用时,局部变量 n 被分配在栈上;函数返回时,栈帧)被弹出, n的内存被系统标记为可复用。

--有时会“侥幸”输出100,但这是完全不可靠的。后续函数调用会覆盖该内存。

2.2 野指针的规避

2.2.1 指针初始化

--当使用指针时,知道指针指向的的对象就直接赋值相应的地址;不知道,可以初始化为空指针——NULL。

-- NULL 是C语言定义的标识符常量,表示指针不指向任何有效的内存地址,值为0;0表示地址,但是无法使用,会报错。

代码语言:javascript
复制
int main()
{
	int a = 10;
	int* p = NULL;
	return 0;
}
2.2.2 规避指针越界

--一个程序向内存申请了多大的空间,通过指针进行访问也就多大,不能超出范围;比如上面数组的访问就是越界。

2.2.3 指针变量不再使用时,及时置NULL;使用指针前检查有效性

--当指针变量指向一块区域时,可以通过指针访问该区域,当后期不再使用这个指针访问时,就可以把指针置NULL;规则:只要是NULL指针就不去访问;同时注意使用指针时判断是否为NULL。

2.2.4 避免指针返回局部变量的地址

--比如2.1.3,变量已销毁,再返回地址无法找到对象。


3. asser断言

--asser.h头文件定义了宏asser(),用于运行时确保程序符合指定条件,不符合就报错终止;这个宏被称为“断言”。

代码语言:javascript
复制
#include <assert.h>
assert(p != NULL); 

- --当条件为真时:程序继续正常执行;

- --当条件为假时:输出错误信息(包含文件名、行号、失败条件);

--使用assert()的好处:

  • 自动识别标识文件和出问题的行号;
  • 不需更改代码就能开启和关闭assert()机制--当已经排除问题,不再需要断言判断,只要在头文件上方定义宏:#define NDEBUG
代码语言:javascript
复制
#define NDEBUG
#include <assert.h>

--这是程序重新编译,禁用文件中所有的assert()语句;当程序有有类似问题时,可以将宏注释掉。

--当然存在缺点:会降低程序运行效率。

--VS平台,一般在Debug版本使用,利于排查问题;在Release版本会直接优化掉,不影响使用效率。


4.指针的使用和传值/传址调用

4.1 strlen的模拟实现

--函数strlen的作用是求字符串长度,计算 ' \0 ' 之前的字符个数。

代码语言:javascript
复制
size_t my_strlen(const char* str)//const防止通过指针修改指向的内容
{
	size_t count = 0;
	//保护指针
	asser(str != NULL);
	while (*str != '\0')
	{
		count++;
		str++;
		return count;
	}
}
int main()
{
	char arr[] = "abcdef";
	size_t len = my_strlen(arr);
	printf("%d\n", len);
	return 0;
}

8.2 传值调用和传址调用

--那我们对指针的了解不了少,那什么问题才需要指针来解决呢?

--举例——写一个函数,交换两个整形变量的值:

--首先不用指针来写:

代码语言:javascript
复制
//定义函数
void Swep1(int x, int y)
{
	int z = 0;
	z = x;
	x = y;
	y = z;
}
int main()
{
	int a = 10;
	int b = 20;
	printf("交换之前,a = %d,b = %d\n", a, b);
	Swep1(a, b);
	printf("交换之后,a = %d,b = %d\n", a, b);

}

--发现并没有实现想要的功能!为什么呢?

--当调试后,发现形参和实参的地址不同,所以不管函数内参数如何变化都不会影响实参。

--因为形参是实参的临时拷贝(数值),但二者都是独立变量,这就叫做传值调用。

--既然如此,将地址一并传过去呢?就用到了指针:

代码语言:javascript
复制
void Swep2(int* pa, int* pb)
{
	//*pa//a
	//*pb//b
	int z = *pa;//z = a
	*pa = *pb;//a = b
	*pb = z;//b = a
	
}
int main()
{
	int a = 10;
	int b = 20;
	printf("交换之前,a = %d,b = %d\n", a, b);
	Swep2(&a, &b);
	printf("交换之后,a = %d,b = %d\n", a, b);

}

--将变量的地址传给函数,叫做传址调用;

--传址调用,让函数和主调函数之间建立了真正的联系,在函数内部就可以修改主函数的变量;

--在设计函数时,如果只需要主函数变量的值计算就用传值;如果函数涉及到修改主函数变量的值,用传址。

结语:本篇内容就到这里了,主要分享了指针的一些基础内容,后续仍会分享指针的相关知识;指针的内容需要反复研读 ,如果这篇文章对你的学习有帮助的话,欢迎一起讨论学习,你这么帅给个三连吧~~~

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-09-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. const修饰指针
    • 1.1 const修饰变量
    • 1.2 const修饰指针变量
  • 2. 野指针
    • 2. 1野指针成因
      • 2.1.1 指针未初始化
      • 2.1.2 指针越界访问
      • 2.1.3 指针指向的空间释放
    • 2.2 野指针的规避
      • 2.2.1 指针初始化
      • 2.2.2 规避指针越界
      • 2.2.3 指针变量不再使用时,及时置NULL;使用指针前检查有效性
      • 2.2.4 避免指针返回局部变量的地址
  • 3. asser断言
  • 4.指针的使用和传值/传址调用
    • 4.1 strlen的模拟实现
    • 8.2 传值调用和传址调用
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档