<C语言> 指针(下)

1.字符指针

字符指针(Character Pointer)char*是指向字符数据的指针变量。

使用实例:

int main() {
    char ch = 'w';    // 声明一个字符变量ch并赋值为 'w'
    char *pc = &ch;   // 声明一个字符指针pc并将其指向ch的地址
    *pc = 'w';        // 通过指针pc修改ch的值为 'w'
    
    char arr[] = "abcdef";  // 声明一个字符数组arr并初始化为字符串 "abcdef"
    char* p = arr;         // 声明一个字符指针p,将其指向数组arr的首地址
    
    return 0;
}

还有一种使用方式如下:

#include <stdio.h>
int main() {
    const char *pstr = "hello world.";   // 声明一个指向常量字符的指针pstr,并将其指向字符串常量"hello wrold."
    printf("%sn", pstr);  // 输出字符串"hello world."
    *pstr = 'w';  // 尝试修改常量字符串,会导致编译错误,因为常量字符串不可修改

    printf("%cn", *pstr);  // 尝试访问常量字符串的第一个字符,不会发生修改,输出仍为原始字符'h'

    return 0;
}

通过指针修改常量字符串的操作是非法的,会导致编译错误。常量字符串应该被视为只读数据。如果需要修改字符串内容,应该使用字符数组而不是指向常量字符的指针。

代码 const char* pstr = "hello world.";

特别容易让同学以为是把字符串 hello world 放到字符指针 pstr 里了,但是/本质是把字符串 hello world. 首字符的地址放到了pstr中。

下面的结果是什么?

#include <stdio.h>
int main() {
    char str1[] = "hello world.";
    char str2[] = "hello world.";
    const char *str3 = "hello world.";
    const char *str4 = "hello world.";
    if (str1 == str2)
        printf("str1 and str2 are samen");
    else
        printf("str1 and str2 are not samen");

    if (str3 == str4)
        printf("str3 and str4 are samen");
    else
        printf("str3 and str4 are not samen");

    return 0;
}
//str1 and str2 are not same
//str3 and str4 are same
  • str1str2 是字符数组,它们包含了相同的字符串 “hello world.”。
  • str3str4 是指向常量字符的指针,它们也指向相同的字符串 “hello world.”。

然后,程序使用条件语句比较了这四个变量的指针值,并输出比较结果:

  • 通过 str1 == str2 比较,它们是两个不同的字符数组,所以输出 “str1 and str2 are not same”。
  • 通过 str3 == str4 比较,它们指向相同的常量字符,所以输出 “str3 and str4 are same”。

尽管 str1str2 的内容相同,但它们是两个不同的数组,因此它们在内存中的地址也不同。而 str3str4 是指向相同字符串常量的指针,它们指向的地址相同。

如果想比较字符串的内容而不是地址,应该使用字符串比较函数 strcmp(),例如 strcmp(str1, str2)

2.指针数组

在C语言中,指针数组是指一个数组,其元素都是指针类型的变量。指针数组可以存储指向不同类型对象的指针。

那么指针数组是指针还是数组?

是数组。是存放指针的数组。

例如:

int main() {
    //整型数组-存放整型的数组
    int arr[10];
    //字符数组-存放字符的数组
    char arr2[5];
    //指针数组-存放指针的数组
    int *arr3[5]; //存放整型指针的数组
    char *arr4[6];//存放字符指针的数组

    return 0;
}

例如:

#include <stdio.h>
int main() {
    int a = 10;
    int b = 20;
    int c = 30;
    int d = 40;
    int e = 50;

    int *arr3[5] = {&a, &b, &c, &d, &e};//存放整型指针的数组
    int i = 0;
    for (i = 0; i < 5; i++) {
        printf("%d ", *(arr3[i]));
    }

    return 0;
}

指针数组使用场景:用一个一维数组模拟二维数组

