【C进阶】指针的高级话题

news2024/11/24 9:46:11

文章目录

  • :star:1. 字符指针
  • :star:2. 指针数组
    • 2.1 指针数组的定义
    • 2.2 指针数组的使用
  • :star:3. 数组指针
    • 3.1 数组的地址
    • 3.2 数组指针的使用
  • :star:4. 数组参数和指针参数
  • :star:5. 函数指针
    • 5.1 函数名和函数的地址
    • 5.2 练习
  • :star:6. 函数指针数组
    • 6.1 转移表
  • :star:7. 指向函数指针数组的指针
  • :star:8. 回调函数
  • :star::star:总结(思维导图)

回顾

在指针初阶时我们已经见过指针了,我们来回顾一下指针的几个知识点
1.指针是一个变量,用来存放的地址,地址唯一标识了一块空间
2.指针的大小是4/8个字节(32/64)平台
3.指针的类型决定了指针± 整数向后跳过多少个字节以及对指针解引用时的访问权限有多大
4.指针可以进行算术运算和关系运算’
5.无法访问野指针

本章重点

1.字符指针
2.指针数组
3.数组指针
4.数组和指针作为参数
5.函数指针
6.函数指针数组
7.指向函数指针数组的指针
8.回调函数

⭐️1. 字符指针

整形指针是一个指向整形的指针变量
浮点型指针是一个指向浮点型的指针变量
字符指针是一个指向字符的指针变量,字符指针的值是字符变量的地址

定义指针变量有两种方式

  • 将单个字符变量的地址赋给字符指针
  • 将字符串常量第一个元素的地址赋给字符指针
#include <stdio.h>
//字符指针的定义
int main()
{
	char* p;
	char a = 'a';
	p = &a;
	p = "abc";	//表示将字符串常量的第一个字符的地址赋给p
	char str[] = "abc";
	p = str;	//表示将字符数组首元素的地址赋给p
	return 0;
}

注:当字符串常量出现在表达式右侧时,字符串常量的值在某些情况下是一个字符地址,该地址是字符串常量首元素的地址
例如在上述代码中,p = "abc",此时字符串常量的值是’a’的地址
但是在char str[] = "abc";,此时字符串常量不是地址,而是数组的元素等价于char str[] = {'a', 'b', 'c', '\0'}

在C语言中,当字符串常量作为一个地址时,操作系统会在静态存储区给字符串常量分配一个地址并将字符串的值置为首元素的地址
例如p = "abc"操作系统首先给字符串常量"abc"在静态存储区分配地址,并且将a的地址赋给变量p,不可以通过解引用p修改p指向的字符串常量
在这里插入图片描述

⭐️2. 指针数组

整形数组中数组的元素是整形
字符数组中数组的元素是字符
指针数组中数组的元素是指针

2.1 指针数组的定义

在C语言和C++等语言中,数组元素全为指针变量的数组称为指针数组,指针数组中的元素都必须具有相同的存储类型指向相同数据类型的指针变量

int main()
{
	int* arr[10];
	return 0;
}

[]的优先级比*高,所以arr和[]结合表示arr是一个数组

arr是一个一维数组名,数组元素是指向整形的指针
指针数组名是首元素的地址,首元素又是指针类型的变量,所以指针数组名是指针变量的地址及二级指针,需要用二级指针变量接受指针数组名

2.2 指针数组的使用

指针数组可以在以下几方面使用

  1. 字符串数组:在C语言中,字符串是一个以空字符(‘\0’)结尾的字符数组,通过指针数组可以方便地存储和处理多个字符串。例如,定义一个字符串数组char *str[3],可以存储三个字符串,并通过指针变量访问它们。
  2. 多维数组:指针数组可以用来实现多维数组的功能,例如,我们可以定义一个指针数组int *p[3],每个元素指向一个整型数组,就可以实现一个3行5列的二维数组。
char a[3][8]={"gain","much","strong"};
char *n[3]={"gain","much","strong"};

在这里插入图片描述
可以看出来,定义二维数组时所有的字符串是连续存放的,但是定义指针数组时,数组元素指向的字符串不一定是连续存放的

