@toc


前言

本篇文章的内容仍然为指针进阶的相关内容,继续上一篇文章的内容。【C语言进阶】——指针(一) (字符指针,数组指针,指针数组)

指针令人头秃!!!!!!!!!


5、函数指针

我们创建函数的时候,就会在内存中开辟一块空间,既然占用了内存空间,那就有对应的内存空间地址。
函数指针,顾名思义就是指向函数的指针。

image-20210812142717970

注意:
& 函数名 和 函数名均表示函数的地址!

数组名 != &数组名<br/>函数名 == &函数名


我们怎么把函数的地址存起来呢

image-20210812143528725

注意 :函数值指针的类型需要和返回函数值类型相同

就像这样:image-20210812144822994


思考:函数指针如何使用呢?

image-20210812143838886


通过函数指针,我们可以找到函数,然后去调用这个函数。 函数指针是 &
函数名,而我们函数调用的时候可以直接使用函数名,那么这里通过函数指针调用函数也可以这样写:

image-20210812144135208

image-20210812144332149

image-20210812145513627


既然这个地方的 可以省略,那么我们在使用的时候 可以用多个,也可以不要, 号在这里就是一个摆设,这个地方放 是为了方便理解 + 学习指针。

#include <stdio.h>
int Add(int x, int y)
{
    int z = 0;
    z = x + y;
    return z;
}

int main()
{
    int a = 10;
    int b = 20;

    int(*pa)(int, int) = Add;
    printf("%d\n", pa(2, 3));
    printf("%d\n", Add(2, 3));
    printf("%d\n",(*pa)(2, 3));
    printf("%d\n", (**pa)(2, 3));
    printf("%d\n", (***pa)(2, 3));
    //打印结果说明*的个数对函数调用没有影响
    return 0;
}

image-20210812145946237


阅读两个有意思的(阴间)代码:

//代码1
(*(void (*) ())0)();

//代码2
void(*signal(int, void(*)(int)))(int);

这两个代码均是书籍《C陷阱与缺陷》中提及的内容,推荐阅读这本书。

这两个代码怎么阅读和理解呢?

//代码1 
(*(void (*) ())0)()

image-20210812151117197

1.先将 void () ()理解清楚,这个是一个函数指针,指针指向的函数返回类型是void的。

2.再理解(void () ())0 我们之前的学习中学到过强制类型转换,需要将强制转换之后的类型用括号()括起来,这个地方就是将 0 强制类型转换成void() ()类型。 为什么要将0强制类型转换成void() ()类型呢? 原因:想要将0当做某个函数的地址

深入扩展:如果一个数字想要当作一个地址,直接使用这个数字肯定是不行的,而是要将这个数字转换成也给地址编号的类型。这也是代码1中为什么要将0强制类型转换的原因。

3.接着再看((void () ())0),对一个指针加, 就是对其进行解引用操作,对函数指针解引用就是找到这个函数


4.((void () ( ))0)( ) ,((void () ())0)找到函数后,对其使用(), 就是调用函数,所以((void () ())0)(); 是一个函数调用。 整体理解下来就是:将0强制类型转换成一个函数指针void(
)( ),再通过对这个函数指针进行解引用操作,找到这个函数,对其进行调用!


//代码2
void( *signal(int, void(*)(int)) ) (int);

1.signal是一个函数声明

2.signal函数的参数有两个,第一个是int类型,第二个是函数指针,该函数指针指向的函数的参数是int类型返回类型是void(void(*)(int))

3.*signal的返回类型是一个函数指针 因为 void(signal XXX)**,该函数指针指向的函数的参数是int类型,返回类型是void(void( )(int) )


这种形式看起来就比较复杂和难以理解,我们可以用typedef类型重定义对其进行简化:

在之前的学习中,我们使用过 typedef 来定义过无符号整型 typedef unsigned int u_int ;

但是我们并没有学过指针类型如何进行类型重定义,比如说 void( )(int) ,如果我们要将其进行重定义,可以写成:

typedef void( )(int) pfun_t; 这种形式吗?

image-20210812152903484