#include<stdio.h>
int main() {
    //用一维数组模拟一个二维数组
    int arr1[] = {1, 2, 3, 4, 5};
    int arr2[] = {2, 3, 4, 5, 6};
    int arr3[] = {3, 4, 5, 6, 7};
    int arr4[] = {4, 5, 6, 7, 8};

    int *arr[4] = {arr1, arr2, arr3, arr4};
    int i = 0;
    for (i = 0; i < 4; i++) {
        int j = 0;
        for (j = 0; j < 5; j++) {
            printf("%d ", *(*(arr + i) + j));
        }
        printf("n");
    }

    //int i = 0;
    //for (i = 0; i < 4; i++)
    //{
    //	int j = 0;
    //	for (j = 0; j < 5; j++)
    //	{
    //		printf("%d ", arr[i][j]);
    //	}
    //	printf("n");
    //}

    return 0;
}

输出结果:

1 2 3 4 5 
2 3 4 5 6 
3 4 5 6 7 
4 5 6 7 8 

3.数组指针

3.1 数组指针的定义

数组指针是指针?还是数组?

答案是:指针。

我们已经熟悉: 整型指针: int * pint; 能够指向整型数据的指针。

浮点型指针: float * pf; 能够指向浮点型数据的指针。 那数组指针应该是:能够指向数组的指针

下面代码哪个是数组指针?

int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?

解释:

int (*p)[10];

p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。

这里要注意:[]的优先级要高于号的,所以必须加上()来保证p先和结合。

int main(){
    int arr[10] = {1,2,3,4,5};
	int (* pa)[10] = &arr;//取出的是数组的地址存放到pa中,pa是数组指针变量
	char arr[5];
	char (*p1)[5] = &arr;
}

3.2 &数组名VS数组名

对于下面的数组:

int arr[10];

arr&arr 分别是啥?

我们知道arr是数组名,数组名表示数组首元素的地址。

那&arr数组名到底是啥? 我们看一段代码:

#include <stdio.h>
int main() {
    int arr[10] = {0};
    printf("%pn", arr);
    printf("%pn", &arr);
    return 0;
}

运行结果如下:

000000000061FDF0
000000000061FDF0

可见数组名和&数组名打印的地址是一样的。

难道两个是一样的吗?

我们再看一段代码:

#include <stdio.h>
int main() {
    int arr[10] = {0};
    printf("arr = %pn", arr);
    printf("&arr= %pn", &arr);
    printf("arr+1 = %pn", arr + 1);
    printf("&arr+1= %pn", &arr + 1);
    return 0;
}

输出结果如下:

arr = 000000000061FDF0
&arr= 000000000061FDF0
arr+1 = 000000000061FDF4
&arr+1= 000000000061FE18

根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。

实际上: &arr表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)

本例中&arr的类型是: int(*)[10] ,是一种数组指针类型数组的地址+1,跳过整个数组的大小,所以&arr+1相对于&arr的差值是40.

3.3 数组指针的使用

那数组指针是怎么使用的呢?

既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

实例:

