当前位置: 首页 > article >正文

【指针(2)-- 使用指针的技巧】

作者主页:lightqjx

本文专栏:C语言


目录

一、const修饰指针

1.const修饰变量

(1)常规操作

(2)特殊操作

2.const修饰指针变量

(1)const放在*号的左边

(2)const放在*号的右边

(3)const放在*号的左边和右边

二、野指针

1.野指针的形成

(1)指针未初始化

(2)指针越界

(3)指向的空间已释放

2.野指针的规避

(1)指针初始化

(2)避免指针越界

(3)指针变量不再使用时,及时置NULL

(4)避免返回局部变量的地址

三、arrert断言

1.assert的作用

2.如何取消使用assert

2.assert的缺点


一、const修饰指针

const的修饰是我们要讲的第一个使用技巧。

const关键字用于声明常量标识不可修改的数据,一般的作用为:替代宏定义的常量,防止函数内部修改参数,区分指针常量和常量指针保护指针指向的数据。

被const修饰的变量,是无法被修改的。若当你要修改它时,编译器就会报错。

1.const修饰变量

(1)常规操作

一般来说,变量是可以被修改的,如:

#include<stdio.h>
int main()
{
	int n = 10;
	n = 20;
	printf("n = %d\n", n);
	return 0;
}

运行结果:

n原本为10,但通过n=20让n改为了20。

若在上述代码中让const来修饰n,即:

const int n = 10;

则会出现错误:

说明这时的 n 的值是无法被修改的。

(2)特殊操作

但是如果运用指针变量来操作,就可以绕过n,通过n的地址来修改,却能够改变了。如:

#include<stdio.h>
int main()
{
	const int n = 10;
	int* p = &n;
	*p = 20;//使用指针进行修改
	printf("n = %d\n", n);
	return 0;
}

运行结果:

虽然这里可以绕过n修改变量n,但是要记住:这样做并不合理。正确的做法应让指针p也无法进行修改

2.const修饰指针变量

当const用来修饰指针变量时,需要分三种情况,每种情况都不相同。即:

const int n = 10;
int* p = &n;//无const修饰
const int*  p = &n;//const放在*左边
int* const p = &n;//const放在*右边

(1)const放在*号的左边

  • const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本身的内容可变。

(2)const放在*号的右边

  • const如果放在*的右边,修饰的是指针变量本⾝,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变。

(3)const放在*号的左边和右边

  • const如果同时放在*的左边和右边,修饰的既有指针变量本身,也有指针指向的内容。保证指针指向的内容不能通过指针来改变,也保证了指针变量的内容不能修改。

        当然,一般来说我们并不会这样将const同时放在*的左边和右边。因为,被这样修饰的指针其实就没有什么用处了,这样不如不创建它呢。

二、野指针

野指针就是我们要理解的第二个使用技巧

在 C 语言中,野指针是指指向无效内存地址的指针(例如指向已被释放的内存,或未经初始化的随机地址)。使用野指针会导致严重的后果,如程序崩溃、数据损坏、调试困难等问题,所以一般当我们要在使用指针时,应该要正确识别野指针以及避免使用到野指针

简单来说,野指针指向了不可知的的位置,而这个位置又是随机的、不正确的、没有明确限制的。

1.野指针的形成

要识别野指针,就要知道它是怎么形成的?也就是也野指针的成因是什么?

一般来说,野指针的成因有三种:

(1)指针未初始化

当我们声明指针变量时,有时会忘记对指针变量赋值就去使用了它,就会产生野指针。如:

#include <stdio.h>
int main()
{
	int* p;
	*p = 10;//未对指针变量赋初值就使用了它
	return 0;
}

这里的p就是一个野指针,在编译器中这样的程序也会报错,即:

导致了程序无法运行。

(2)指针越界

当我们用指针来访问一个数组时,有时就会出现指针的越界访问,这是也会产生野指针。因为当指针越界之后,该指针所指向的地址时不确定的,如果这个地址有我们存的数据,那么使用越界的指针进行访问时就会覆盖这个数据,造成数据丢失

如:

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	int* p = arr;
	for (i = 0; i <= 10; i++)
	{
		//循环运行了11次,在第11次时p已经越界
		*p = 1;
		p++;
	}
	return 0;
}

当p指向越界的数组地址进行操作时,这时p就是野指针,调试上述代码会出现以下错误异常:

(3)指向的空间已释放

比如当指向局部变量的指针,函数返回局部变量的地址后,该变量超出作用域,导致指针失效。如:

#include <stdio.h>
int* test() 
{
    int x = 10;
    return &x; // 返回后,x的内存被回收
}
int main()
{
    int* p = test(); // p为野指针
    return 0;
}

p接收到了一个已释放的内存的空间地址,这是p就是野指针

2.野指针的规避

有产生野指针的形成,当然也有规避野指针的方法,这种方法一般有4种。

(1)指针初始化

我们知道指针未初始化会产生野指针,所以每当我们声明一个指针变量时,就应该将它进行赋值。

如果知道它指向哪里,就直接对它赋值这个地址;

如果不知道它指向哪里,那么就对它赋值空指针(NULL)

NULL 是C语言中定义的一个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错。

在C语言的定义中:

#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif

指针初始化:

#include <stdio.h>
int main()
{
	int* p = NULL;
	*p = 10;
	return 0;
}

