『C语言进阶』指针进阶(二)

news2024/10/7 14:30:12

在这里插入图片描述
🔥博客主页 小羊失眠啦
🔖系列专栏 C语言
🌥️每日语录上天是公平的,有付出就有收获
❤️感谢大家点赞👍收藏⭐评论✍️


在这里插入图片描述

前言

在上篇指针进阶中,我们对字符指针、指针数组、数组指针以及数组传参和指针传参有了一定的了解,你以为指针进阶就只有这些内容嘛?不不不,接下来,小羊将继续完善指针进阶内容,坐好小板凳准备上课了~~~

一、函数指针

1.1 函数指针的定义

函数指针,顾名思义,就是一个指向函数的指针
上篇中我们学到

整形指针是接收整形的地址
字符指针是接收字符的地址
数组指针是接收数组的地址

那么函数有地址吗?函数名又表示什么呢?
答案是:函数是有地址的,地址是函数名或者&函数名,解引用时,p和*p都可以

#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int a = 1, b = 2;
	int c = Add(a, b);
	printf("%d\n", c);
	printf("%p\n", &Add);
	printf("%p\n", &Add);
	return 0;
}

运行结果:

3
00007FF750191348
00007FF750191348

由此可见,函数名也可以表示函数的地址

所以问题来了,我们以数组指针为例:
写一个指向int arr[10]数组的数组指针

第一步:
(*p)		//先确定是一个指针
第二步:
(*p)[10]	//确定指向的是一个有10个元素的数组
第三步:
int(*p)[10]	//确定该数组元素为int型
第四步:
int(*p)[10]=&arr;//将数组的地址赋值给数组指针
//或者int(*p)[10]=arr;

我们照着数组指针的例子来写一个函数指针:指向int add(int x,int y)

第一步:
(*p)			//先确定是一个指针
第二步:
(*p)(int,int)	//确定指向的函数有两个参数
第三步:
int (*p)(int,int)	//确定该函数的返回类型
第四步:
int (*p)(int,int)=&add;//将函数的地址赋值给函数指针
//等价于:int (*p)(int,int)=add;

1.2 函数指针调用

1.1中得到 &函数名==函数名 ,所以函数指针的解引用调用可以不写*,也可以不写*。
既然我们现在知道函数指针是怎么写的,那么函数指针有什么用呢?
数组指针可以用来访问数组,那函数指针当然也就是调用函数的了,
我们还是以数组指针为例:

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5 };
	int(*p)[10] = arr;

	for (int i = 0; i < 5; i++)
	{
		printf("%d ", (*p)[i]);
	}
	printf("\n");
	return 0;
}

那现在用函数指针调用函数:

#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int a = 1, b = 2;
	int(*p1)(int, int) = &Add;
	int(*p2)(int, int) = Add;
	int tmp1 = Add(a, b);
	int tmp2 = (*p1)(a, b);//写法一
	int tmp3 = (p2)(a, b);//写法二
	printf("tmp1=%d\ntmp2=%d\ntmp3=%d", tmp1, tmp2, tmp3);
	return 0;
}

运行结果:

tmp1=3
tmp2=3
tmp3=3

1.3 有趣的代码

一、

(*(void(*)())0)();
//先分解
一、void(*)()是一个无参无返回类型的函数指针
二、(void(*)())0是将0强制类型转换,0原本是int类型,被强制转换为void(*)()函数指针
三、*void(*)()0)这是将0转换为函数指针后的解引用操作
四、(*(void(*)()0)()此时0就是一个函数的地址,可以看成(*0)(),意思就是解引用一个函数地址并且调用

二、

void(*signal(int,void(*)(int)))(int);
我先一步一步分解给你们看
void(*)(int)
signal(int,void(*)(int))
signal(int,void(*)(int))
void(*signal(int,void(*)(int)))(int)
signal先和(int,void(*)(int))结合,说明它是一个函数,参数为int,void(*)(int),现在我们明确了它的函数名和参数,还需要知道它的返回类型,先看一个函数,int Add(int),这个函数的函数名是Add,参数是int,把函数名和参数去掉后就是返回类型,同样,把这里的函数名和参数去掉,void(*)(int)这个就是它的返回类型

上面代码是一个函数调用


二、函数指针数组

2.1 函数指针数组的定义

函数指针数组,存放函数指针的数组,每一个元素都是函数指针类型

先写出函数指针
int(*p)(int,int)
改成数组
int(*p[10])(int,int)

例:

#include<stdio.h>

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

int main()
{
	int(*pf1) (int, int) = Add;
	int(*pf2) (int, int) = Sub;
	int(*pf3) (int, int) = Mul;
	int(*pf4) (int, int) = Div;
	int (*arr[4])(int, int) = { Add,Sub,Mul,Div };
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
	{
		printf("%d\n", arr[i](7, 4));
	}
	return 0;
}

2.2 实战操作

用C语言制作简易计算器

#include<stdio.h>

void menu()
{
	printf("****************************\n");
	printf("*****  1.Add    2.Sub  *****\n");
	printf("*****  3.Mul    4.Div  *****\n");
	printf("*****  0.exit          *****\n");
	printf("****************************\n");
}
int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

int main()
{
	int input = 0;
	int x = 0, y = 0;
	int (*arr[5])(int, int) = { 0,Add,Sub,Mul,Div };
	do 
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			printf("%d\n", arr[input](x, y));
		}
		else if (input == 0)
		{
			printf("退出计算机");
			break;
		}
		else
			printf("选择错误\n请重新选择:>\n");

	} while (input);
	return 0;
}