#include <stdio.h>
int main() {
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
    int(*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
    //但是我们一般很少这样写代码
    return 0;
}

一个数组指针的使用:

#include <stdio.h>
void print1(int *arr, int sz) {
    int i = 0;
    for (i = 0; i < sz; i++) {
        //*(arr + i) 表示指针arr偏移i个元素后所指向的值。
        printf("%d ", *(arr + i));
    }
    printf("n");
}

void print2(int (*p)[10], int sz) {
    int i = 0;
    for (i = 0; i < sz; i++) {
        //int (*p)[10]是数组指针,(*p)[i] 表示指针p所指向的数组的第i个元素的值。
        printf("%d ", (*p)[i]);   //*p = arr;
    }
    printf("n");
}

int main() {
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int sz = sizeof(arr) / sizeof(arr[0]);

    print1(arr, sz);  //1 2 3 4 5 6 7 8 9 10
    print2(&arr, sz);  //1 2 3 4 5 6 7 8 9 10
    return 0;
}

print1 函数的参数是一个整型指针,可以传递任意大小的整型数组给它进行打印。而 print2 函数的参数是一个指向包含10个整数的数组的指针,因此只能传递大小为10的整型数组给它进行打印。

这两种写法都可以用于打印整型数组的元素,选择使用哪种方式取决于你的需求和所操作的数组的类型和大小。

继续:

void print3(int arr[3][5], int r, int c) {
    int i = 0;
    for (i = 0; i < r; i++) {
        int j = 0;
        for (j = 0; j < c; j++) {
            //arr[i][j] 表示二维数组arr的第i行、第j列的元素的值。
            printf("%d ", arr[i][j]);
        }
        printf("n");
    }
}

void print4(int (*p)[5], int r, int c) {
    //函数接受一个指向包含5个整数的数组指针p
    int i = 0;
    for (i = 0; i < r; i++) {
        int j = 0;
        for (j = 0; j < c; j++) {
            //*(*(p + i) + j) 表示指针p所指向的二维数组的第i行、第j列的元素的值。
            //p为指针 p+i表示一维数组
            printf("%d ", *(*(p + i) + j));
        }
        printf("n");
    }
}

int main() {
    int arr[3][5] = {{1, 2, 3, 4, 5}, {2, 3, 4, 5, 6}, {3, 4, 5, 6, 7}};
    print3(arr, 3, 5);  
    print4(arr, 3, 5);
    return 0;
}

解析 *(*(p + i) + j)

  1. (p + i):首先,p 是一个指向包含5个整数的数组的指针。通过 p + i 运算,指针 p 偏移了 i 个整数大小的字节,即指向了二维数组的第 i 行。
  2. *(p + i): 在上一步中,p 偏移了 i 行,所以 *(p + i) 表示指针 p 所指向的二维数组的第 i 行的首个元素。这是一个指向整数的指针。
  3. (*(p + i) + j): 在上一步的基础上,j 是用于偏移列的索引。*(p + i) + j 表示指针 *(p + i) 所指向的一维数组中,偏移了 j 个整数大小的字节,即指向了二维数组中第 i 行、第 j 列的元素。
  4. *(*(p + i) + j): 最后,*(*(p + i) + j) 使用指针解引用操作符 *,获取指针 (*(p + i) + j) 所指向的整数值。这就是二维数组中第 i 行、第 j 列的元素值。

学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];
  • int arr[5]; 这是一个包含5个整数的数组,名为arr
  • int *parr1[10]; 这是一个包含10个指向整数的指针的数组,名为parr1。它可以存储10个整数的地址。
  • int (*parr2)[10]; 这是一个指向包含10个整数的数组的指针,名为parr2。它指向一个具有10个整数的数组。
  • int (*parr3[10])[5]; 这是一个包含10个指向包含5个整数数组的指针的数组,名为parr3。它可以存储10个指向具有5个整数的数组的指针。

让我们分解int (*parr3[10])[5]并解释其含义:

  1. 从变量名parr3开始,我们知道这是一个数组。
  2. 继续向右,我们看到[10],表示parr3是一个包含10个元素的数组。
  3. 继续向右,我们看到*,表示数组的元素是指针。
  4. 继续向右,我们看到[5],表示指针指向的是包含5个整数的数组。
  5. 最后,我们有int,表示数组中的整数类型。

因此,int (*parr3[10])[5]可以解读为:parr3是一个包含10个元素的数组,每个元素都是指向包含5个整数的数组的指针。

4.数组参数、指针参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

4.1 一维数组传参

#include <stdio.h>
void test(int arr[])//ok?
{}

void test(int arr[10])//ok?
{}

void test(int *arr)//ok?
{}

void test2(int *arr[20])//ok?
{}

void test2(int **arr)//ok?
{}

int main() {
    int arr[10] = {0};
    int *arr2[20] = {0};
    test(arr);
    test2(arr2);
}

上述代码中都OK。arr[10]等价于*arr,[]中的值可以省略。 *arr[20] 等价于**arr

4.2 二维数组传参