我们尝试将其放到编译器下,就会发现编译器报错,显然这种方式是行不通的!


思考:那么可以将这个类型重定义后的名称类似与定义函数指针一样放到( )里面吗?也就是typedef void(pfun_t)(int);

image-20210812153021764

写成这种形式后,编译器没有在报错或者警告,说明这种方式是对的,实际上正确的书写方式也正是这样!

完整应该是这样书写:

//代码2
void(* signal(int, void(*)(int)) )(int);

//那么这个类型可以简化成:
typedef  void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

虽然将一行代码变成了两行,但是简化后的代码更便于阅读和理解。

typedef 在进行类型重定义的时候,如果是函数指针类型,那么名称需要放到* 旁边,也就是说

不能写成这种形式:typedef void(*)(int) pfun_t;

<br/>正确的形式是:typedef void(*pfun_t)(int);

深入扩展:当函数的返回类型是一个函数指针的时候,函数名需要放到函数返回类型-- - 函数指针内部,而不是直接放到返回类型— 函数指针后面。
void( signal(int, void( )(int)) )(int);


6、函数指针数组

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

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

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

int ( *parr1[10] )( ); √

int* parr2[10] ( ); x

int (*)( ) parr3[10]; x

答案是:parr1 parr1先和[结合,说明parr1是数组。
数组的内容是什么呢 ?
是int(*)()类型的函数指针。


#include <stdio.h>
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;
}

int main()
{
    //指针数组
    //int* arr[5];
    int (*pa)(int, int) = Add;//Sub/Mul/Div
    //需要一个数组,这个数组可以存放4个函数的地址 - 函数指针的数组
    int(*parr[4])(int, int) = {Add, Sub, Mul, Div};//函数指针的数组
    int i = 0;
    for (i = 0; i < 4; i++)
    {
        printf("%d\n", parr[i](2, 3));//5 -1 6 0
    }
    return 0;
}

image-20210816140450585


练习:

定义一个函数;

char my_strcpy (char dest, const char* src);

练习要求:

1.写一个函数指针pf,能够指向my——strcpy
2.写一个函数指针数组 pfArr,能够存放4个my_strcpy函数的地址

答案:

1.char (pf)(char, const char )
2.char
(pfArr[4])(char, const char )


1.函数指针数组的用途

2.制作计算器

转移表 —《C和指针》这本书提及

请看下面简易计算器的例子:

1.使用switch……case语句

#include<stdio.h>
void cal_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;
}
int main()
{
    int input;//根据菜单提示输入操作符
    int x = 0;
    int y = 0;
    do
    {
        cal_menu();
        printf("请选择操作符:>>\n");
        scanf("%d", &input);
        printf("请输入两个操作数:>>\n");
        scanf("%d%d", &x, &y);
        switch (input)
        {
        case 1:
            printf("%d\n", Add(x, y));
            break;
        case 2:
            printf("%d\n", Sub(x, y));
            break;
        case 3:
            printf("%d\n", Mul(x, y));
            break;
        case 4:
            printf("%d\n", Div(x, y));
            break;
        case 0:
            printf("退出!\n");
            break;
        default:
            printf("选择错误!\n");
            break;
        }
    } while (input);
    return 0;
}

image-20210816141428723

这里我们用函数调用的方式实现两个数之间的加、减、乘、除操作,但是当我们想要退出程序的时候,选择0并不能之间退出游戏,还需要进行输入两个操作数,这种方式明显不符合我们的预期,对其稍微进行改造以下:


方法一:

下面我们通过函数指针数组的方式来实现吧!

#include<stdio.h>
void cal_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;
}
int main()
{
    int input;//根据菜单提示输入操作符
    int x = 0;
    int y = 0;
    int(*pArr[])(int, int) = { 0,Add,Sub,Mul,Div };
    do
    {
        cal_menu();
        printf("请选择操作符:>>\n");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
        case 2:
        case 3:
        case 4:
            printf("请输入两个操作数:>>\n");
            scanf("%d%d", &x, &y);
            printf("%d\n", (*pArr[input])(x, y)); //通过input找到 函数 加减乘除
            break;
        case 0:
            printf("退出!\n");
            break;
        default:
            printf("选择错误!\n");
            break;
        }

    } while (input);
    return 0;
}

