一. 函数指针的两种用途

1.1 回调函数

回调函数就是一个通过函数指针调用的函数

回调函数三要素

  1. 定义一个回调函数
  2. 将回调函数的指针注册给调用者
  3. 当特定的事件或条件发生时,调用者使用函数指针调用回调函数

回调函数的意义在于实现模块间的解耦,模块化编程

应用程序常会调用库函数,但有些库函数却要求应用程序先传给它一个函数,好在合适的时候调用,以完成目标任务。那个被传入,后又被调用的函数就是回调函数

举例:有一家旅馆提供叫醒服务,但是要求旅客自己决定叫醒的方法,可以是打电话,敲门或者睡懒觉不要被叫醒。这个叫醒服务是旅馆提供的,相当于库函数,但是叫醒的方式是由旅客自己决定的,也就是回调函数。旅客告诉旅馆怎么叫醒自己,对应把回调函数传入库函数的动作。

#include <stdio.h>

typedef void (*callway)(char*);

void knockDoor(char* name)
{
    printf("I'll Knock at a door... for %s\n", name);
}

void callUp(char* name)
{
    printf("I'll Call up... for %s\n", name);
}

void overLook(char* name)
{
    printf("I'll Overlook... for %s\n", name);
}

void callBed(char* name, callway call_way)
{
    printf("Day breaks!!!   ");
    call_way(name);
}

  
int main(void)
{  
    char *customs[3] = {"alice", "Jack", "Eish"};
    callway customs_call_way[3] = {knockDoor, callUp, overLook};
    for(int i = 0; i < 3; ++i)
    {
        callBed(customs[i], customs_call_way[i]);

        printf("===========\n");
    }
    return 0;
}

1.2 转移表

转移表实际就是一个函数指针数组,也称分支表

用于将程序控制转移到程序另一部分。关于跳转到程序另一部分最常用的方法是使用 switch 语句,但是使用 switch 语句存在的一个弊端就是如果分支过多,会造成程序的冗长,而转移表刚好能解决这个问题,对于优化程序的结构有很大的帮助

#include <stdio.h>

enum{
    add,
    sub,
    mul,
    div,
};

double addNum(double num_1, double num_2){
    return num_1 + num_2;
}

double subNum(double num_1, double num_2){
    return num_1 - num_2;
}

double mulNum(double num_1, double num_2){
    return num_1 * num_2;
}

double divNum(double num_1, double num_2){
    return num_1 / num_2;
}

int main(void){
    double result = 0;
    int operator = 0;
    double num_1 = 0;
    double num_2 = 0;
    printf("Please input two numbers: "); scanf("%lf, %lf", &num_1, &num_2);
    printf("Please input operator: +[0], -[1], *[2], /[3] "); scanf("%d", &operator);
    switch(operator){
        case add:
            result = addNum(num_1, num_2);
            break;
        case sub:
            result = subNum(num_1, num_2);
            break;
        case mul:
            result = mulNum(num_1, num_2);
            break;
        case div:
            result = divNum(num_1, num_2);
            break;
        default:
            printf("without this operator\n");
            break;
    }
    printf("result = %lf\n", result);  
    return 0;
}
#include <stdio.h>

double addNum(double num_1, double num_2){
    return num_1 + num_2;
}

double subNum(double num_1, double num_2){
    return num_1 - num_2;
}

double mulNum(double num_1, double num_2){
    return num_1 * num_2;
}

double divNum(double num_1, double num_2){
    return num_1 / num_2;
}

int main(void){
    double (*transfer[4])(double, double) = {addNum, subNum, mulNum, divNum};
    int operator = 0;
    double num_1 = 0;
    double num_2 = 0;
    printf("Please input two numbers: "); scanf("%lf, %lf", &num_1, &num_2);
    printf("Please input operator: +[0], -[1], *[2], /[3] "); scanf("%d", &operator);
    printf("result = %lf\n", transfer[operator](num_1, num_2));  
    return 0;
}

二. qsort 函数

qsort 函数是 C 库中实现的快速排序算法,包含在 stdlib.h 头文件中

函数原型如下

void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *));
  1. 第一个参数是需要排序的数组的基地址,因为是 void * 类型,所以此函数可以给任何类型的数组进行排序

  2. 第二个参数是待排序数组有多少个元素

  3. 第三个参数是单个数组元素的大小

  4. 第四个参数就是回调函数,一个指向函数的指针,其作用是规定排序规则

回调函数的返回值大于 0 时,交换第一个参数和第二个参数的顺序,回调函数的形参是指向数组元素的指针,强转要转成正确的类型,否则会发生奇怪的事情

int 数据类型的排序

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

// 从小到大
int compar(const void *elem_1, const void *elem_2){
    return *(const int*)elem_1 - *(const int *)elem_2;
}

void printArr(int *arr, int size){
    for(int i = 0; i < size; ++i){
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main(void){
    int arr[6] = {-5, 2, 3, -6, 1};
    printArr(arr, 6);

    qsort(arr, 6, sizeof(int), compar);
    printArr(arr, 6);    
    return 0;
}

结构体数据类型的排序

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

typedef struct{
    char name[8];
    int score;
}Student;

// 根据学生成绩从小到大排列
int compar(const void *elem_1, const void *elem_2){
    return (**(const Student **)elem_1).score - (**(const Student **)elem_2).score;
}

int main(void){
    Student student_1 = {"Alice", 100};
    Student student_2 = {"Jack", 99};
    Student student_3 = {"Eish", 98};

    Student *class[3] = {&student_1, &student_2, &student_3};
    for(int i = 0; i < 3; ++i){
        printf("%d ", class[i]->score);
    }    
    printf("\n");

  
    qsort(class, 3, sizeof(Student *), compar);
    for(int i = 0; i < 3; ++i){
        printf("%d ", class[i]->score);
    }    
    printf("\n");
    return 0;
}

二维数组排序

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

typedef struct{
    char name[8];
    int score;
}Student;

// 根据每个一维数组的第一个元素由大到小排列
int compar(const void *elem_1, const void *elem_2){
    return (*(const int **)elem_2)[0] - (*(const int **)elem_1)[0];
}

void printArr(int **arr, int row, int col){
    for(int i = 0; i < row; ++i){
        for(int j = 0; j < col; ++j){
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

int main(void){
    int arr_create[3][3] = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };

    int *arr[3] = {arr_create[0], arr_create[1], arr_create[2]};
    printArr(arr, 3, 3);
    
    printf("----------------------------\n");
    qsort(arr, 3, 8, compar);
    printArr(arr, 3, 3);
    return 0;
}

作业

1. 编写函数,给定一个数组 array 和一个值 val,,删除所有数值等于 val 的元素,并返回移除后数组的新长度。

数组在内存空间的地址是连续的,所以在删除元素的时候,是通过移动其它元素,去覆盖要删除的元素,并不是让待删除元素凭空消失

示例1

输入:array = [3, 2, 2, 3],val = 3

输出:2

解释:删除后 array 的前 2 个元素均为 2,无需考虑数组中超出新长度后面的元素

示例2

输入:array = [0, 1, 2, 2, 3, 2],val = 2

输出:3

解释:删除后 array 的前 3 个元素为 0,1,3

2. 给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

示例

输入:s = 7, nums = [2,3,1,2,4,3]

输出:2

解释:子数组 [4,3] 是该条件下的长度最小的子数组。