转移表和回调函数的使用(袖珍计算器的编写 / 模仿qsort的功能实现一个通用的冒泡排序)

在上一篇已经对函数指针、函数指针的数组、函数指针数组的指针加以分析,那么如此繁多的 “指针/数组” 如何使用?
最常见的两个用途就是转换表和作为参数传递给另一个函数(回调函数)

转换表

我们取一段实现袖珍式计算器的程序代码:

switch(input);
case ADDresult=add(x,y);
    break;
case SUB:
    result=sub(x,y);
    break;
case MUL:
    result=mul(x,y);
    break;
case DIVresult=div(x,y);
    break;

我们可以看到,我们进行多少种运算,就需要写多少个case语句分支。对于一个新奇的具有上百个操作符的计算器,这条switch语句将会很长。

为了使用switch语句,表示操作符的代码必须是整数。如果它们是从零开始连续的整数,我们可以使用转换表来实现相同的任务。转换表就是一个函数指针数组。
创建一个转换表需要两个步骤:
首先,声明并初始化一个函数指针数组。唯一需要留心之处就是确保这些函数的原型出现在这个数组是声明之前。
double add(double,double);
double sub(double,double);
double mul(double,double);
double div(double,double);

...
double (*pfun[](double double)={ADD,SUB,MUL,DIV,...};
初始化列表中各个函数名的正确顺序取决于程序中用于表示每个操作符的整型代码。
第二个步骤是用下面这条语句替换前面整条switch语句。
ret=pfun[input](x,y)
input从数组中选择正确的函数指针,而函数调用操作符将执行这个函数。

下面就是用转移表的方法实现袖珍计算器

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.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 x, int y)
{
    return x + y;
}
int Sub(int x, int y)
{
    return x - y;
}
int Mul(int x, int y)
{
    return x * y;
}
int Div(int x, int y)
{
    return x / y;
}
void calc(int(*pfun)(int ,int))
//函数的地址传参,使用函数指针,该指针指向的函数有两个整型参数,返回类型为int
{
    int x = 0;
    int y = 0;
    int ret = 0;
    printf("请输入两个数:\n");
    assert(2==scanf("%d%d", &x, &y));
    ret = pfun(x, y);
    printf("ret=%d\n", ret);
}
int main()
{
    int(*pfun[5])(int, int) = { 0, Add,Sub,Mul,Div};
//函数指针的数组(作用相当于switch语句)---转移表
//指向+、-、*、/这样的函数,参数为两个整型,返回类型为整型,数组第一个元素地址的下标为0,联系整个代码Add定义的为1,为了更好使用,所以将第一个空过去(设置为0)
    int choose = 0;
    do{
        menu();
        printf("请选择:");
        assert(1 == scanf("%d", &choose));
        if (choose >= 1 && choose <= 4)
            calc(pfun[choose]);
        //函数地址传参,将使用的函数
        else if (choose == 0){
            printf("退出程序\n");
            break;
        }
        else
            printf("非法输入!\n");
    } while (choose);
    system("pause");
    return 0;
}


简单的来说,转移表就是将存在多个同类型的函数放入函数指针数组中,达到简化了函数调用的代码量,从而优化程序。
注意:在转换表中,越界下标引用就像在其他任何数组中一样是不合法的。但一旦出现这种情况,把它诊断出来要困难的多。所以,在使用转移表时,一定要保证转移表所使用的下标位于合法的范围内。

回调函数

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

我们无法再上下文环境中为回调函数编写一个准确的原型,因为我们并不知道进行比较的值的类型。事实上,我们需要查找函数能作用于任何类型的值。解决这个难题的方法就是把参数类型声明为 void * ,表示“一个指向未知类型的指针”。

在这里我们使用回调函数模拟实现qsort(采用冒泡的方式)给大家讲解
首先给大家介绍一下什么是qsort函数,怎么使用!

qsort函数

  • 编译器函数库自带的快速排序函数

  • qsort 的函数原型是

void qsort(void*base,size_t num,size_t width,int(__cdecl*compare)(const void*,const void*));
各参数:1 待排序数组首地址 2 数组中待排序元素数量 3 各元素的占用空间大小 4 指向函数的指针
其中compare( (void *) & elem1, (void *) & elem2 )就是我们调用方自己要编写实现的函数,返回要求是

  • 用qsort实现整型排序
