指针破解(下)指针和数组的应用,转移表和回调函数

转移表

上一篇中对指针,数组之间得声明形式进行了详细得介绍,那么如此繁多得声明该如何使用?其中应用之一就是通过将函数用指针得形式存放于函数指针数组,在不同得条件下进行相应的调用,也就是转移表

典例

  • C语言用功能函数实现加减乘除运算
    这个题目看起来似乎再简单不过,映现得思路是先将加减乘除分别用四个函数来进行封装,然后再main函数中用switch语句进行选择。
    但是如果这个这个题目不仅仅是要求加减乘除而是上百种运算,那么除了这些函数得定义,是否在main函数中也需要上百种case语句呢?
    显然不需要,我们有“哈雷”就不必再骑28自行车了,这里就用到了转移表

为了使用switch语句,表示操作符的代码必须是整数。如果它们是从零开始连续的整数,我们可以使用转换表来实现相同的任务。转换表就是一个函数指针数组。
创建一个转换表需要两个步骤。首先,声明并初始化一个函数指针数组。唯一需要留心之处就是确保这些函数的原型出现在这个数组的声明之前。
double add(double,double);
double sub(double,double);
double mul(double,double);
double div(double,double);
double (*oper_func[])(double,double)={add,sub,mul,div,…};
初始化列表中各个函数名的正确顺序取决于程序中用于表示每个操作符的整型代码。这个例子假定ADD是0,SUB是1,MUL是2,接下去以此类推。
第二个步骤是用下面这条语句替换前面整条switch语句!
cal(oper_func[input]);
其中cal函数中包含了功能函数得调用和数字得键入
oper从数组中选择正确的函数指针,而函数调用操作符将执行这个函数。

函数实现:

#include<stdio.h>
#include<Windows.h>
#include<assert.h>
//转换表
double ADD(double a, double b)
{
    return a + b;
}double SUB(double a, double b)
{
    return a - b;
}double MUL(double a, double b)
{
    return a * b;
}double DIV(double a, double b)
{
    return a / b;
}
void cal(double(*oper_func)(double, double))//运算模块
{
    double x = 0, y = 0, ret = 0;
    printf("Enter two numbers to calculate\n");
    scanf("%lf %lf", &x, &y);
    ret=oper_func(x, y);//此处使用函数调用操作符引用地址进行运算
    printf("%lf\n", ret);
 }
int main()
{
    double(*oper_func[5])(double, double) = { 0,ADD,SUB,MUL,DIV };
    int input = 0;
    do
    {
        printf("select one func,1.ADD 2.SUB 3.MUL 4.DIV 0.quit\n");
        scanf("%d", &input);
        if (input > 0 && input < 5)
        {
            cal(oper_func[input]);
        }
        else
        {
            printf("invalid num,try again\n");
        }
    } while (input);
    system("pause");
    return 0;
}}

3

  • 简单得来说,转移表为将存在多个同类型得函数放入函数指针数组中,大大简化了函数调用得代码量,从而使程序更简洁方便

回调函数

C语言在设计出场伊始,所有former必然都是徒步行走,仅仅是实现一个及其简单得hello world估计都得花上不少精力,为了方便程序员和发展,便有了品类繁多得库函数,程序员在进行一些比如排序等等必要算法时,不必埋头再写,而是直接调用库函数就可以实现功能。但是,如何让一个函数得功能再强大一些?例如排序问题,不同类型得操作数比较原则不同,那么如何让一个函数可以实现全类型排序呢?

这里就需要使用回调函数,由调用者自行提供一个用来比较目标类型大小得函数,将这个函数以指针得形式传递给排序函数,当排序函数运行到需要进行比较大小得时候。就需要通过指针调用这个比较大小得函数来进行后续操作,那么这就是回调函数。

qsort函数

c语言中当然也提供了具有回调函数功能得库函数,例如qsort

  • 函数原型
void qsort(void*base,size_t num,size_t width,int(__cdecl*compare)(const void*,const void*));

各参数:1 待排序数组首地址 2 数组中待排序元素数量 3 各元素的占用空间大小 4 指向函数的指针
其中int(__cdecl*compare)(const void*,const void*)就是我们调用方要实现的函数,下面是它的返回要求