使用指针数组的优点是

  • 指针数组中每个元素所指的字符串不必限制在相同的字符长度
  • 访问指针数组中的一个元素是用指针间接进行的,效率比下标方式要高
int main()

{
	int i;
	char* pch[6] = { "妹","妹","你","坐","船","头" };
	for (i = 0; i < 6; i++)
	{
		printf("%s, ", pch[i]);
	}
	printf("\n");
	for (i = 5; i >= 0; i--)
	{
		printf("%s\n", pch[i]);
	}
	return 0;
}

在这里插入图片描述

在访问元素时,指针数组名可以当成二维数组名来使用

在这里插入图片描述


⭐️3. 数组指针

指向整形的指针叫做整形指针
指向字符的指针叫做字符指针
指向数组的指针叫做数组指针

数组也有地址,那么数组的地址就可以用数组指针来存放

int main()
{
	int arr[10] = { 1, 2, 3, 4,5 ,6 , 7, 8, 9 ,10 };//定义了一个有十个元素的数组
	int(*parr)[10] = &arr;//定义了一个数组指针,该指针指向的是有10个int型的数据
	return 0;
}

3.1 数组的地址

我们知道整形的地址是第一个字节的编号
那么数组的地址是否是第一个元素的地址呢?
在这里插入图片描述
我们发现数组的地址和数组第一个元素的地址在上是相等的,
我们现在通过分别让它们±整数,观察它们的含义是否相等
在这里插入图片描述

指针类型的含义决定了指针±整数向后一步跨度有多大,因此数组的地址和数组首元素的地址值一样,含义不同,arr+1代表向后跳过一个整形的大小,&arr+1代表向后跳过整个数组的大小

3.2 数组指针的使用

我们先来看一个打印二维数组的函数

