第三十六天学习记录:C语言进阶:指针详解Ⅳ

news2024/11/28 2:20:02

指向函数指针数组的指针

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

	int(*pfArr[4])(int, int);//pfArr是一个数组-函数指针的数组
	int(*(*ppfArr)[4])(int, int) = &pfArr;
	//ppfArr是一个数组指针,指针指向的数组有4个元素
	//指向的数组的每个元素的类型是一个函数指针int(*)(int,int)

回调函数

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

问:
C语言的回调函数和C++的回调函数本质是一样的吗?
ChatAI:
C语言和C++中的回调函数的本质是类似的,但在具体实现和使用时,两者还是略有不同之处的。

回调函数是在函数调用过程中传递一个函数指针(function pointer)给被调用的函数,并由被调用函数进行回调,即在内部执行函数指针所指向的函数。在C语言中,回调函数被广泛应用于事件处理、信号处理、多线程编程等方面。而在C++中,回调函数一般被称为回调(Callback)或函数对象(Functor),主要应用于STL库中的算法和容器等实现中。

在C语言中,回调函数通常作为一个函数指针参数传递给被调用函数,通过指针调用进行回调。函数指针可以指向普通的全局或局部函数,也可以指向结构体中的成员函数,这使得回调函数可以方便地扩展为面向对象的形式。在C语言中,回调函数的使用通常需要注意函数指针类型的匹配问题,以及函数指针的生命周期管理等问题。

在C++中,回调函数往往被实现为函数对象,即可被调用的运算符重载函数。使用回调函数对象时,可以把函数对象当作一个普通的参数传递给被调用函数,不需要考虑指针类型匹配和生命周期管理等问题。此外,C++的回调函数对象还可以方便地重载operator(),使得函数对象可以像函数一样调用,并支持函数对象的继承和多态等高级功能。

因此,虽然C语言和C++中的回调函数本质相似,但在具体实现和使用时,两者还是有区别的,而且C++的回调函数对象更为灵活和高级。

例题:
计算器变种版

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a*b;
}
int div(int a, int b)
{
	return a / b;
}
void Calc(int(*pf)(int, int))
{
	int x = 0;
	int y = 0;
	printf("请输入两个操作数:");
	scanf("%d%d", &x, &y);
	printf("%d\n", pf(x, y));
}

