第三章 C指针重要主题与使用技术

第三章 C指针重要主题与使用技术



前言

在C语言中指针的重要性不言而喻,但是在很多时候他又是一把双刃剑。

一方面,指针构建数据结构操作内存的精确而高效的工具。
另一方面,他又很容易误用,从而产生不可预知的软件BUG。

无论如何,想要有效的使用C语言,我们必须对指针有透彻的了解。
下面介绍指针的几个重要主题,并同时介绍几种使用指针的技术。


一、指针基础

一方面,知道理解指针的最佳方法:画图表
另一方面,学习在使用基本指针的过程中如何避免空指针产生。

一个指针其实就是一个变量,他存储数据在内存中的地址,而不是存储数据本身。也就是说,指针包含内存地址。在C语言中,我们无法改变的一个事实就是指针能够指向一个无效的地址。指向无效地址的指针有时被称为悬空指针

可能产生悬空指针的编程错误有:

将任意的整型变量强制转换为指针变量;
操作超出数组边界的指针;
释放一个或多个仍被引用的指针。

二、存储空间分配

存储空间分配是指在内存中预留存储空间的过程,理解指针与内存分配是密不可分的。当通过指针访问内存时,就如同一个虚拟的菜谱一样,指针(内存地址)对应彩名,其所指向的内存空间存储的数据对应实际的菜。

在C语言中声明一个指针时,与声明其他类型的变量类似。一定量的存储空间会分配给这个指针,一般情况下指针会占用一个机器字长的存储空间,但有些时候他们的大小也会有所不同。

必须记住一点是:当声明一个指针时,仅仅只是为指针本身分配了空间,并没有为指针所引用的数据分配了空间。

而数据分配存储空间有两种方法:

一种是直接声明一个变量;
另一种是在运行是动态的分配存储空间。

内存泄漏问题的产生是由于动态分配了内存空间,但从未释放他(甚至在程序不再使用此数据空间时都不释放他)造成的。

特别是在重复执行代码时,这种泄漏问题会表现的尤为严重。

三、数据集合与指针的算术运算

在C语言中,数据集合主要指结构和数组。指针的算术运算定义指针的运算规则。
指向结构的指针对于建立数据结构起着至关重要的作用。在C语言中,数组和指针一样,都是以指针的算术运算的方法进行运算的。
指针在C语言中最常见用途就是用来引用数据集合。数据集合是由多个相关联的元素构建的数据。

结构通常是由各种各样的有序的元素组成的,从而他可以被看作单个连续的数据结构。结构指针是构建一个数据结构的重要组成部分。结构使我们把数据捆绑在一起,指针使我们能够让这些捆绑包在内存中一个一个连接起来。用这些连接起来的结构,我们可以对他们加以组织并用来解决实际的问题。

结构不允许包含自身的实例,但可以包含指向自身实例的指针。
这种编程思想非常重要,因为很多结构数据都可能是由它自身的结构变量所组成。

typedef struct tag_listElmt{
	void                *data;
	struct tag_listElmt *next;//结构不允许包含自身的实例,但可以包含指向自身实例的指针。
}listElmt_t;

数组是在内存中连续排列的同类元素的序列,在C语言中,数组和指针密不可分。

事实上,当一个数组标识符在表达式中出现时,C语言显然会把数组转换为一个指向数组第一个元素的固定指针。

考虑到这一点,以下两个函数是等价的。

int fun(void)
{
	int a[10],*iptr;
	itpr=a;
	itpr[0]=10;

	return 0;
}

int fun(void)
{
	int a[10],*iptr;//当一个数组标识符在表达式中出现时
	itpr=a;
	*itpr=10;//把数组转换为一个指向数组第一个元素的固定指针。

	return 0;
}

a[i]与*(a+i)表达的意思相同。

当对指针加一个整数 i 时,实际得到了一个地址,这个地址等于a所在地址加上a的数据类型所占字节数乘以 i 得到。而不是简单的在a所在的地址加上i个字节。当从指针中减去一个整数时也是执行类似的操作。

这样我们也解释了为什么数组的索引从零开始,因为数组的第一个元素在位置0.

四、作为函数参数的指针

在C语言的函数调用中指针起着至关重要的作用,最重要的是指针支持参数作为引用传递给函数。

按引用传递参数时,当函数改变此参数时,这个被改变的参数的值一直存在,甚至函数退出后都仍然存在。
相对而言,按值调用传递函数时,此时值的改变只能持续到函数返回时。

无论是否要改变函数的输入输出参数,使用指针传递大容量复杂的函数参数也是一种高效的手段。这种方法高效的原因就在于我们只是在传递一个指针而不是一个数据的完整副本到函数中,这样可以大大地节省内存空间。