image-20210816143831625


利用函数指针数组的方式,不仅可以简化我们的代码,也可以方面后面的扩展,比如说后面我们添加一个取模功能或一个异或功能,整个代码仅仅需要进行细微的调整即可满足我们的要求:

#include<stdio.h>
void cal_menu()
{
    printf("*****************************\n");
    printf("*****   1.Add     2.Sub  ****\n");
    printf("*****   3.Mul     4.Div  ****\n");
    printf("*****   5.Mod     6.Xor  ****\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;
}
int Mod(int x, int y)
{
    return x % y;
}
int Xor(int x, int y)
{
    return x ^ y;
}
int main()
{
    int input;//根据菜单提示输入操作符
    int x = 0;
    int y = 0;
    int(*pArr[])(int, int) = { 0,Add,Sub,Mul,Div,Mod,Xor };
    do
    {
        cal_menu();
        printf("请选择操作符:>>\n");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
        case 2:
        case 3:
        case 4:
        case 5:
        case 6:
            printf("请输入两个操作数:>>\n");
            scanf("%d%d", &x, &y);
            printf("%d\n", (*pArr[input])(x, y));
            break;
        case 0:
            printf("退出!\n");
            break;
        default:
            printf("选择错误!\n");
            break;
        }

    } while (input);
    return 0;
}

image-20210816143952902

这个地方我们仅仅调整了打印的菜单栏,函数指针数组初始化的值以及switch语句中加上case 5,case6。即可满足我们的要求,后续如果进行更多的功能扩展也可以按照这种方式,非常简单。


通过函数指针来实现

printf("请输入两个操作数:>>\n");
scanf("%d%d", &x, &y);
printf("%d\n", Add(x, y));

我们将这个重复的代码封装到一个函数中:

void cal_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 (*pf)(int, int))
{
    int x = 0;
    int y = 0;
    printf("请输入两个操作数:>>\n");
    scanf("%d%d", &x, &y);
    printf("%d\n", pf(x, y)); //(*)可以省略
}
int main()
{
    int input;//根据菜单提示输入操作符

    do
    {
        cal_menu();
        printf("请选择操作符:>>\n");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            Calc(Add);
            break;
        case 2:
            Calc(Sub);
            break;
        case 3:
            Calc(Mul);
            break;
        case 4:
            Calc(Div);
            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("**  5. xor       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;
}

int Xor(int x, int y)
{
    return x ^ y;
}

//主函数部分,实现计算函数调用
//使用函数指针数组
int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    //pfArr 是一个函数指针数组 - 转移表
    int(*pfArr[])(int, int) = { 0, Add, Sub, Mul, Div, Xor };
    do
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);
        if (input >= 1 && input <= 5)
        {
            printf("请输入两个操作数:>");
            scanf("%d%d", &x, &y);
            int ret = pfArr[input](x, y);//通过指针找到相应的函数
            printf("%d\n", ret);
        }
        else if (input == 0)
        {
            printf("退出\n");
        }
        else
        {
            printf("选择错误\n");
        }
    } while (input);
    return 0;
}

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

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

例如:

void cal_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 (*pf)(int, int))  //函数地址 传到(*pf)接收
{
    int x = 0;
    int y = 0;
    printf("请输入两个操作数:>>\n");
    scanf("%d%d", &x, &y);
    printf("%d\n", (*pf)(x, y));  //(*)可以省略
}

int main()
{
    int input;//根据菜单提示输入操作符

    do
    {
        cal_menu();
        printf("请选择操作符:>>\n");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            Calc(Add);
            break;
        case 2:
            Calc(Sub);
            break;
        case 3:
            Calc(Mul);
            break;
        case 4:
            Calc(Div);
            break;
        case 0:
            printf("退出!\n");
            break;
        default:
            printf("选择错误!\n");
            break;
        }
    } while (input);
    return 0;
}

image-20210816145539242

