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;
}

本篇完。

目录
相关文章
|
16天前
|
C语言
【c语言】指针就该这么学(3)
本文介绍了C语言中的函数指针、typedef关键字及函数指针数组的概念与应用。首先讲解了函数指针的创建与使用,接着通过typedef简化复杂类型定义,最后探讨了函数指针数组及其在转移表中的应用,通过实例展示了如何利用这些特性实现更简洁高效的代码。
12 2
|
17天前
|
C语言
如何避免 C 语言中的野指针问题?
在C语言中,野指针是指向未知内存地址的指针,可能引发程序崩溃或数据损坏。避免野指针的方法包括:初始化指针为NULL、使用完毕后将指针置为NULL、检查指针是否为空以及合理管理动态分配的内存。
|
17天前
|
C语言
C语言:哪些情况下会出现野指针
C语言中,野指针是指指向未知地址的指针,通常由以下情况产生:1) 指针被声明但未初始化;2) 指针指向的内存已被释放或重新分配;3) 指针指向局部变量,而该变量已超出作用域。使用野指针可能导致程序崩溃或不可预测的行为。
|
16天前
|
C语言
【c语言】qsort函数及泛型冒泡排序的模拟实现
本文介绍了C语言中的`qsort`函数及其背后的回调函数概念。`qsort`函数用于对任意类型的数据进行排序,其核心在于通过函数指针调用用户自定义的比较函数。文章还详细讲解了如何实现一个泛型冒泡排序,包括比较函数、交换函数和排序函数的编写,并展示了完整的代码示例。最后,通过实际运行验证了排序的正确性,展示了泛型编程的优势。
15 0
|
16天前
|
编译器 C语言
【c语言】指针就该这么学(2)
本文详细介绍了指针与数组的关系,包括指针访问数组、一维数组传参、二级指针、指针数组和数组指针等内容。通过具体代码示例,解释了数组名作为首元素地址的用法,以及如何使用指针数组模拟二维数组和传递二维数组。文章还强调了数组指针与指针数组的区别,并通过调试窗口展示了不同类型指针的差异。最后,总结了指针在数组操作中的重要性和应用场景。
14 0
|
5月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
1月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
20 0
|
2月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
|
3月前
|
C语言
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
|
3月前
|
C语言
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
下一篇
无影云桌面