通过这种方式,可以按照传递引用的方法传递函数参数。

在C语言中,传递数组或者大型结构时,使用指针是一种普遍而高效的方法。

作为函数参数的指针和指向指针的指针都涉及指针的使用,但它们有一些区别。

  1. 指针作为函数参数:当将指针作为函数参数传递时,传递的是该指针所指向的内存地址。函数可以通过该指针修改指针指向的数据,但不能修改指针本身的值。

示例代码:

void modifyPointer(int* ptr) {
    *ptr = 10;  // 修改指针指向的值
    ptr = nullptr;  // 不能修改指针本身的值
}

int main() {
    int num = 5;
    int* ptr = #
    modifyPointer(ptr);

    // num 的值被修改为 10,ptr 仍然指向 num
    return 0;
}

总结:通过指针作为函数参数,可以修改指针指向的值;通过指向指针的指针作为函数参数,可以修改指针指向的值以及指针本身的值。

五、指向指针的指针

这是一种指向指针的指针,而不是指向具体变量的指针。

指向指针的指针作为函数的参数来传递是是非常普遍的。

作为函数参数的指针和指向指针的指针都涉及指针的使用,但它们有一些区别。
2. 指向指针的指针作为函数参数:当将指向指针的指针作为函数参数传递时,传递的是指针的地址。函数可以通过该指向指针的指针修改指针的值,从而改变指针所指向的内存地址和数据。

示例代码:

void modifyPointerPointer(int** ptrPtr) {
    **ptrPtr = 10;  // 修改指针指向的值
    *ptrPtr = nullptr;  // 修改指针本身的值
}

int main() {
    int num = 5;
    int* ptr = #
    int** ptrPtr = &ptr;
    
    modifyPointerPointer(ptrPtr);

    // num 的值被修改为 10,ptr 的值被修改为 nullptr
    return 0;
}

总结:通过指针作为函数参数,可以修改指针指向的值;通过指向指针的指针作为函数参数,可以修改指针指向的值以及指针本身的值。

六、泛型指针与类型转换

泛型指针和类型转换是用来跨界和覆盖C语言的类型系统途径。

泛型指针指向某一数据而不需要理会数据的具体类型

类型转换允许临时改变变量的数据类型,
泛型指针可以转换成任何类型的指针。

#include<stdio.h>
#include<stdint.h>
#include<stdlib.h>
#include<string.h>

int swap(void *pva11,void *pva12,uint8_t size)
{
	if((NULL==pva11)||(NULL==pva12)||(size==0))
		return -1;
	void *ptmp=NULL;
	
	ptmp=malloc(size);
	if(NULL==ptmp)
		return -2;
	
	memcpy(ptmp,pva11,size);
	memcpy(ptmp,pva12,size);
	memcpy(pva12,ptmp,size);
	free(ptmp);
	ptmp=NULL;

	return 0;
}

七、函数指针

函数指针指向可执行代码段或者指向调用可执行代码段的信息块的指针,而不是指向某种具体的数据。

函数指针将函数当作普遍数据那样存储和管理。

声明一个函数指针与声明一个函数非常类似,只是在函数名之前有一个表示指针的星号(*)。并且函数名与星号用圆括号括起来。
例如下面这段代码中,init被声明为一个函数指针,无函数参数,同时无返回值。

void (*init)(void);//init被声明为一个函数指针,无函数参数,无返回值。

要执行一个函数指针所引用的函数,只需要正常调用普通函数的地方调用函数指针。
函数指针的一个重要用途是将函数封装到数据结构中,使用指针把函数另存为数据结构的一部分是C语言一种非常好的特性,因为它可以使数据结构或者函数变得更具通用性。

#ifndef _TASK_H_
#define _TASK_H_

#include<stdint.h>

//任务结构
typedef struct{
	uint8_t run;         //程序运行标记:0,不运行;1,运行
	uint16_t timer;      //定时器,单位:ms
	uint16_t itv_time;   //任务运行间隔时间,单位:ms
	void (*hook)(void);  //要运行的任务函数
}task_params_t;

//任务清单
enum TASK_LIST{
	TASK_TASK1,//任务1
	TASK_TASK2,//任务1
	TASK_TASK3,//任务1
	
	TASK_NUM   //任务总数
};

//函数声明
void task_remarks(void);
void task_proc(void);

#endif

总结

简单讲解了C语言指针的几个重要主题,并同时介绍几种使用指针的技术。
原文参考:
https://www.bilibili.com/video/BV19d4y1c7bE/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=1eced22eb8e5470b4cdecdfe25dc2cb8

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值