void test(int arr[3][5])//ok?
{}

void test(int arr[][])//ok?  err
{}

void test(int arr[][5])//ok?
{}

int main() {
    int arr[3][5] = {0};
    test(arr);
}

总结:

二维数组传参,函数形参的设计只能省略第一个[]的数字。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。这样才方便运算。

void test(int *arr)//ok?
{}

void test(int *arr[5])//ok?  err
{}

void test(int (*arr)[5])//ok?
{}

void test(int **arr)//ok? err
{}

int main() {
    int arr[3][5] = {0};
    test(arr);
}

函数 test(int *arr[5]) 是错误的,因为它声明了一个包含 5 个指向整数的指针的数组,而不是一个指向整数的指针。函数 test(int **arr) 也是错误的,因为它声明了一个指向指针的指针,而不是一个指向整数或整数数组的指针。在主函数中,参数 int *arr 的正确函数声明应该是 test(int (*arr)[5]),因为它是一个包含 5 个整数的数组的指针 。

4.3 一级指针传参

#include <stdio.h>
void print(int *p, int sz) {
    int i = 0;
    for (i = 0; i < sz; i++) {
        printf("%d ", *(p + i));//1 2 3 4 5 6 7 8 9 10
    }
}
int main() {
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int *p = arr;
    int sz = sizeof(arr) / sizeof(arr[0]);
    //一级指针p,传给函数
    print(p, sz);
    return 0;
}

思考:

当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

比如:

void test1(int *p)
{}
//test1函数能接收什么参数?  int型一维数组,和int型变量的地址
void test2(char* p)
{}
//test2函数能接收什么参数?  char型一维数组,和char型变量的地址

4.4 二级指针传参

#include <stdio.h>
void test(int **ptr) {
    printf("num = %dn", **ptr);
}

int main() {
    int n = 10;
    int *p = &n;
    int **pp = &p;
    test(pp);//num = 10
    test(&p);//num = 10
    return 0;
}

思考:

当函数的参数为二级指针的时候,可以接收什么参数?

void test(char **p) {
}

int main() {
    char c = 'b';
    char *pc = &c;
    char **ppc = &pc;
    char *arr[10];
    test(&pc);  //pc是一个一级指针,&pc即二级指针
    test(ppc);  //ppc是一个二级指针
    test(arr);//Ok?  
    return 0;
}

数组名本身也可以被解释为指向数组首元素的指针。对于一个char*类型的数组arr[10]arr可以被解释为指向arr[0]的指针,而arr[0]又是一个char*类型的指针。

因此,当调用test(arr)时,实际上将指向arr[0]的指针传递给了test函数。在test函数内部,参数p的类型是char**,它可以接收指向char*类型的指针。因此,test(arr)是合法的。

5.函数指针

首先看一段代码:

#include <stdio.h>
void test() {
    printf("hehen");
}

int main() {
    printf("%pn", test);
    printf("%pn", &test);
    return 0;
}

输出的结果:

0000000000401550
0000000000401550

输出的是两个地址,这两个地址是 test 函数的地址。

那我们的函数的地址要想保存起来,怎么保存?

下面我们看代码:

void test() {
    printf("hehen");
}

//下面pfun1和pfun2哪个有能力存放test函数的地址?
void (*pfun1)();
void *pfun2();

pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。

什么是函数指针?

函数指针是指向函数的指针变量。它可以用于在程序运行时动态地选择调用不同的函数,或者将函数作为参数传递给其他函数。

函数指针的声明:

return_type (*pointer_name)(parameter_list);

其中,return_type是函数的返回类型,pointer_name是指针变量的名称,parameter_list是函数的参数列表。例如,以下是一个指向返回整数类型、接受两个整数参数的函数指针的声明:

int (*sum_ptr)(int, int);

函数指针的赋值:

函数指针可以通过将函数的名称赋值给指针变量来进行初始化。例如,假设有以下函数定义:

int sum(int a, int b) {
    return a + b;
}

可以将该函数赋值给函数指针:

sum_ptr = sum;

使用函数指针调用函数: 使用函数指针调用函数与直接调用函数的语法相似,只需将指针变量后面加上参数列表即可。例如:

int result = sum_ptr(3, 4);

实例1:

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

int main() {
    //pf就是函数指针变量
    int (*pf)(int x, int y) = Add;  //Add和&Add都是函数的地址,没有区别,类似于数组名和&数组名
    int sum = (*pf)(3, 5);  //等价于int sum = Add(3, 5);
    
    printf("%dn", sum);  //8
    return 0;
}

实例2:

#include <stdio.h>
int test(const char *str, double d) {
}

int main() {
    int (*pt1)(const char *, double) = &test;
    int (*pt2)(const char *str, double d) = &test;

    return 0;
}

阅读两段有趣的代码:

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

(*(void (*)())0)();把0直接转换成一个void (*)()的函数指针,然后去调用0地址处的函数,函数的参数为无参

void (*signal(int , void(*)(int)))(int);是一次函数声明

声明的函数叫:signal

signal函数的第一个参数是int类型的

signal函数的第二个参数是一个函数指针类型,该函数指针指向的函数参数是int,返回类型是void

signal函数的返回类型也是一个函数指针类型,该函数指针指向的函数参数是int,返回类型是void

代码2太复杂,如何简化:

typedef void(*pfun_t)(int);  //pfun_t 是一个函数指针,参数是int,返回类型是void
pfun_t signal(int, pfun_t);

6.函数指针数组

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

比如:

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

函数指针数组中的每个元素都是一个函数指针。函数指针是指向函数的指针变量,可以用来调用该函数。

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

int (*parr1[10])();   //函数指针数组,指向一个无参函数,返回值为int
int *parr2[10]();	 
int (*)() parr3[10];  

答案是: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;
}


void menu() {
    printf("***************************n");
    printf("***** 1.add    2. sub  ****n");
    printf("***** 3.mul    4. div  ****n");
    printf("***** 0.exit           ****n");
    printf("***************************n");
}

int main() {
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;
    do {
        menu();
        printf("请选择:>");
        scanf("%d", &input);

        switch (input) {
            case 1:
                printf("请输入2个操作数:>");
                scanf("%d %d", &x, &y);
                ret = Add(x, y);
                printf("%dn", ret);
                break;
            case 2:
                printf("请输入2个操作数:>");
                scanf("%d %d", &x, &y);
                ret = Sub(x, y);
                printf("%dn", ret);
                break;
            case 3:
                printf("请输入2个操作数:>");
                scanf("%d %d", &x, &y);
                ret = Mul(x, y);
                printf("%dn", ret);
                break;
            case 4:
                printf("请输入2个操作数:>");
                scanf("%d %d", &x, &y);
                ret = Div(x, y);
                printf("%dn", ret);
                break;
            case 0:
                printf("退出计算器n");
                break;
            default:
                printf("选择错误n");
                break;
        }
    } while (input);
}

使用函数指针数组简化代码:

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


void menu() {
    printf("***************************n");
    printf("***** 1.add    2. sub  ****n");
    printf("***** 3.mul    4. div  ****n");
    printf("***** 0.exit           ****n");
    printf("***************************n");
}

int main() {
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;

    //函数指针数组   - 转移表
    int (*pfArr[])(int, int) = {0, Add, Sub, Mul, Div};

    do {
        menu();
        printf("请选择:>");
        scanf("%d", &input);
        if (input == 0) {
            printf("退出计算器n");
            break;
        }

        if (input >= 1 && input <= 4) {
            printf("请输入2个操作数:>");
            scanf("%d %d", &x, &y);
            ret = pfArr[input](x, y);
            printf("%dn", ret);
        } else {
            printf("选择错误n");
        }
    } while (input);
}

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

指向函数指针数组的指针是一个指针,它指向函数指针数组的起始地址。可以使用这个指针来访问函数指针数组中的元素,以及通过函数指针调用相应的函数。