三、函数指针数组指针

指向函数指针数组的指针,是一个指针,指针指向存放函数指针的数组,该数组成员都是函数指针

先写出函数指针
int (*p)(int,int)
改写成函数指针数组
int(*p[10])(int,int)
最后写成函数指针数组指针
int(*(*p)[10]))(int,int)

示例:

#include<stdio.h>

void menu()
{
	printf("****************************\n");
	printf("*****  1.Add    2.Sub  *****\n");
	printf("*****  3.Mul    4.Div  *****\n");
	printf("*****  0.exit          *****\n");
	printf("****************************\n");
}
int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

int main()
{
	int (*pa)(int, int) = Add;//函数指针
	int (*arr[4])(int, int) = { Add };//函数指针数组,存放函数指针的数组
	int (*(*ppa)[4])(int, int) = &arr;//函数指针数组指针,存放函数指针数组地址的指针
	return 0;
}

四、回调函数

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

示例:

#include<stdio.h>

void menu()
{
	printf("****************************\n");
	printf("*****  1.Add    2.Sub  *****\n");
	printf("*****  3.Mul    4.Div  *****\n");
	printf("*****  0.exit          *****\n");
	printf("****************************\n");
}
int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int Mul(int x, int y)
{
	return x * y;
}

int Div(int x, int y)
{
	return x / y;
}

void Calc(int (*pf)(int, int))
{
	int x = 0, y = 0;
	printf("请输入两个操作数:>");
	scanf("%d %d", &x, &y);
	int ret = pf(x, y);
	printf("%d\n", ret);
}

int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			printf("退出计算机\n");
			break;
		case 1:
			Calc(Add);
			break;
		case 2:
			Calc(Sub);
			break;
		case 3:
			Calc(Mul);
			break;
		case 4:
			Calc(Div);
			break;
		default:
			printf("输入错误\n");
			break;
		}
	} while (input);
	return 0;
}

4.1 排序

冒泡排序,相邻两个两个的比较并交换位置,在C语言初阶数组中,详细讲解了关于冒泡排序的知识点,忘记了的铁汁们可以看一下
代码展示:

