你真的懂指针吗(4)?

1.回调函数?

1.1 回调函数的定义:

我们在上一篇文章中讲述了转移表,但是并没有讲完全,这里我们将借助回调函数来完成和完善这个回调函数。我们先看回调函数的定义:回调函数是通过函数指针或参数形式传递给另一个函数,并在特定事件或条件触发时被调用执行的函数​。它并非由开发者直接调用,而是由接收它的函数在需要时激活,常用于实现灵活的事件响应和异步逻辑。这种设计充分体现了c语言的灵活性。

void menu() {
	printf("****************************​\n"
		"​**********1. add ​************\n"
		"​**********2. sub ************\n"
		"​**********3. mul ************\n"
		"​**********4. div ************\n"
		"​**********0. exit ***********\n"
		"输入你的选项 --------->");
}
void add(int x, int y)
{
	int ret = x + y;
	printf("执行的结果为 %d ", ret);
}

void sub(int x, int y)
{
	int ret = x - y;
	printf("执行的结果为 %d ", ret);
}

void mul(int x, int y)
{
	int ret = x * y;
	printf("执行的结果为 %d ", ret);
}

void div(int x, int y)
{
	int ret = x / y;
	printf("执行的结果为 %d ", ret);
}

int main()
{
	int i = 0, j = 0;
	int input = 1;
	void (*p[5])(int a, int b) = {0,add,sub,mul,div};
	do
	{
		menu();
		scanf("%d",&input);
		if (input <= 4 && input >= 1)
		{
			printf("请输入两个数字:");
			scanf("%d %d", &i, &j);
			(*p[input])(i, j);//函数的使用
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
			printf("输入错误,请重新输入\n");
	} while (input);
	return 0;
}

我们来看这段代码这段代码有多段的函数,我们是否能通过回调函数来实现改进,有代码如下:

#define  _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
void menu()
{
	printf(
		"**************欢迎使用计算器 ************\n"
		"**************1.加法  2.减法 ************\n"
		"**************3.除法  4.乘法 ************\n"
		"**************  0. 退出      ************\n"
		"*****************************************\n"
		"****************请输入你需要功能的序号—>\n"
	);
} // 菜单的函数,来完成整个程序的菜单

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 calc(int(*pf)(int,int))//通过函数指针传进去
{
	int ret = 0; //定义 ret 来接受结果
	int x, y = 0;
	printf("请输入你要计算的两个值:\n");
	scanf("%d %d", &x,&y);
	ret = pf(x, y);
	printf("ret = %d\n", ret);
}

int main()
{
	int input = 0;
	do
	{
		menu();//每一次都应该输出菜单
		scanf("%d",&input);
		switch (input) {
		case 1: calc(add); break;
		case 2: calc(sub); break;
		case 3: calc(div); break;
		case 4: calc(mul); break;
		//函数名也是地址,将函数的地址传入函数指针 int(*pf)(int int)
		case 0: printf("已经退出\n"); break;
		default:
			printf("输入错误,请输入其他值\n");
			while (getchar() != '\n'); // 清空缓冲区
		}
	} while (input);
}

这段代码就是用来函数调用来实现的:
函数回调(Callback)​​ 的核心实现体现在calc函数和switch分支的动态绑定上

  1. 函数指针的定义
int calc(int(*pf)(int, int)) { // pf是函数指针参数
    int ret = pf(x, y); // 通过指针调用具体函数
}
  • ​**int(*pf)(int, int)**​:定义了一个函数指针pf,该指针指向一个接受两个int参数并返回int的函数。
  • 动态绑定​:通过将不同函数(如addsub)的地址传递给calc,实现了同一接口处理多种操作。
  1. 回调函数的触发和注册
   switch (input) {
    case 1: calc(add); break; // 传递add函数地址
    case 2: calc(sub); break; // 传递sub函数地址
    ...
}
  • 注册阶段​:用户选择操作(如加法)时,将add函数地址作为参数传递给calc
  • 触发阶段​:在calc内部通过pf(x, y)调用实际绑定的函数(如add),完成计算。

2. qsort函数的使用

2.1 qsort函数的定义:

以下是关于C语言中qsort函数的详细解析:
qsort是C标准库(<stdlib.h>)提供的通用快速排序函数,支持对任意数据类型数组的排序。其核心原理基于分治法,通过递归将数组划分为子序列并独立排序,最终合并结果,​函数原型​:
qsort 是 C 标准库中提供的一个函数,用于对数组进行快速排序。它在 <stdlib.h> 头文件中定义。qsort 使用的是快速排序算法(quicksort),这是一种高效的排序算法,平均时间复杂度为 O(n log n)。
C 库函数 void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*)) 对数组进行排序。
在这里插入图片描述

  • base: 指向待排序数组的第一个元素的指针。
  • nitems: 数组中的元素数量。
  • size: 数组中每个元素的大小(以字节为单位)。
  • compar: 比较函数的指针,该函数用于比较两个元素。比较函数应当返回一个整数,表示比较结果:
    • 小于零:表示第一个元素小于第二个元素。
    • 等于零:表示两个元素相等。
    • 大于零:表示第一个元素大于第二个元素。
      这个比较和排序很想我们在[[C语言 5:指针及其拓展(1)#7.冒泡排序:]]所写的冒泡排序,不过我们用的冒泡排序只能使用给int整形来进行排序,此时qsort排序可以进行多种排序,他正是用了函数回调,可以做到排序结构体和浮点数等其他的数。
      qsort通过通用化设计回调函数机制,实现了对任意数据类型的灵活排序。

2.2qsort函数的使用

2.2.1 qsort 函数排序int型数组:

我们按照定义来尝试去使用排序函数去排序int型,代码如下:

int_compare(const void *p1,const void *p2)
{
	return (*(int*)p1 - *(int*)p2);
	//强制转换为int型,然后相减
}

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

在C语言的qsort函数中,使用const void*作为参数类型是为了实现通用性安全性的双重目标。以下是详细解释:
void*的作用:通用指针类型

  1. 支持任意数据类型
    qsort是一个通用排序函数,需支持对任意类型数据​(如int、结构体、字符串等)的排序。
    • void*是“无类型指针”,可接受任何类型的指针(如int*char**),避免为每种数据类型单独编写排序函数。
    • 在比较函数中​:通过强制类型转换(如*(int*)p1)将其还原为具体类型
  2. 内存操作灵活性
    qsort通过void*size参数(元素大小)计算偏移,实现对不同大小元素的统一内存访问:
    void *element = (char*)base + i * size; // 通用地址计算
    

const的作用:数据保护

  1. 防止意外修改
    const void*表示指针指向的内容是只读的,禁止在比较函数中修改数据。例如,若误写为*(int*)p1 = 0,编译器会报错
    error: assignment of read-only location ‘*(int*)p1’
    
  2. 语义明确性
    • 对排序函数而言,比较操作不应改变数据内容const明确告知调用者数据不会被修改。
    • 若比较函数可能修改数据,排序结果将不可预测。
      为何不直接使用int*作为参数类型?​
  • 若比较函数声明为int(*)(int*, int*),则qsort只能用于int数组,失去通用性。

  • 使用void*允许同一函数处理多种数据类型(如结构体、字符串)。
    为何不省略const?​

  • 从语法上可以省略,但会降低代码安全性。例如,以下代码能编译通过但可能导致错误:

    int unsafe_compare(void *p1, void *p2) {
        *(int*)p1 = 0; // 危险操作:修改原数据
        return *(int*)p2 - *(int*)p1;
    }
    
  • const强制开发者遵守“只读”约定,减少潜在错误
    总结:

特性void*的作用const的作用
通用性支持任意数据类型,适配qsort的通用设计不涉及
安全性不直接涉及防止比较函数意外修改原数据
类型操作需强制类型转换以访问具体数据不涉及

通过void*const的配合,qsort实现了类型安全、高复用性的排序功能,是C语言通用库设计的经典范例。

我们将在下篇文章讲述qsort排序结构体

<think>嗯,用户想了解C++指针的使用教程和常见问题。首先,我需要整理指针的基本概念,比如什么是指针、声明和初始化方法。记得要分步骤讲解,确保结构清晰。然后,常见的操作部分,像解引用、指针运算、动态内存分配这些都要覆盖到。用户可能对空指针和野指针不太清楚,这里要重点解释,并举例子说明可能的问题。 接下来是常见问题部分,用户提到的悬挂指针、内存泄漏、指针类型不匹配都需要详细说明。特别是内存泄漏,很多新手容易忘记释放内存,这里要强调new和delete的配对使用。还有指针悬挂的问题,可以引入Qt的QPointer作为解决方案,不过要注明这是第三方库的内容,可能超出标准C++的范围。 引用部分需要正确标注,比如引用[2]提到指针的基本概念,引用[4]提到空指针误用的情况。要注意在相关段落末尾自然地加上引用标识。代码示例要使用正确的语法,比如指针声明、动态分配内存的代码片段,确保用户能看。 可能用户是初学者,所以语言要尽量通俗,避免过于专业的术语,必要时用比喻帮助理解。比如指针就像地址簿中的地址,解引用就像根据地址找到房子。同时,要提醒常见陷阱,比如未初始化指针导致的崩溃,以及如何避免这些问题。 最后,生成的相关问题要围绕用户的需求,可能他们还想知道如何检测内存泄漏、智能指针的使用,或者指针和引用的区别。这些问题可以作为扩展,帮助用户进一步学习。检查是否所有数学表达式都用了$...$格式,独立公式用$$...$$,确保格式正确。总之,整个回答要逻辑清晰,覆盖用户的需求点,同时提供实用的示例和解决方案。</think>### C++指针使用教程与常见问题解析 #### 一、指针基础概念 1. **指针的定义** 指针是存储变量内存地址的特殊变量,通过`*`符号声明。例如: ```cpp int num = 10; int *ptr = &num; // ptr指向num的地址 ``` 指针的直接操作内存特性使其在动态内存管理和数据结构中至关重要[^2]。 2. **指针的初始化** - 未初始化的指针可能成为**野指针**,导致程序崩溃。 - 建议初始化时赋值为`nullptr`(C++11后推荐): ```cpp int *ptr = nullptr; // 安全的初始化方式 ``` #### 二、指针常见操作 1. **解引用(Dereference)** 使用`*`访问指针指向的值: ```cpp int value = *ptr; // 获取ptr指向的值 *ptr = 20; // 修改ptr指向的值 ``` 2. **指针运算** 指针支持加减运算,用于数组遍历(步长由类型决定): ```cpp int arr[3] = {1,2,3}; int *p = arr; p++; // 指向arr[1] ``` 3. **动态内存分配** 使用`new`和`delete`管理堆内存: ```cpp int *p = new int(5); // 分配内存并初始化为5 delete p; // 释放内存 ``` #### 三、常见问题与解决方案 1. **悬挂指针(Dangling Pointer)** - **问题**:指向已释放内存的指针可能引发未定义行为。 - **解决**:释放内存后立即置空指针: ```cpp delete p; p = nullptr; // 避免悬挂 ``` 2. **内存泄漏** - **问题**:未释放`new`分配的内存。 - **解决**:确保`new`和`delete`成对使用,或使用智能指针(如`std::unique_ptr`)。 3. **指针类型不匹配** - **错误示例**: ```cpp double *dPtr = new int(5); // 类型不兼容 ``` - **修正**:使用类型一致的指针4. **空指针解引用** - **问题**:未检查空指针直接使用: ```cpp if (ptr != nullptr) { /* 安全操作 */ } ``` 未检查可能导致程序崩溃[^4]。 #### 四、高级应用与工具 1. **智能指针** C++11引入`std::shared_ptr`、`std::unique_ptr`等,自动管理内存生命周期: ```cpp #include <memory> std::unique_ptr<int> smartPtr(new int(10)); // 自动释放内存 ``` 2. **Qt的QPointer(第三方库)** 用于解决对象删除后指针悬挂问题,自动置空指针[^3]: ```cpp QPointer<QObject> objPtr = new QObject; delete objPtr.data(); // objPtr自动变为nullptr ``` #### 五、代码示例:安全指针操作 ```cpp #include <iostream> using namespace std; int main() { int *ptr = new int(100); // 动态分配内存 cout << "Value: " << *ptr << endl; delete ptr; // 释放内存 ptr = nullptr; // 避免悬挂 if (ptr != nullptr) { cout << *ptr; // 此处不会执行 } return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值