(2)避免指针越界

之前我们知道,当我们访问数组时,如果指针指向越界的数组,就会产生野指针。所以我们需要注意避免这种情况。

当我们在空间中申请的哪些空间,那么通过指针也就只能访问哪些空间,不能超出范围进行访问,超出了就是越界访问。

(3)指针变量不再使用时,及时置NULL

我们知道只要是NULL指针就不去访问

所以当指针变量指向一块空间区域的时候,我们可以通过指针访问该区域,后期不再使用这个指针访问空间的时候,我们可以把该指针置为NULL。

如果我们后续再使用这个指针变量时,就需要在使用指针之前可以判断指针是否为NULL。

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	int* p = arr;
	for (i = 0; i < 10; i++)
	{
		*p = 1;
		p++;
	}
	//跳出循环时,p就已经越界,可以把p置为空指针(NULL)
	p = NULL;
	//在下次如果要使用p时,需要判断p是否为NULL

	p = &arr[0];//让p重新获得地址
	if (p != NULL)//判断
	{
		//判断到p不为空(NULL),可以使用p
		*p = 2;
	}
	return 0;
}

(4)避免返回局部变量的地址

在上述理解野指针形成时,我们知道指针指向空间已经释放的地址时,就是野指针。

所以我们在使用函数传递返回值时,需要注意一般不要将返回值设做地址(即不要返回地址)。

三、assert断言

assert的断言使用便是我们要理解的第三个使用技巧

assert()是头文件assert.h中定义的一个。这个宏常常被称为 “ 断言 ”。  它1使用方法如下:

assert( 表达式 );

1.assert的作用

当assert的括号中的表达式中的结果为真(返回值非零)时,则这条代码啥也不干,程序继续正常执行。

若assert的括号中的表达式中的结果为假(返回值为零)时,则程序立即终止,并显示出错误信息,显示没有通过的表达式,以及包含这个表达式的文件名行号,帮助我们快速定位问题。

如:

#include <stdio.h>
#include <assert.h>
void test(int* p)
{
	assert(p!= NULL);//表达式为假,程序终止
}
int main()
{
	int n = 10;
	int* p = NULL;
	test(p);//传递空指针NULL
	printf("%d\n", n);
	return 0;
}

运行结果:

因此,我们在判断空指针时,就可以使用它来代替   if(p != NULL)   的条件判断语句:

if (p != NULL)
{
	//......
}

不仅仅只局限与指针,当然还可以判断许许多多的表达式。

2.如何取消使用assert

在有一个程序中,若存在 “  assert(表达式)  ” 的语句,当你已经确定在该语句中的表达式没有问题,已经不需要再做断言时,这是就将要屏蔽它了。

你可以对它进行注释的方法来屏蔽,也可以通过删除它的方法来屏蔽它。

但是,如果在该程序中的assert语句较多时,就不能用注释或删除的方法来屏蔽它了。这就需要另一种方法:在 “   #include<assert.h>   ” 的前面  定义一个宏NDEBUG  就可以了。即:

#define NDEBUG      //该宏要在“#include <assert.h>”之前定义
#include <assert.h>

这样就可以让该程序中的所有assert(表达式) 无效了。

若程序又出现了问题,则可以再将 “   #define NDEBUG   ” 注释或删除,这样就可以重启程序中的所有 “  assert(表达式)  ”  语句了。

2.assert的缺点

知道了assert的使用以及屏蔽操作,当然也必须要知道它的缺点:

  1. 当我们在运用assert()时,就引入的额外的检查,增加了程序的运行时间。
  2. 一般我们只能在Debug版本中使用,而不能在Release版本中使用。比如,在VS编译器环境中,在 Release 版本中,assert直接就是被优化掉了,所以无法使用。

  • 在次感谢各位读者的欣赏!

http://www.kler.cn/a/596553.html

相关文章:

  • 人工智能在智能交通中的应用:以L4级无人电动物流拖车为例
  • 解析DeepSeek的技术内核:混合专家架构如何重塑AI效能
  • 【第16章】亿级电商平台订单系统-部署架构设计
  • 蓝桥杯备考:模拟题之神奇的幻方
  • 2025年渗透测试面试题总结- shopee-安全工程师(题目+回答)
  • asp.net core mvc模块化开发
  • 网络知识编-数据链路层(以太网 局域网通信 ARP协议 ARP 欺骗 DDos 攻击)
  • Linux系统管理与编程07:任务驱动综合应用
  • 蓝牙AOA定位技术:开启车辆精准定位的智能新时代
  • MySQL数据库精研之旅第二期:库操作的深度探索
  • python:AI+ music21 构建 LSTM 模型生成爵士风格音乐
  • 【Java篇】静动交融,内外有别:从静态方法到内部类的深度解析
  • 单表达式倒计时工具:datetime的极度优雅(DeepSeek)
  • C++异常处理完全指南:从原理到实战
  • vue3配置代理实现axios请求本地接口返回PG库数据【前后端实操】
  • 红宝书第八讲:箭头函数与高阶函数:厨房工具与智能菜谱的对比
  • 3月22日星期六今日早报简报微语报早读
  • java项目之基于ssm的游戏攻略网站(源码+文档)
  • RHCE 使用nginx搭建网站
  • CH32V208蓝牙内部带运放32位RISC-V工业级微控制器