#include <stdio.h>
void Print(int arr[], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
void bubble_sort(int arr[],int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}
int main()
{
	int arr[] = { 5,3,7,6,1,8,9,2,4,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr,sz);
	Print(arr, sz);
	return 0;
}

4.2 qsort部分展示

冒泡排序只能排序整形,而qsort函数,内部采用快速排序,可以排序各种类型的数据,接下来展示qsort排序部分类型的方法.

qsort是一个库函数,快速排序的方法来实现的, 头文件是<stdlib.h>
qsort库函数,void qsort( void *base, size_t num, size_t width, int (_cdecl *compare )
(const void *elem1, const void *elem2 ) );传入的参数,一个是指针,一个整形,一个整形一个函数指针,base 数组首元素(就是数组名),num数组里有多少个元素,width每个元素的大小(单位是字节),compare比较两个指针指向的元素,小于 输出小于0的元素,等与 输出0,大于 输出大于0的元素 排序任意类型

示例:
qsort函数部分应用
分别将元素比较方法int_cmp和char_cmp的指针(地址) 传给 qsort函数.由qsort函数调用这些比较函数

#include <stdlib.h>
#include <stdio.h>
int int_cmp(const void* e1, const void* e2)//整形元素排序方法
{
	return *(int*)e1 - *(int*)e2;
}
int char_cmp(const void* e1, const void* e2)//字符型元素排序方法
{
	return *(char*)e1 - *(char*)e2;
}
int main()
{
	int arr1[10] = { 4,5,1,8,9,2,10,3,7,6 };
	char arr2[] = "fbadegc";
	int sz1 = sizeof(arr1) / sizeof(arr1[0]);
	int sz2 = sizeof(arr2) / sizeof(arr2[0]);
	qsort(arr1,sz1,sizeof(arr1[0]),int_cmp);
	for (int i=0; i < sz1; i++)
	{
		printf("%d ", arr1[i]);
	}
	printf("\n");
	qsort(arr2, sz2, sizeof(arr2[0]), char_cmp);
	for (int i = 0; i < sz2; i++)
	{
		printf("%c ", arr2[i]);
	}
	printf("%s", arr2);
	return 0;
}

运行结果:

1 2 3 4 5 6 7 8 9 10
 a b c d e f g

qsort函数用冒泡排序的模拟实现,以及各种类型的排序咱们下篇一起学习~
希望这篇文章对铁汁们有所帮助,咱们下期再见!

在这里插入图片描述

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

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

相关文章

计网 - 图解OSI 七层模型 和 TCP/IP 四层模型

文章目录 OSI 七层模型TCP/IP四层模型图解 OSI图解 TCP/IP小结 OSI 七层模型 OSI&#xff08;开放系统互联模型&#xff0c;Open Systems Interconnection Model&#xff09;是一个用于理解和描述计算机网络体系结构的标准化框架&#xff0c;由国际标准化组织&#xff08;ISO&…

基于Xml方式Bean的配置-beanName个别名配置

SpringBean配置详解 Bean的基础配置 例如前文涉及到的配置文件 <bean id"userService" class"com.example.Service.Impl.UserServiceImpl"/>此时存储到Spring容器&#xff08;singleObjects单例池&#xff09;中的beanName是userService&#xf…

【Java】IDEA 将 Java 项目打包成 Jar 包

在工件中添加 JAR 构建&#xff08;Build&#xff09;工件&#xff0c;JAR 包会生成在 out 目录下

【系统架构】什么是集群?为什么要使用集群架构?

什么是集群&#xff1f;为什么要使用集群架构&#xff1f; 1.什么是集群&#xff1f;2.为什么要使用集群&#xff1f;2.1 高性能2.2 价格有效性2.3 可伸缩性2.4 高可用性2.5 透明性2.6 可管理性2.7 可编程性 3.集群的常见分类3.1 负载均衡集群3.2 高可用性集群3.3 高性能计算集…

Vue2+Vue3

文章目录 Vue快速上手Vue是什么第一个Vue程序插值表达式Vue核心特性&#xff1a;响应式 Vue指令v-htmlv-show 与 v-ifv-else 与 v-else-ifv-onv-bindv-forv-model指令修饰符 计算属性watch侦听器&#xff08;监视器&#xff09;watch——简写watch——完整写法 Vue生命周期 和 …

c语言输出杨辉三角

#include<stdio.h> int main() {int x 0; //表示杨辉三角的的大小int y 1;printf("请输入x的值: ");scanf("%d", &x);for (int i 0; i < x; i) {for (int j 0; j < i; j) {if (j 0 || i 0) {y 1;}else {y y * (i - j 1) / j;}pri…

[mockjs]-mockjs的使用

Mock主要是用于前后端分离时&#xff0c;模拟交互时的返回数据 接下来介绍一下其它几种Mock的方式 json-server 与 express 之前介绍过json-server,可以启动一个express创建的mock的服务&#xff0c;通过接口获取数据&#xff1b;json-server也可以通过命令直接启动一个json…

【深度学习】大模型卷到机器人上了

当一项变革性技术出现后&#xff0c;以此为基础的技术就会像雨后春笋般蔓延。 就像Transformer出现后&#xff0c;以此为基础的大语言模型ChatGPT&#xff0c;视觉基础模型Segment Anything相继横空出世&#xff0c;并展现出强大的涌现能力。生成式AI可谓百花齐鸣&#xff0c;…

性能测试-性能工程落地的4个阶段(21)

性能工程按照不同的内容和目的划分为4个阶段,分别是线下单系统压测分析阶段、线下全链路压测分析阶段、生产只读业务压测及容量评估阶段、生产读写业务全链路压测及容量评估阶段。(也可以理解为一个企业性能测试体系的发展阶段) 线下单系统压测分析阶段 针对单系统的性能…

那些你不得不知道的CSS知识点

目录 1、CSS的盒子模型1.1 css盒子模型有哪些&#xff1a;1.2 css盒子模型的区别1.3 通过css如何转换css盒子模型 2、css优先级3、line-height和height的区别4、如果用css画一个三角形&#xff1f;5、CSS选择符有哪些&#xff1f;哪些属性可以继承&#xff1f;6、一个盒子不给宽…

《已解决 Go Error: panic: runtime error: index out of range BUG 》

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f405;&#x1f43e; 猫头虎建议程序员必备技术栈一览表&#x1f4d6;&#xff1a; &#x1f4a1; 后端技术 Backend: 编程语言: &#…

【计算机视觉】Image Data Augmentation算法介绍合集(二)

文章目录 一、Random Grayscale二、GridMask三、Adversarial Color Enhancement四、Population Based Augmentation五、MaxUp六、SuperpixelGridCut, SuperpixelGridMean, SuperpixelGridMix七、InstaBoost八、Random Mix-up九、Sample Redistribution十、Batchboost十一、CutB…

人工智能前景如何?人工智能所对应到企业中是什么样的岗位呢?

目录 分布式计算在硕士阶段主要是在干什么呢&#xff0c;主要修的课程是些什么呢&#xff1f; 分布式计算所对应到企业中是什么样的岗位呢&#xff1f; 分布式计算前景如何&#xff1f; 人工智能在硕士阶段主要是在干什么呢&#xff0c;主要修的课程是些什么呢&#xff1f;…

vue国际化教程

需求背景 项目需求要做国际化&#xff0c;结果网上找了好几篇文章&#xff0c;没有一个可以一次性搞定&#xff0c;现在这里总结一下。首先&#xff0c;我们分为两部分处理&#xff0c;一个是前端页面的静态文字&#xff0c;这个由前端vue.json自行处理。第二部分就是后端的错…

基于springboot漫画管理系统springboot001

摘 要 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&…

vue+element-ui el-descriptions 详情渲染组件二次封装(Vue项目)

目录 1、需求 2.想要的效果就是由图一变成图二 ​编辑 3.组件集成了以下功能 4.参数配置 示例代码 参数说明 5,组件 6.页面使用 1、需求 一般后台管理系统&#xff0c;通常页面都有增删改查&#xff1b;而查不外乎就是渲染新增/修改的数据&#xff08;由输入框变成输…

十四、流式编程(2)

本章概要 中间操作 跟踪和调试流元素排序移除元素应用函数到元素在 map() 中组合流 中间操作 中间操作用于从一个流中获取对象&#xff0c;并将对象作为另一个流从后端输出&#xff0c;以连接到其他操作。 跟踪和调试 peek() 操作的目的是帮助调试。它允许你无修改地查看…

CMU 15-445 Project #3 - Query Execution(Task #1、Task #2)

文章目录 一、题目链接二、准备工作三、SQL 语句执行流程四、BusTub 表结构五、Task #1 - Access Method Executors5.1 顺序扫描执行器5.2 插入执行器5.3 删除执行器5.4 索引扫描执行器 六、Task #2 - Aggregation & Join Executors6.1 聚合执行器6.2 循环连接执行器6.3 索…

设计模式Java实战

文章目录 一、前置1.1 目的1.2 面向对象1.3 接口和抽象类 二、七大设计原则2.1 单一职责2.2 接口隔离原则2.3 依赖倒转原则2.4 里氏替换原则2.5 开闭原则2.6 不要重复原则2.7 迪米特最少知道法则 三、23种设计模式3.1创建型&#xff1a;创建对象3.1.1 单例模式定义最佳实践场景…

【基础篇】ClickHouse 表引擎之集成Kafka

文章目录 0.前言1.集成示例官方教程示例1&#xff1a;示例2&#xff1a;配置Kerberos 支持 虚拟列 参考文档 0.前言 ClickHouse为了方便与Kafka集成&#xff0c;提供了一个名为Kafka引擎的专用表引擎。Kafka引擎允许你在ClickHouse中创建一个表&#xff0c;这个表的数据源来自…