//数组指针的使用
void PrintArr1(int arr[][5], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

注:因为二维数组可以看成数组元素是一维数组的数组,因此二维数组的行下标二维数组名结合就是一个一维数组,而二维数组名又是首元素的地址即&arr[0],arr[0]又是一个一维数组,因此二维数组名是一个数组指针指向的数组元素的个数就是二维数组的列下标,这也是为什么二维数组传参时列下标不能省略的原因

上述代码还可以写成这种形式

void PrintArr2(int(*arr)[5], int row, int col)
{
	for (int i = 0; i < row; i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

我们再来探讨以下表达式arr[i][j]的含义
在这里插入图片描述

数组指针可以当作二维数组名来使用

⭐️4. 数组参数和指针参数

这里将前面讲的内容做一个大致的总结

当形参是数组时

形参是一维数组

实参可以是一级指针
实参可以是普通变量的地址
实参可以是一维数组名

形参是二维数组

实参可以是二维数组
实参可以是数组指针

当实参是数组时

实参是一维数组

形参可以是一维数组名
形参可以是一级指针

实参是二维数组

形参可以是二维数组名
形参可以是数组指针

形参是指针

形参是一级指针

实参可以是一指针
实参可以是普通变量的地址
实参可以是一维数组ming

形参是二级指针

实参可以是二级指针变量
实参可以是一级指针变量的地址
实参可以是指针数组名

实参是指针

实参是一级指针

形参可以是一级指针
形参可以是一维数组名

实参是二级指针

形参可以是二级指针变量
形参可以是指针数组名


⭐️5. 函数指针

先来看一段代码
在这里插入图片描述
程序运行到50行后,会去寻找Add函数,是先寻找Add函数所在的地址(每一个函数都用自己的地址),然后再执行该地址处的代码

函数也有自己的地址,存放函数地址的指针叫做函数指针

int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int(*pf)(int, int) = &Add;//定义了一个函数指针,指向的函数的形参是2个int,返回值是int
							  //并且将函数Add的地址赋给了pf
}

5.1 函数名和函数的地址

在这里插入图片描述

函数的值和函数的地址在大多数情况下是等价的
但是在函数调用时不可以&函数名调用函数

正是因为函数的值和函数的地址是一样的,这就导致对函数指针解引用n次最后的到的还是函数本身的值也就是函数的地址
在这里插入图片描述

函数调用可以通过函数指针来进行


在这里插入代码片

5.2 练习

分析下面代码含义

1.(*(void (*)())0)();

在这里插入图片描述

  1. 先看2-8,3和4说明了2-7是一个函数指针类型,指向的函数的返回值是void,函数的参数是void
  2. 2-8将数字0强转成一个函数指针类型
  3. 1-9将地址处为0的地址解引用得到一个参数为void的函数
  4. 1-10调用地址处为0的函数

这段代码的含义是将地址为 0 的内存空间强制类型转换为一个返回值为 void 类型、参数列表为空的函数指针类型,并对该指针进行解引用,即调用该地址上存储的函数。
由于该代码中将地址 0 强制类型转换为一个函数指针并进行解引用,因此该代码通常会被认为是一种非常危险的行为,因为在绝大多数操作系统中,地址 0 存储了操作系统保留的内存空间,不允许用户程序直接访问和修改。

因此,这段代码可能会导致系统崩溃或者其他严重后果。一般情况下,我们不应该编写这样的代码。如果确实需要进行类似的操作,也应该确保该地址指向的是一个合法的函数。

2.void (*signal(int , void(*)(int)))(int);

在这里插入图片描述

  1. signal是一个函数2-8说明了signal函数的参数一个是iint,另一个是指向参数为Int,返回值为void的函数的指针
  2. 1-19说明了signal函数的指针返回值是只一个函数指针,指针指向的是参数为Int,返回值为void的函数
    该代码可以简化
    typedef void(*f_return)(int)
    代码变为f_return signal(int, f_return)

总结

  1. 解引用函数指针n次得到的是函数本身
  2. 不可以&函数名来调用函数

⭐️6. 函数指针数组

函数指针数组是一个数组,数组元素是指向函数的指针

//函数指针数组
int Add(int a, int b)
{
	return a + b;
}

int Sub(int a, int b)
{
	return a - b;
}

int Div(int a, int b)
{
	return a / b;

}

int Mul(int a, int b)
{
	return a * b;
}

int main()
{
	//arr_Pf是一个数组,数组元素是指向参数为int,int返回值为int函数的指针
	int(*arr_Pf[5])(int, int) = { 0, Add, Div, Div, Mul };
}

结合函数指针,我们可以通过函数指针数组元素来调用函数

6.1 转移表

//函数指针数组
int add(int a, int b)
{
	return a + b;
}

int sub(int a, int b)
{
	return a - b;
}

int div(int a, int b)
{
	return a / b;

}

int mul(int a, int b)
{
	return a * b;
}

void menu()
{
	printf("***************************\n");
	printf("  1. add	    2. sub        \n");
	printf("  3. div	    4. mul    \n");
	printf("         0. exit          \n");
	printf("***************************\n");

}
int main()
{
	//arr_pf是一个数组,数组元素是指向参数为int,int返回值为int函数的指针
	int(*arr_pf[5])(int, int) = { NULL, add, sub, div, mul };//因为0充当的是exit,所以数组下标为0规定为NULL

	int input = 1;
	int a, b;
	while (input)
	{
		menu();
		printf("请选择你需要的操作->\n");
		scanf("%d", &input);
		if (input != 0)
		{
			printf("请输入两个操作数->\n");
			scanf("%d%d", &a, &b);
			printf("结果是%d\n", arr_pf[input](a, b));
		}
	}
	return 0;
	
}

使用转移表可以简化代码量,试想一下,如果没有转移表,当我们想实现的功能越来越多时,代码将会有大量的switch case语句

⭐️7. 指向函数指针数组的指针

指向函数指针数组的指针是一个指针,指针指向的是一个数组,数组元素是函数指针

//指向函数指针数组的指针
void test(const char* str)
{
	printf("%s\n", str);
}

int main()
{
	//函数指针pfun
	void(*pfun)(const char*) = test;
	//函数指针的数组pfunArr
	void (*pfunArr[5])(const char* );
	pfunArr[0] = test;
	//指向函数指针数组pfunArr的指针ppfunArr
	void(*(*pfunArr)[5])(const char*);
	return 0;
}

认识它的声明即可,因为我也不知道可以用在哪些方面😢😢
面向gpt编程😜😜
在这里插入图片描述


⭐️8. 回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。通过函数指针被调用的函数称为回调函数

回调函数的用法详见万能排序qsort

⭐️⭐️总结(思维导图)

最后当然是最重要的思维导图啦😆

在这里插入图片描述

写在最后:

周末花一天时间肝出来的,明天又要开始上课了💢,以后一个星期能更2篇就不错了,继续保持吧,希望这篇流量能高一点 😃 最后如果本章内容对你有帮助,请留下您的三连,感谢!!!😄😄😄

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/373983.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

昌德科技冲刺上市:计划募资约12亿元,蒋卫和为实控人

近日&#xff0c;深圳市昌德新材科技股份有限公司&#xff08;下称“昌德科技”&#xff09;递交招股书&#xff0c;准备在真真证券交易所主板上市。本次冲刺上市&#xff0c;昌德科技计划募资11.69亿元&#xff0c;中信建投证券为其保荐机构。 据招股书介绍&#xff0c;昌德科…

西电编译原理期末核心考点汇总(期末真题+相关知识点)

文章目录前言一、正规式1.1 相关知识点1.1.1 正规式定义1.1.2 辅助定义1.2 历年真题二、二义文法2.1 相关知识点2.1.1 二义性概念2.2 历年考题三、全部短语、直接短语和句柄3.1 相关知识点3.1.1 短语&#xff0c;直接短语和句柄定义3.1.2 短语&#xff0c;直接短语和句柄例题3.…

【企业管理】研发部视角提出对外支撑业务自助门户构思和实现

导读&#xff1a;公司是由不同部门组成&#xff0c;各个部门之间必然有协同才能使得公司各项职能正常运行。可以说公司的竞争力越强往往会得出公司内部之间工作协同就越高效&#xff0c;可以看出公司各部门之间协同对公司营运是十分重要的。高效协同前提必然是实现便利的信息共…

数据库设计表与表之间的关系详细介绍

文章目录数据库设计数据库设计简介表关系之一对多表关系之多对多表关系之一对一数据库设计 数据库设计简介 软件研发的步骤如下: 设计数据库还是很重要的 数据库设计概念: 数据库设计就是根据业务系统的具体需求&#xff0c;结合我们所选用的DBMS&#xff0c;为这个业务系统构…

Synology搭建Gitea(Docker)

Synology搭建Gitea(Docker) 文章目录Synology搭建Gitea(Docker)参考增加用户与用户组增加映像安装配置反向代理路由器端口转发参考 Nas轻量git方案&#xff1a;Docker安装Gitea;群晖(Synology) NAS 如何安装 gitea 增加用户与用户组 为所有Docer创建一个组docker&#xff1b; 权…

行测-判断推理-图形推理-样式规律-黑白运算

黑白元素个数不同&#xff0c;优先考虑黑白运算白白白黑黑白黑白黑选A考试时&#xff0c;这种题不要先把规律全部推出来&#xff0c;再去做题&#xff0c;太慢了直接看要推的图&#xff0c;通过排除法选答案黑白元素个数不同&#xff0c;优先考虑黑白运算白白白黑黑白黑白黑选B…

【5G RRC】5G系统消息SIB3介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

[python入门(52)] - python中的OS模块(包) - 1

目录 ❤ OS模块和path模块(函数) ❤ 当前路径及路径下的文件 ❤ 绝对路径 ❤ 查看指定文件路径的文件夹路径部分和文件名部分 ❤ 路径拼接 ❤ 获取路径的文件夹路径部分 ❤ 获取路径的文件名 ❤ 查看文件时间 ❤ 查看文件大小 ❤ 查看文件是否存在 ❤ OS模…

【原创】java+swing+mysql设备预约管理系统设计与实现

我们在办公室或者学校实验室的&#xff0c;经常需要使用一些设备&#xff0c;因此需要提前租借。今天我们主要介绍如何使用javaswing和mysql数据库去完成一个设备预约管理系统&#xff0c;方便用户进行设备管理和预约。 功能分析&#xff1a; 设备预约管理系统主要是为了方便…

医疗床头卡(基站方案)

一、产品特色 低功耗&#xff0c;常规使用3-5年电池寿命支持空中唤醒点阵电子纸屏幕安装简单&#xff0c;快速布置远程智能化管理低碳环保&#xff0c;无纸化安全可靠ESL_BWR7.5_V2二、系统结构 三、多基站组织架构 四、电子床头卡 接收路由器发送的数据信息并解析&#xff0…

深度学习之卷积神经网络学习笔记一

1. 引言深度学习是一系列算法的统称&#xff0c;包括卷积神经网络&#xff08;CNN&#xff09;&#xff0c;循环神经网络&#xff08;RNN&#xff09;&#xff0c;自编码器&#xff08;AE&#xff09;&#xff0c;深度置信网络&#xff08;DBN&#xff09;&#xff0c;生成对抗…

FreeRTOS优先级翻转

优先级翻转优先级翻转&#xff1a;高优先级的任务反而慢执行&#xff0c;低优先级的任务反而优先执行优先级翻转在抢占式内核中是非常常见的&#xff0c;但是在实时操作系统中是不允许出现优先级翻转的&#xff0c;因为优先级翻转会破坏任务的预期顺序&#xff0c;可能会导致未…

16- TensorFlow实现线性回归和逻辑回归 (TensorFlow系列) (深度学习)

知识要点 线性回归要点: 生成线性数据: x np.linspace(0, 10, 20) np.random.rand(20)画点图: plt.scatter(x, y)TensorFlow定义变量: w tf.Variable(np.random.randn() * 0.02)tensor 转换为 numpy数组: b.numpy()定义优化器: optimizer tf.optimizers.SGD()定义损失: …

利用Redis一步步实现优惠券的最终秒杀方案

订单ID不能采用自增长的原因&#xff1a; 1、规律变化太明显。两天下单的ID的差值&#xff0c;能够计算出商城的订单量&#xff1b; 2、如果采用自增长&#xff0c;订单数据是会不断产生的&#xff0c;到时候要分表&#xff0c;但是每个表的ID都是从0开始增长的&#xff0c;这…

selenium模块(自动化)

文章目录一、环境配置二、使用selenium解析源码三、基本函数四、子页面&#xff08;ifFrame&#xff09;&#xff08;动作链&#xff0c;拖拽&#xff09;五、实现无可视化界面&#xff0c;规避被检测的风险&#xff08;反反爬&#xff09;六、等待七、异常处理Selenium是自动化…

Vue+python+django+flask共享汽车租赁管理系统

共享汽车管理系统的系统管理员可以管理用户&#xff0c;可以对用户信息修改删除以及查询操作。具体界面的展示如图所示。 3.2投放地区管理 系统管理员可以对投放地区信息进行添加&#xff0c;修改&#xff0c;删除以及查询操作。具体界面如图所示。 3.3汽车信息管理 系统管…

【原创】java+swing+mysql教室管理系统设计与实现

大学生活中&#xff0c;我们很多时候都要用到教室&#xff0c;比如开个班会&#xff0c;开展某次活动&#xff0c;一般情况下为了避免占用教室资源&#xff0c;一般情况下都会提前进行预约教室&#xff0c;所以今天我们讲的就是如何使用javaswingmysql去设计一个教室预约管理系…

13 Sentinel初始化监控

Sentinel初始化工程演示 通过一个案例来让大家了解Sentinel的初始化演示&#xff0c;现在我们需要做几件事&#xff1a; 启动本地Nacos&#xff1a; 8848创建新的Module&#xff1a;cloudalibaba-sentinel-service8401启动Sentinel服务&#xff1a;8080启动sentinel微服务840…

Day901.内部临时表 -MySQL实战

内部临时表 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于内部临时表的内容。 sort buffer、内存临时表和 join buffer。这三个数据结构都是用来存放语句执行过程中的中间数据&#xff0c;以辅助 SQL 语句的执行的。 其中&#xff0c;在排序的时候用到了 sort bu…

win10环境下安装openCDA(详细解答CARLA+SUMO以及遇到的问题)

目录预备知识CARLA安装安装CMAKE安装MAKE安装Visual Studio 2019安装unreal engine 4.26openCDA安装安装SUMO预备知识 opeCDA结合了carla和sumo&#xff0c;支持协同驾驶开发与测试&#xff0c;最近开源了。 论文链接&#xff1a;https://arxiv.org/abs/2107.06260 官方linux安…