这种方法代码也比较简洁,扩展性也比较好。
这种方法利用了回调函数,后面会详解回调函数!


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

int Add(int x, int y)
{
    return x + y;
}

int main()
{
    int arr[10] = { 0 };
    int (*p)[10] = &arr;//取出数组的地址
    int(*p)[10] = &arr;//数组指针
    char arr1[5] = { 0 };//字符数组
    char* (*p1)[5] = &arr1;//字符数组指针

    int(*pf)(int, int)=Add ;//函数指针
    int (*pfArr[4])(int, int);//pfArr是一个数组-函数指针的数组

    //ppfArr是一个指向[函数指针数组]的指针
    int(*(*ppfArr)[4])(int, int) = &pfArr;//指向函数数组指针的指针
    //ppfArr 是一个数组指针,指针指向的数组有4个元素
    //指向的数组的每个元素的类型是一个函数指针 int(*)(int, int)

    return 0;
}

如何写出指向函数指针数组的指针呢?
方法一:

①首先我们应该要知道该指针指向的函数指针数组到底是什么?以上面的代码为例:对应的函数指针数组是:int(p3[4])(int, int) = { 0 }; 该函数指针数组的类型是:int()(int, int) //去掉变量名p3和数组[4]剩下的就是数组的类型 int()(int,int)
②用指向函数指针数组的指针变量名替换原来的数组变量名,也就是用(p4)替换p3得到的就是int((
p4)[4])(int, int) = &p3;//指向函数指针数组的指针

方法二:

①指向函数指针数组的指针本质上也是指针,所以我们首先写(p4),这个指针指向的一个数组,数组元素个数是4,所以右边要紧接着加上[4],
变成(p4)[4]
②此时要清楚知道数组的元素类型,以上面的代码为例:对应的函数指针数组是:int(p3[4])(int, int) = { 0 }; 该函数指针数组的类型是:int()(int, int) 将①中写好的内容(p4)[4],放到类型int()(int,int) 后面,得到:
int((*p4)[4])(int, int)//指向函数指针数组的指针

扩展:如果我们要继续往下写一个指向函数指针数组的指针数组,可以这样写:

int((p4[3])[4])(int, int)//指向函数指针数组的指针数组


8、回调函数

1.定义

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

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

举例:比如上面计算器的方法二实现方式就是利用了回调函数的方法。

//计算器
#include <stdio.h>
void menu()
{
    printf("**************************\n");
    printf("**  1. add       2. sub **\n");
    printf("**  3. mul       4. div **\n");
    printf("**  5. xor       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;
}

int Xor(int x, int y)
{
    return x ^ y;
}
void Calc(int (*pf)(int, int))//Calc就是一个回调函数
{
    int x = 0;
    int y = 0;
    printf("请输入两个操作数:>");
    scanf("%d%d", &x, &y);
    printf("%d\n", pf(x, y));
}
int main()
{
    int input = 0;

    do
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);

        switch (input)
        {
        case 1:
            Calc(Add);
            break;
        case 2:
            Calc(Sub);
            break;
        case 3:
            Calc(Mul);
            break;
        case 4:
            Calc(Div);
            break;
        case 0:
            printf("退出\n");
            ;
        default:
            printf("选择错误\n");
            break;
        }
    } while (input);
}

冒泡排序复习回顾: 整型数组排序。那我们将这样使 用函数:
冒泡排序的思想:两两相邻的元素进行比较,并且可能的话需要交换。

image-20210816152548937

一趟解决一个数字的排序问题,

第一趟最大值9出现到最右侧,

第二趟8到右侧第二位 10个数字,需要进行9躺冒泡排序

n个数字 n - 1躺

一趟冒泡排序内部:

第一趟:10个数字待排序,9对比较

第二趟:9个数字待排序,8对比较

第三趟:8个数字待排序,7对比较 ……

第九趟:2个数字待排序,1对比较