int main()
{
	int input = 0;
	do
	{
		printf("*************************\n");
		printf(" 1:add             2:sub \n");
		printf(" 3:mul             4:div \n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			Calc(add);
			break;
		case 2:
			Calc(sub);
			break;
		case 3:
			Calc(mul);
			break;
		case 4:
			Calc(div);
			break;
		case 0:
			printf("退出程序\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);

	return 0;
}

例题2:

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

void print(char* str)
{
	printf("Chung:%s", str);
}

void test(void(*p)(char*))
{
	printf("test\n");
	p("Perfect");
}

int main()
{
	test(print);
	return 0;
}

输出:
在这里插入图片描述
例题3:

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>

void BubbleSort(int arr[], int sz)
{
	int flag = 0;
	int i = 0;
	int j = 0;
	int tmp = 0;
	for (i = 0; i < sz-1; i++)
	{
		j = 0; flag = 1;
		for (j = 0; j < sz - 1 - i;j++)
		if (arr[j] > arr[j + 1])
		{
			tmp = arr[j];
			arr[j] = arr[j + 1];
			arr[j + 1] = tmp;
			flag = 0;
		}
		if (flag == 1)
		{
			break;
		}
	}
}
//qsort-可以排序任意类型的数据
struct Stu
{
	char name[20];
	int age;
};

int main()
{
	int arr[] = { 1,3,5,7,9,2,4,6,8,0 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	struct  Stu s[3] = { {"Zhangsan",20},{"Lisi",30},{"Wangwu",10} };
	float f[] = { 9.0,8.0,7.0,6.0 ,5.0,4.0};
	BubbleSort(arr, sz);
	BubbleSort(f, sz);
	for (i = 0; i < sz; ++i)
	{
		printf("%d", arr[i]);
	}
	return 0;
}

void* 的步长是多少?
ChatAI答:
void*指针是一种通用类型的指针,可以指向任何类型的数据,但是由于void类型没有存储空间,因此对void*类型进行算术运算或者偏移指针需要使用合适的步长以确保指针指向正确的地址。

在C和C++中,void*指针的步长是由所指向的类型进行决定的。例如,如果指向的是int类型的数据,那么步长就是sizeof(int);如果指向的是double类型的数据,那么步长就是sizeof(double),以此类推。

需要注意的是,对于void*类型的指针,编译器不会进行任何类型检查或隐式转换。因此,使用void*指针时需要确保指针类型和实际类型匹配,否则可能会导致程序出现未定义的行为。建议在使用void*指针前,先将指针强制转换为合适的类型以进行正确的内存操作。

//void* 类型的指针 可以接收任意类型的地址
//void* 类型的指针 不能进行解引用操作
//void* 类型的指针 不能进行±整数的操作

问:

//为什么C语言的字符串无法用"=="比较,但Qt的QString可以直接用"=="比较?

ChatAI答:
在C语言中,使用"=="运算符对字符串进行比较时,比较的是两个字符串的地址是否相等,也就是比较它们是否指向同一个存储位置,这显然并不是我们想要的字符串比较方式。正确的比较方式是使用库函数strcmp,该函数会按照字典序逐个比较字符串中的字符,以确定它们的相对大小关系。

而在Qt框架中,QString类定义了"=="运算符的重载实现,允许直接对QString对象进行比较,比如:

QString str1 = "hello";
QString str2 = "hello";
if (str1 == str2) {
  // 两个字符串相等
}

这种比较方式会比较两个字符串的内容是否相等,而不是它们的地址。在实现中,QString类重载了"=="运算符来比较两个字符串,可以直接返回字符串内容的比较结果。不同于C语言,Qt框架有自己的库函数,QString类中也提供了compare()函数来进行字符串比较。

例题4:
qsort的使用:

#define _CRT_SECURE_NO_WARNINGS 1

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

struct Stu
{
	char name[20];
	int age;
};

//void qsort(void* base, size_t num, size_t width, int(*cmp)(const void* e1, const void* e2));

int cmp_int(const void* e1, const void*e2)
{
	//比较两个整型值的
	return *(int*)e1 - *(int*)e2;
}

void test1()
{
	int arr[] = { 1,3,5,7,9,2,4,6,8,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < sz; ++i)
	{
		printf("%d ", arr[i]);
	}
}

int cmp_float(const void* e1, const void*e2)
{
	if (*(float*)e1 == *(float*)e2)
	{
		return 0;
	}
	else if (*(float*)e1> *(float*)e2)
	{
		return 1;
	}
	else
	{
		return -1;
	}
}

void test2()
{
	float f[] = { 9.0,8.0,7.0,6.0 ,5.0,4.0};
	int sz = sizeof(f) / sizeof(f[0]);
	qsort(f, sz, sizeof(f[0]), cmp_float);
	int i = 0;
	for (i = 0; i < sz; ++i)
	{
		printf("%f ", f[i]);
	}
}

int cmp_stu_by_age(const void* e1, const void*e2)
{
	return ((struct  Stu*)e1)->age - ((struct  Stu*)e2)->age;
}

int cmp_stu_by_name(const void* e1, const void*e2)
{
	//比较名字就是比较字符串,字符串比较不能直接用><=来比较,应该用
	return strcmp(((struct  Stu*)e1)->name , ((struct  Stu*)e2)->name);
}

void test3()
{
	struct  Stu s[3] = { {"Zhangsan",20},{"Lisi",30},{"Wangwu",10} };
	int sz = sizeof(s) / sizeof(s[0]);
	//qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}

int main()
{
	test1();
	printf("\n");
	test2();
	printf("\n");
	test3();
	return 0;
}

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

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

相关文章

网络进阶学习:重要网络协议(tcp协议,udp协议,http协议)

重要网络协议&#xff08;tcp协议&#xff0c;udp协议&#xff0c;http协议&#xff09; 网络协议是什么?TCP协议UDP协议HTTP协议TCP与UDP的职能区别⭐TCP职能⭐就TCP原理层面说应该分为三部分建立连接数据传输断开连接 ⭐UDP职能⭐就UDP原理层面说应该分为三部分发送数据数据…

单链表(增、删、查、改)的详细介绍 必看!!!

文章目录 链表介绍单链表初始化单链表打印增加节点单链表的头插单链表的尾插在给定位置之后插入在给定位置之前插入 删除节点单链表的头删单链表的尾删删除给定位置之后的节点删除给定位置处的节点 查找节点修改节点单链表销毁 链表介绍 链表是一种物理存储单元上非连续、非顺序…

【Mybatis】如何实现ORM映射-二

唠嗑部分 上篇文章我们说了Mybatis基本的CRUD操作及工具类的封装&#xff0c;相关文章&#xff1a; 【Mybatis】简单入门及工具类封装-一 大家都知道&#xff0c;Mybatis是半自动化的ORM框架&#xff0c;那么它到底是如何帮我们完成ORM映射的呢&#xff1f; 这就是本篇文章和…

SpringBoot整合Dubbo+Zookeeper

第一步、使用IDE创建一个SpringBoot项目 第二步、启动一个Zookeeper服务&#xff08;如果是第一次安装且没有配置zoo.cfg&#xff0c;此时会报出zookeeper服务器会提示缺少zoo.cfg文件&#xff09; 在zookeeper安装目录下的conf文件夹内有一个名为zoo_sample.cfg的配置文件&a…

[深度学习思想] ControlNet 工作原理

Stable Diffusion (2021 https://arxiv.org/abs/2112.10752) 带领了vision领域&#xff0c;具体是AI绘画领域达到了一个新高度。但是可控性成为使用的一个大瓶颈。Controlnet (2023 https://arxiv.org/abs/2302.05543) 提出一个深度学习模型框架&#xff0c;很好解决这个问题&a…

Liunx基础命令 - touch命令

touch命令 – 创建空文件与修改时间戳 touch命令的功能是用于创建空文件与修改时间戳。如果文件不存在&#xff0c;则会创建出一个空内容的文本文件&#xff1b;如果文件已经存在&#xff0c;则会对文件的Atime&#xff08;访问时间&#xff09;和Ctime&#xff08;修改时间&a…

带头双向循环链表(增、删 、查、改)基本操作详细介绍 必看!!!

文章目录 链表介绍链表初始化链表打印查找元素增加节点头插尾插在指定位置插入 删除节点头删尾删删除指定位置节点 链表判空获取链表中元素的个数链表销毁 链表介绍 前面说到&#xff0c;链表的结构一共有八种&#xff1a;带头单向循环链表、带头单向非循环链表、带头双向循环…

渗透测试--3.2捕获和监听网络数据

目录 1.监听捕获数据方法 2.kali监听捕获工具介绍 arpspoof driftnet 1.监听捕获数据方法 渗透测试中&#xff0c;捕获和监听网络数据是非常重要的一项任务&#xff0c;可以帮助我们发现潜在的漏洞和攻击面。以下是一些常见的捕获和监听网络数据的方法&#xff1a; 抓包工…

Liunx基础命令 - cp复制命令

cp命令 – 复制文件或目录 cp命令来自英文单词“copy”的缩写&#xff0c;中文译为“复制”&#xff0c;其功能是用于复制文件或目录。cp命令能够将一个或多个文件或目录复制到指定位置&#xff0c;亦常用于文件的备份工作。-r参数用于递归操作&#xff0c;复制目录时若忘记添…

网络编程——UDP编程

UDP编程 UDP编程步骤通信流程serverclient 函数接口socketbindrecvfromsendto 举例UDP客户端UDP服务器 UDP编程步骤 在C语言中进行UDP编程的一般步骤如下&#xff1a; &#xff08;1&#xff09;包含头文件&#xff1a; 在代码中包含必要的头文件&#xff0c;以便使用UDP编程所…

借助国内ChatGPT平替+剪映/百度AIGC平台快速制作短视频

系列文章目录 借助国内ChatGPT平替MindShow&#xff0c;飞速制作PPT 借助国内ChatGPT平替markmap/Xmind飞速生成思维导图 文章目录 系列文章目录前言一、科大讯飞“星火”认知大模型二、使用步骤1.借助讯飞星火生成短视频的文案2.选择剪映生成短视频3.选择百度AIGC平台生成短…

redis复制机制

文章目录 1. Redis 复制机制2. 基本命令3. 修改配置文件4. 代码案例4.1 一主二仆4.2 薪火相传4.3 反客为主 5. Redis复制工作流程6. Redis 复制的缺点 1. Redis 复制机制 概念 : Redis 复制机制 能干的活 : 读写分离 &#xff1a; 写 就找 主机 master &#xff0c; 读就找从机…

Centos7离线安装Docker

在我们部署Docker镜像时&#xff0c;难免会遇到服务器没有网络情况&#xff0c;这时候我们就需要离线安装Docker 参考文档&#xff1a;https://docs.docker.com/engine/install/binaries 下载Docker安装包 下载地址&#xff1a;https://download.docker.com/linux/static/st…

玩转Google开源C++单元测试框架Google Test系列(gtest)之六 - 运行参数

一、前言 使用gtest编写的测试案例通常本身就是一个可执行文件&#xff0c;因此运行起来非常方便。同时&#xff0c;gtest也为我们提供了一系列的运行参数&#xff08;环境变量、命令行参数或代码里指定&#xff09;&#xff0c;使得我们可以对案例的执行进行一些有效的控制。…

OpenCL编程指南-4.3类型转换

隐式类型转换 隐式类型转换是一种自动的类型转换&#xff0c;只要混合使用不同的类型&#xff0c;编译器就会完成这种隐式类型转换。这里支持表4-1中定义的标量类型&#xff08;除void、double和half以外&#xff09;的隐式转换。完成隐式转换时&#xff0c;并不只是重新解释一…

c/c++中的数据内存分布

c/c中的数据内存分布 1. C/C内存分布2. C语言中动态内存管理方式3. C中动态内存管理4. operator new与operator delete函数5. new和delete的实现原理6. malloc/free和new/delete的区别7.内存泄漏**什么是内存泄漏&#xff0c;内存泄漏的危害****内存泄漏分类&#xff08;了解&a…

练习时长两年半的扫雷

目录 设计思路 游戏运行效果 函数的声明 头文件game.h 游戏主体(源文件) 1.game.c 2.test.c 各文件的阐述 各部分设计心得 1.打印菜单 2.初始化雷池 3.打印雷池以及玩家界面 打印效果 如何改变雷的数量与雷池大小 4.生成随机雷 5.排雷与对局判断 对于越界的看法 设计…

Pruning 系列 (五)Dropout、Dropconnect剪枝

环境 python 3.9numpy 1.24.1pytorch 2.0.0+cu117一、Dropout dropout在全连接层使用。 假设丢弃概率是P,有两种实现方式,不管是在《测试阶段》还是《训练阶段》对每层的输出做缩放,都是为了保持伯努利分布的期望np。《测试阶段》没dropout 而是全量神经元参与运算,不然预…

JAVA8的新特性——lambda表达式

JAVA8的新特性——lambda表达式 此处&#xff0c;我们首先对于Java8的一些特性作为一个简单介绍 Java 8是Java编程语言的一个重要版本&#xff0c;于2014年发布。Java 8引入了许多新特性和改进&#xff0c;以提高开发效率和性能。以下是Java 8的一些主要新特性&#xff1a; Lam…

Liunx基础命令 - pwd命令

pwd命令 – 显示当前工作目录的路径 ​ pwd 命令来自英文词组“print working directory”的缩写&#xff0c;其功能是用于显示当前工作目录的路径&#xff0c;即显示所在位置的绝对路径。在实际工作中&#xff0c;我们经常会在不同目录之间进行切换&#xff0c;为了防止“迷路…