下面是一个简单的示例,展示了如何声明和使用指向函数指针数组的指针:

#include <stdio.h>

void test(const char* str){
	printf("%sn", str);
}

int main(){
 	//函数指针pfun
	void (*pfun)(const char*) = test;
 	//函数指针的数组pfunArr
	void (*pfunArr[5])(const char* str);
 	pfunArr[0] = test;
 	//指向函数指针数组pfunArr的指针ppfunArr
 	void (*(*ppfunArr)[5])(const char*) = &pfunArr;
 	return 0;
}


// 函数指针的声明
typedef void (*FuncPtr)(int);

// 函数定义
void func1(int num) {
    printf("func1: %dn", num);
}

void func2(int num) {
    printf("func2: %dn", num);
}

void func3(int num) {
    printf("func3: %dn", num);
}

int main() {
    // 函数指针数组的定义和初始化
    FuncPtr funcs[] = { func1, func2, func3 };

    // 指向函数指针数组的指针
    FuncPtr (*ptrToFuncArray)[3] = &funcs;

    // 通过指针访问函数指针数组的元素,并调用相应的函数
    (*ptrToFuncArray)[0](10);  // 调用 func1
    (*ptrToFuncArray)[1](20);  // 调用 func2
    (*ptrToFuncArray)[2](30);  // 调用 func3

    return 0;
}

8.回调函数

回调函数是一种函数指针的使用方式,它允许将一个函数作为参数传递给另一个函数,并在需要的时候调用该函数。回调函数提供了一种灵活的机制,使得代码可以根据特定的条件或事件来执行不同的行为。

#include <stdio.h>

// 回调函数类型的定义
typedef void (*CallbackFunc)(int);

// 执行回调函数的函数
void performCallback(CallbackFunc callback, int value) {
    // 调用传递进来的回调函数
    callback(value);
}

// 回调函数1
void callback1(int value) {
    printf("Callback 1: %dn", value);
}

// 回调函数2
void callback2(int value) {
    printf("Callback 2: %dn", value);
}

int main() {
    int data = 10;

    // 使用回调函数1进行操作
    performCallback(callback1, data);

    // 使用回调函数2进行操作
    performCallback(callback2, data);

    return 0;
}

输出结果如下:

Callback 1: 10
Callback 2: 10

我们首先定义了一个回调函数类型 CallbackFunc,它是一个函数指针类型,可以指向一个带有一个 int 参数和 void 返回类型的函数。

然后,我们定义了一个名为 performCallback 的函数,它接受一个回调函数作为参数,并在需要的时候调用该函数,传递给它一个值。

接下来,我们定义了两个回调函数 callback1callback2,它们都符合 CallbackFunc 类型的函数签名。

main 函数中,我们定义了一个整数变量 data,然后使用 performCallback 函数两次,分别传递不同的回调函数和 data 值。这样就实现了在不同情况下执行不同回调函数的功能。

当程序运行时,会依次调用 performCallback 函数,并根据传递的回调函数打印相应的信息。

qsort函数

C语言中qsort函数也是通过函数指针回调函数实现的

#include <stdio.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void *p1, const void *p2) {
    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;
}

用冒泡排序模拟实现下回调函数方式:

#include <stdio.h>

int cmp_int(const void *e1, const void *e2) {
    return (*(int *) e1 - *(int *) e2);
}

//char类型1字节,适用于任何类型
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(void *base, int sz, int width, int (*cmp)(const void *e1, const void *e2)) {
    int i = 0;
    //趟数
    for (i = 0; i < sz - 1; i++) {
        //一趟冒泡排序的过程
        int j = 0;
        for (j = 0; j < sz - 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);
            }
        }
    }
}

int main() {
    int arr[] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 0};
    //char *arr[] = {"aaaa","dddd","cccc","bbbb"};
    int i = 0;
    bubble_sort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), cmp_int);
    for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
        printf("%d ", arr[i]);
    }
    printf("n");
    return 0;
}