#include<stdio.h>
void print(int arr[], int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
void bubble_sort(int arr[], int sz)
{
    int i = 0;
    int j = 0;

for (i = 0; i < sz - 1; i++)//确定冒泡排序的趟数
    {
        int flag = 1;//用于判断比较是否继续
        for (j = 0; j < sz - 1 - i; j++)//确定每一趟两两比较的次数
        {
            if (arr[j] > arr[j + 1])
            {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
                flag = 0;
            }
        }
        if (flag == 1)
        {
            break;
        }
    }
}
int main()
{
    int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    printf("排序前:");
    print(arr, sz);
    //冒泡排序,进行升序排列
    bubble_sort(arr, sz);
    printf("排序后:");
    print(arr, sz);
    return 0;
}

image-20210816152647657

上面我们写的冒泡排序只能排整数,如果遇到浮点型、结构体或者其他类型,那么上面的冒泡排序函数就不能使用了。


2.qsort函数

在C语言的库函数中有一个用于任意类型排序的函数 qsort

qsort-- - 使用快速排序的方法

qsort相关信息:

image-20210816152850758

void qsort(void* base,//待排序目标数组的起始位置
    size_t num,//数组的元素个数
    size_t width,//每个元素占用的字节数
    int(__cdecl * compare)(const void* elem1, const void* elem2)
    //函数指针,排序所使用的比较函数
    //不同类型的数据比较的方法是不一样的,所以需要将比较方法写成函数传递给qsort
   //简化:int(* cmp)(const void* elem1, const void* elem2) 
   //函数指针,指针指向的参数有两个,参数类型都是const void*,函数返回类型是int

void 类型讲解: void是无,空的含义,void 表示的是无类型的指针。 void* 指针可以接收任意类型的地址(void 可以看作指针万能筒,可以接收任意类型) void 类型指针不能进行解引用操作的。

理解:我们知道指针的类型决定了指针解引用操作时访问字节的个数,如果指针类型是void *,无类型指针,那么就无法得知该指针解引用访问多少个字节数。

void* 类型指针不能进行++ / ±整数运算的操作。

理解:我们知道指针类型的意义除了决定指针解引用时访问字节的个数外,还决定了指针 + -整数运算的步长,如果指针类型是void *
无类型指针,那么就无法得知该指针 + -的步长到底是多少个字节。

image-20210816152948787


① qsort排序整型数组

qsort函数中为了排序任意类型的数据,所以参数均给了void *
(1)下面我们来试着用qsort排序一下整型数组,熟悉qsort的使用:

#include<stdio.h>
#include<stdlib.h>
    //为了调用qsort,需要将比较函数传给qsort
    //按照qsort函数参数的类型,写出需要使用的比较函数
    int cmp_int(const void* elem1, const void* elem2)
{

    return *(int*)elem1 - *(int*)elem2;
}
void test1()
{
    int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    int i = 0;
    qsort(arr, sz, sizeof(arr[0]), cmp_int);
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
}
int main()
{
    test1();//排序整型数组并打印
    return 0;
}

image-20210816153150142


② qsort排序浮点型数组

(2)下面再用qsort排序一下浮点型型数组,继续熟悉qsort的使用方式:

#include<stdio.h>
#include<stdlib.h>
int cmp_float(const void* e1, const void* e2)
{
    方法一:判断大小,返回整数
    //if (*(float*)e1 == *(float*)e2)
    //{
    //  return 0;
    //}
    //else if (*(float*)e1 - *(float*)e2 > 0)
    //{
    //  return 1;
    //}
    //else
    //{
    //  return -1;
    //}

    //方法二:强制类型转换成int类型
    return (int)(*(float*)e1 - *(float*)e2);
}
void test2()
{
    float arr2[] = { 4.0, 5.0, 1.0, 2.0, 3.0, 9.0 };
    int sz = sizeof(arr2) / sizeof(arr2[0]);
    int i = 0;
    qsort(arr2, sz, sizeof(arr2[0]), cmp_float);
    for (i = 0; i < sz; i++)
    {
        printf("%lf ", arr2[i]);
    }
}
int main()
{
    test2();//排序浮点型数组并打印
    return 0;
}

image-20210816153225748


③ qsort排序结构体

(3)利用qsort排序结构体数据

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{
    char name[20];
    int age;
};
int cmp_Stu_by_age(const void* e1, const void* e2)
{
    return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int cmp_Stu_by_name(const void* e1, const void* e2)
{
    //字符串比较需要用到字符串比较库函数strcmp
    return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void test3()
{
    struct Stu s[] = { {"zhangsan",20},{"lisi",30},{"wangwu",10} };
    int sz = sizeof(s) / sizeof(s[0]);
    int i = 0;
    qsort(s, sz, sizeof(s[0]), cmp_Stu_by_age);
    printf("按年龄排序:\n");
    for (i = 0; i < sz; i++)
    {
        printf("%s,%d\n", s[i].name, s[i].age);
    }
    qsort(s, sz, sizeof(s[0]), cmp_Stu_by_name);
    printf("按名字排序:\n");
    for (i = 0; i < sz; i++)
    {
        printf("%s,%d\n", s[i].name, s[i].age);
    }
}
int main()
{
    test3();//排序结构体数组并打印
    return 0;
}

image-20210816153301404


④ qsort排序任意类型数组

(4)模拟实现qsort排序任意类型的排序函数-- - Bubble_sort

#include<stdio.h>
#include<string.h>
int cmp_int(const void* elem1, const void* elem2)
{
    return (*(int*)elem1 - *(int*)elem2);
}
void Swap(char* e1, char* e2, int width)
{
    int i = 0;
    //逐个字节交换
    for (i = 0; i < width; i++)
    {
        //方法一:
        //char tmp = *(e1 + i);
        //*(e1 + i) = *(e2 + i);
        //*(e2 + i) = tmp;
        //方法二:
        char tmp = *e1;
        *e1 = *e2;
        *e2 = tmp;
        e1++;
        e2++;
    }
}

void Bubble_sort(void* base, int num, int width, int (*cmp)(void* e1, void* e2))
{
    int i = 0;//size_t就是unsigned int无符号整型的类型重定义,这里可以直接用int类型
    //确定排序的趟数
    for (i = 0; i < num - 1; i++)
    {
        int j = 0;
        //确定每趟排序的对数
        for (j = 0; j < num - 1 - i; j++)
        {
            //比较大小
            if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
            {
                //元素位置交换
                Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
            }
        }
    }
}

//void qsort(void* base,//待排序目标数组的起始位置
//         size_t num,//数组的元素个数
//         size_t width,//每个元素占用的字节数
//         int(__cdecl* compare)(const void* elem1, const void* elem2)//函数指针,
//排序所使用的比较函数
//   //简化:int(* cmp)(const void* elem1, const void* elem2) 
//   //函数指针,指针指向的参数有两个,参数类型都是const void*,函数返回类型是int
//   //不同类型的数据比较的方法是不一样的,所以需要将比较方法写成函数传递给qsort
//           );

void test4()
{
    int arr[] = { 1,3,5,7,9,2,4,6,8,10 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    int i = 0;
    Bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
    for (i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }
}

struct Stu
{
    char name[20];
    int age;
};
int cmp_Stu_by_age(const void* e1, const void* e2)
{
    return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int cmp_Stu_by_name(const void* e1, const void* e2)
{
    //字符串比较需要用到字符串比较库函数strcmp
    return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

void test3()
{
    struct Stu s[] = { {"zhangsan",20},{"lisi",30},{"wangwu",10} };
    int sz = sizeof(s) / sizeof(s[0]);
    int i = 0;
    qsort(s, sz, sizeof(s[0]), cmp_Stu_by_age);
    printf("按年龄排序:\n");
    for (i = 0; i < sz; i++)
    {
        printf("%s,%d\n", s[i].name, s[i].age);
    }
    Bubble_sort(s, sz, sizeof(s[0]), cmp_Stu_by_name);
    printf("按名字排序:\n");
    for (i = 0; i < sz; i++)
    {
        printf("%s,%d\n", s[i].name, s[i].age);
    }
}
int main()
{
    test3();//调用模拟排序函数排序struct Stu类型数据并打印
    test4();//调用模拟排序函数排序int类型数据并打印
    return 0;
}

image-20210816153343618


图片.png