Compare 函数的返回值描述
< 0elem1将被排在elem2前面
0elem1 等于 elem2
> 0elem1 将被排在elem2后面

有了这个要求,可见qsort是实现从小到大排序的。那么我们就很容易写出相应的类型排序,仅需返回两个指针解引用的差值即可。

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

我、

  • 实现结构体排序
    创建一个存储姓名和年龄的结构体,分别通过姓名或者年龄来进行排序

    • 通过姓名
      这次我们需要实现结构体中字符串的排序,恰好strcmp不仅可以比较字符串字符之间的大小,恰好返回值是int型。
struct st
{
    char name[19];
    int age;
};
    void print(struct st arr[],int sz)//打印
    {
        int i = 0;
        for (i = 0; i < sz; i++)
        {
            printf("%s %d\n" , arr[i].name,arr[i].age);
        }
    }
int cmp(const void*elm1, const void*elm2)// 用于结构体比较的回调函数
{
    return strcmp(((struct st*)elm1)->name, ((struct st*)elm2)->name);
}
int main()
{
    struct st stu[3] = { {"liudehua",55},{"zazahui",46},{"yijianlian",32} };
    int sz = sizeof(stu) / sizeof(stu[0]);
    int width = sizeof(struct st);
    qsort(stu, sz, width, cmp);
    print(stu, sz);
    getchar();
    system("pasue");
    return 0;
}

3

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

4


用冒泡排序回调函数

现在我们编写bubble_sort实现同qsort同样的功能。

通过qsort得函数原型可以得知函数需要四个参数,分别是起始位置得指针,数组元素得个数,每个元素得宽度(字节数),cmp比较函数得指针
通过这些信息,就可以写出bubble_sort函数得原型

void bubble_sort(void *base, int sz, int width, int(*cmp)(void const*e1, void const*e2))

下面开始写函数得定义

  • 冒泡排序中如何书写需要判断得连个元素得指针?
    因为传入bubble_sort函数得指针是void*类型,为了能够适应各种类型,必须将传来得指针强制类型转换成(char*)类型,从而就可以利用目标元素得类型来准确判断每个元素所占空间,从而进行排序工作。函数定义如下
  • 每个对应类型元素如何交换?
    对于交换,因为每个类型最小字节数是1 ,所以可以无差别得通过宽度进行每个字节得交换,在本设计中,我们把交换过程封装成Swap()函数来进行交换
void Swap(char*e1, char*e2, int width)//此处功能应sort函数内实现,因为此处可以无类别交换,这里为了简化代码易于阅读
{
    assert(e1);
    assert(e2);
    char tmp = 0;
    while (width--)//逐字节交换
    {
        tmp = *e1;
        *e1 = *e2;
        *e2 = tmp;
        e1++;
        e2++;
    }
}
void bubble_sort(void *base, int sz, int width, int(*cmp)(void const*e1, void const*e2))
{
    assert(base&&cmp);//对传入得指针进行检测
    int i = 0;
    int j = 0;
    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);
            }
        }
    }
}

现在,同qsort一样,使用bubble_sort对一个结构体进行姓名排序

struct st
{
    char name[19];
    int age;
};
    void print(struct st arr[],int sz)//打印
    {
        int i = 0;
        for (i = 0; i < sz; i++)
        {
            printf("%s %d\n" , arr[i].name,arr[i].age);
        }
    }
int cmp(const void*elm1, const void*elm2)// 用于结构体比较的回调函数
{
    return strcmp(((struct st*)elm1)->name, ((struct st*)elm2)->name);
}
int main()
{
    struct st stu[3] = { {"aobama",55},{"kebi",46},{"mianjinge",32} };
    int sz = sizeof(stu) / sizeof(stu[0]);
    int width = sizeof(struct st);
    bubble_sort(stu, sz, width, cmp);
    print(stu, sz);
    getchar();
    system("pasue");
    return 0;
}

3


此处已经对C语言部分指针得声明形式,函数指针,转移表,回调函数进行了简单得介绍和应用,必然对指针,数组部分内容存在遗漏或可能存在误解之处,如有错误请指出。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值