int cmp_int(const void*el, const void*el)//整型比较回调函数
{
    return (*(int*)el) - (*(int*)el);
}
void print_int(int arr[],int sz)//打印数组
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
int main()
{
    int arr[] = { 1,3,5,7,9,2,4,6,8,0 };
    int sz = sizeof(arr)/sizeof(*arr);
    qsort(arr, sz, sizeof(arr[0]), cmp_int);
    print_int(arr, sz);
    system("pause");
    return 0;
}

  • 用qsort实现结构体排序
    创建一个存储姓名和年龄的结构体,分别通过姓名或者年龄来进行排序
    • 通过姓名
      需要实现结构体中字符串的排序,恰好strcmp不仅可以比较字符串字符之间的大小,恰好返回值是int型。
struct STU
{
    char name[20];
    int age;
};
    void print_stu(struct st arr[],int sz)//打印
    {
        int i = 0;
        for (i = 0; i < sz; i++)
        {
            printf("name=%s age=%d\n", arr[i].name,arr[i].age);
        }
        printf("\n");
    }
int cmp_stu(const void*el, const void*el)// 用于结构体比较的回调函数
{
    return strcmp(((struct stu*)el)->name, ((struct stu*)el)->name);
}
int main()
{
    struct STU stu[3] = { {"张三",20},{"李四",30},{"王五"40 };
    int sz = sizeof(stu) / sizeof(stu[0]);
    qsort(stu, sz, sizeof(stu[0]), cmp_stu);
    print_stu(stu, sz);
    system("pasue");
    return 0;
}

  • 用年龄排序
    只需要将回调函数的name改成age的比较即可
int cmp_stu(const void*el, const void*el)// 用于结构体比较的回调函数
{
    return (((struct stu*)el)->age)-(((struct stu*)el)->age);
}

使用回调函数模拟实现qsort

思路:

  • 因为传入bubble_sort函数的指针是void*类型,为了能够适应各种类型,必须将传来得指针强制类型转换成(char*)类型,从而就可以利用目标元素得类型来准确判断每个元素所占空间,从而进行排序工作。

  • 其次就是自编比较回调函数的代码段。其中cmp(参数1,参数2),传参是传的比较两个数的地址,将base转换成char*,一次比较一个字节,下一个元素为其地址加上其宽度,即 * (char *)base+width

  • 对于交换,因为每个类型最小字节数是1 ,所以可以无差别得通过宽度进行每个字节得交换,在本设计中,我们把交换过程封装成Swap()函数来进行交换

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
struct STU
{
    char name[20];
    int age;
};
int cmp_int(const void * e1, const void * e2)
{
    assert(e1 && e2);
    return *(int *)e1 - *(int *)e2;
}
int cmp_stu(const void * e1, const void * e2)
{
    assert(e1 && e2);
    return strcmp(((struct STU *)e1)->name, ((struct STU *)e2)->name);
}
int cmp_stu_age(const void * e1, const void * e2)
{
    assert(e1 && e2);
    return (*(struct STU *)e1).age - (*(struct STU *)e2).age;
}
void print_int(int arr[], int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
void print_stu(struct STU stu[], int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("name=%s age=%d\n", stu[i].name,stu[i].age);
    }
    printf("\n");
}
void Swap(char * buf1, char * buf2, int width)//交换两个数
{
    int i = 0;
    assert(buf1 && buf2);
    for (i = 0; i < width; i++)
    {
        char tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
        buf1++;
        buf2++;
    }
}
void bubble_sort(void *base, int sz, int width, int (*cmp)(const void *e1, const void *e2))//冒泡排序
{
    int i = 0;
    int j = 0;
    assert(base && cmp);
    for (i = 0; i < sz - 1; i++)
    {
        for (j = 0; j < sz - 1 - i; j++)
        {
            if (cmp((char *)base + width*j, (char *)base + width*(j + 1))>0)
                Swap((char *)base + width*j, (char *)base + width*(j + 1), width);
        }
    }
}
int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    struct STU stu[3] = { { "张三", 20 }, { "李四", 30 }, { "王五", 40 } };
    int sz1 = sizeof(arr) / sizeof(arr[0]);
    int sz2 = sizeof(stu) / sizeof(stu[0]);
    //bubble_sort(arr, sz1, sizeof(arr[0]), cmp_int);//排序整型
    bubble_sort(stu, sz2, sizeof(stu[0]), cmp_stu);//排序结构体(名字)
    bubble_sort(stu, sz2, sizeof(stu[0]), cmp_stu_age);//排序结构体(年龄)
    //print_int(arr, sz1);//打印整型
    print_stu(stu, sz2);//打印结构体
    system("pause");
    return 0;
}




注意:
在使用比较函数中的指针之前,它们必须被强制转换成正确的类型。因为强制类型转换能够躲过一般的类型检查。

猜你喜欢

转载自blog.csdn.net/sifanchao/article/details/80189511
今日推荐