C语言进阶⑪(指针上)(知识点和对应练习)回调函数模拟实现qsort。(下)

简介: C语言进阶⑪(指针上)(知识点和对应练习)回调函数模拟实现qsort。

C语言进阶⑪(指针上)(知识点和对应练习)回调函数模拟实现qsort。(中):https://developer.aliyun.com/article/1513046

6. 函数指针数组

函数指针数组就是存放函数指针数组

数组是一个存放相同类型数据的存储空间,我们已经学习了指针数组,

比如:

 
int *arr[10];
//数组的每个元素是int*

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

 
int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10];

答案是:parr1

parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?

是 int (*)() 类型的函数指针。

 
int Add(int x, int y) 
{
    return x + y;
}
 
int Sub(int x, int y) 
{
    return x - y;
}
 
int main()
{
    int (*pf)(int, int) = Add;
    int (*pf2)(int, int) = Sub;
 
    int (*pfArr[2])(int, int) = { Add, Sub };//pfArr 就是函数指针数组
 
    return 0;
}

函数指针数组的用途:转移表

6.1函数指针数组的应用

例子:(计算器)

 
#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 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;
}
int main()
{
    int x = 0, y = 0;
    int input = 1;
    int ret = 0;
    do
    {
        menu();
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = add(x, y);
            printf("ret = %d\n", ret);
            break;
        case 2:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = sub(x, y);
            printf("ret = %d\n", ret);
            break;
        case 3:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = mul(x, y);
            printf("ret = %d\n", ret);
            break;
        case 4:
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = div(x, y);
            printf("ret = %d\n", ret);
            break;
        case 0:
            printf("退出程序\n");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    } while (input);
    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 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;
}
int main()
{
    int x = 0, y = 0;
    int input = 1;
    int ret = 0;
    int(*p[5])(int x, int y) = { NULL, add, sub, mul, div }; //转移表 《C和指针》
    do
    {
        menu();
        printf("请选择:");
        scanf("%d", &input);
        if ((input <= 4 && input >= 1))
        {
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = (*p[input])(x, y);
            printf("ret = %d\n", ret);
        }
        else if (input == 0)
        {
            printf("退出程序");
        }
        else
        {
            printf("选择错误,重新选择\n");
        }
    } while (input);
    return 0;
}

解析:这就是函数指针数组的应用。接收一个下标,通过下标找到数组里的某个元素,这个元素如果恰好是一个函数的地址,然后去调用那个函数。它做到了一个 "跳板" 的作用,所以通常称这种数组叫做 转移表(转移表在《C和指针》这本书中有所提及)。

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

指向函数指针数组的指针是一个 指针

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

如何定义?

 
int Add(int x, int y) 
{
    return x + y;
}
 
int main()
{
    int arr[10] = { 0 };
    int(*p)[10] = &arr; // 取出数组的地址
 
    int (*pfArr[4])(int, int); // pfArr是一个数组 - 函数指针的数组
    // ppfArr是一个指向[函数指针数组]的指针
    int (*(*ppfArr)[4])(int, int) = &pfArr;
    // ppfArr 是一个数组指针,指针指向的数组有4个元素
    // 指向的数组的每个元素的类型是一个函数指针 int(*)(int, int)
 
    return 0;
}

7.1指针的总结

8. 回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个

函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,

用于对该事件或条件进行响应。

下面使用回调函数模拟实现qsort。

8.1 qsort函数介绍:

引入:我们以前自己实现的冒泡排序函数只能排序整型顺序,如果我们要排序字符串或者一个结构体,我们是不是要单独重新实现这个函数呢?而 qsort 函数可以帮我们排任意想排的数据类型。

说明:qsort 函数是C语言编译器函数库自带的排序函数( 需引入头文件 stdlib.h ),能够排序任意数据类型的数组其中包括整形,浮点型,字符串甚至还有自定义的结构体类型。

qsort函数定义:

注意:这里第一次使用 void* 的指针,void* 可以指向任意类型的地址,但是void * 类型的指针不能进行加减操作,也就无法移动

1.首元素地址base

我们要排序一组数据,首先我们需要找到这组数据在哪,因此我们直接将首元素的地址传给qsort函数来确定从哪开始排序。

2.元素个数num

我们知道了从哪开始,也要知道在哪结束才能确定一组需要排序的数据,但是我们不方便直接将结尾元素的地址传入函数,因此我们将需要排序的元素的个数传给qsort函数来确定一组数据。

3.元素大小width

我们知道qsort函数能排序任意数据类型的一组数据,因此我们用void * 类型的指针来接收元素,但是我们知道void * 类型的指针不能进行加减操作,也就无法移动,那么在函数内部我们究竟用什么类型的指针来操作变量呢?我们可以将void * 类型的指针强制类型转换成char * 类型的指针后来操作元素,因为char * 类型的指针移动的单位字节长度是1个字节,我们只需要再知道我们需要操作的数据是几个字节就可以操作指针从一个元素移动到下一个元素,因此我们需要将元素大小传入qsort函数

4.自定义比较函数compar

第四个参数是一个函数指针

需要告诉qsort函数我们希望数据按照怎么的方式进行比较,比如对于几个字符串,可以比较字符串的大小(strcmp),也可以比较字符串的长度(strlen),因此要告诉qsort函数我们希望的比较方式,就需要传入一个比较函数compar,简写为cmp。


演示一下qsort函数的使用:

排序int 类型:

 
#include <stdio.h>
#include<stdlib.h>//qsort的头文件
//qsort函数的使用者得实现一个比较函数
int int_cmp(const void* p1, const void* p2)//类型要和qsort第四个参数一致
{
    return (*(int*)p1 - *(int*)p2);//比较什么类型的元素就强制类型转化为什么类型的指针
}
int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    int i = 0;
 
    qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
    for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
    return 0;
}

排序结构体类型:写结构体的cmp函数(升序):

( 需求:结构体内容为 " 姓名 + 年龄 ",使用qsort,实现按年龄排序和按姓名排序 )

 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
struct Stu
{
    char name[20];
    int age;
};
 
int cmp_struct_age(const void* p1, const void* p2)
{
    return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
 
int cmp_struct_name(const void* p1, const void* p2)
{
    return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
 
void struct_sort()
{
    // 使用qsort函数排序结构体数据
    struct Stu s[3] = {
        {"zhangsan", 20},
        {"lisi", 40},
        {"wangwu", 30}
    };
    int sz = sizeof(s) / sizeof(s[0]);
    // 按照年龄排序
    //qsort(s, sz, sizeof(s[0]), cmp_struct_age);
    // 按照名字来排序
    qsort(s, sz, sizeof(s[0]), cmp_struct_name);
}
 
int main()
{
    struct_sort();
    return 0;
}

现在是升序,如果我想实现降序呢?

很简单,只需要把 p1 - p2 换为 p2 - p1 即可:

8.2回调函数模拟实现qsort(采用冒泡的方式):

 
#include <stdio.h>
#include <string.h>
struct Stu
{
    char name[20];
    char age;
};
// 模仿qsort实现一个冒泡排序的通用算法
void Swap(char* buf1, char* buf2, int width) 
{
    int i = 0;
    for (i = 0; i < width; i++) 
    {
        //一个字节一个字节交换
        char tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
        buf1++;
        buf2++;
    }
}
void bubble_sort_q(
    void* base, // 首元素地址
    int sz, // 元素总个数
    int width, // 每个元素的大小
    int (*cmp)(const void* p1, const void* p2) // 两个元素的函数
)
{
    // 确认趟数
    int i = 0;
    for (i = 0; i < sz - 1; i++) 
    {
        // 一趟排序
        int j = 0;
        for (j = 0; j < sz - 1 - i; j++) {
            // 两个元素比较   arr[i] arr[j+i]
        if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) 
            {
                //传参给用户传进来的cmp函数,但不知道比较的数据的类型
                //把base强制类型转化为char*,一个字节一个字节交换
                //把两个元素的地址传给cmp函数,升序,>0就交换
           Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
            }
        }
    }
}
 
int cmp_struct_age(const void* p1, const void* p2) 
{
    return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int cmp_struct_name(const void* p1, const void* p2)
{
    return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void struct_sort()
{
     //使用qsort排序结构体数据
        struct Stu s[3] = {
        {"zhangsan", 20},
        {"lisi", 40},
        {"wangwu", 30}
        };
    int sz = sizeof(s) / sizeof(s[0]);
    // 按照年龄排序
    bubble_sort_q(s, sz, sizeof(s[0]), cmp_struct_age);
    // 按照名字排序
    bubble_sort_q(s, sz, sizeof(s[0]), cmp_struct_name);
}
 
void print_arr(int arr[], int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++) 
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
 
int cmp_int(const void* p1, const void* p2) 
{
    // 升序: p1 - p2
    return *(int*)p1 - *(int*)p2;
}
void int_sort()
{
    int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    // 排序
    bubble_sort_q(arr, sz, sizeof(arr[0]), cmp_int);
    // 打印
    print_arr(arr, sz);
}
 
int main()
{
    int_sort();
    // struct_sort();
    return 0;
}

本篇完。

目录
相关文章
|
2月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
124 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
2月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
186 9
|
2月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
65 7
|
3月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
C语言
我们要掌握好多少C语言知识点才能做好C语言项目?
我们要掌握好多少C语言知识点才能做好C语言项目?
1268 0
|
1月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
63 23
|
1月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
68 15
|
1月前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
61 24
|
1月前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
66 16
|
1月前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
37 3

热门文章

最新文章