十.指针进阶(对指针的深度理解)

news2024/12/30 3:34:02

目录

一. 字符指针

1.字符指针的定义

2.字符指针的用法

3.字符指针练习

二. 数组指针

1.指针数组的定义

2.指针数组的用法

三. 指针数组

1.数组指针的定义

2.数组名和&数组名的区别

3.数组指针的用法

4.练习

四. 数组传参和指针传参

1.一维数组传参

2.二维数组传参

3.一级指针传参

4.二级指针传参

 五. 函数指针

1.函数指针的定义

2.取函数指针的地址

3.函数指针的用法

4.练习

六. 函数指针数组

1.函数指针数组的定义

2.函数指针数组的用法

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

1.指向函数指针数组的指针的定义

2.指针总结

八. 回调函数

1.回调函数的概念

2.回调函数的例子

3.qsort函数

4.模拟实现冒泡排序版qsort函数


前言:

指针的主题,我们在初级阶段的《六.初阶指针_殿下p的博客-CSDN博客》章节已经接触过了,我们知道了指针的概念:

1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4. 指针的运算。

这个章节,我们继续探讨指针的高级主题。


一. 字符指针

1.字符指针的定义

定义:字符指针,常量字符串,存储时仅存储一份(为了节约内存)

char *pa="string";

2.字符指针的用法

用法:

int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'w';
    
    return 0;
}

 对于指向字符串的字符指针:

int main()
{
    char* pstr = "hello world";
    printf("%s\n", pstr);
    
    return 0;
}

//打印结果为:hello world

上面代码 char* pstr = " hello world "  特别容易让人以为是把 hello world 放在字符指针 pstr 里了,但是本质上是把字符串 hello world 首字符的地址放到了 pstr 中。

3.字符指针练习

下面代码将输出什么结果呢?

int main()
{
    char str1[] = "abcdef";
    char str2[] = "abcdef";
    const char* str3 = "abcdef";
    const char* str4 = "abcdef";
 
    if (str1 == str2)
        printf("str1 == str2\n");
    else
        printf("str1 != str2\n");
 
    if (str3 == str4)
        printf("str3 == str4\n");
    else
        printf("str3 != str4\n");
 
    return 0;
}

运行结果:

这是因为:

(1)str1和str2是数组,在内存中开辟两块内存空间,这两块内存空间的起始地址不相同,这两个值自然不一样。

(2)“abcdef”是常量字符串,本身不可以被修改,在内存中这个常量字符串只开辟一块内存空间,str3和str4是两个字符指针,这两个指针都指向该字符串的首字符地址,所以str3和str4相等。

 

二. 数组指针

1.指针数组的定义

定义:指针数组是数组,数组中存放的是指针(地址)

int arr1[10];    //整型数组
char arr2[5];    //字符数组
int *parr1[10];  //存放整形指针的数组--指针数组
char *parr2[5];  //存放字符指针的数组--指针数组

 注:[] 优先级高于*,会先与 p 结合成为一个数组,再由 int* 说明这是一个整型指针数组。

2.指针数组的用法

#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* p[] = { arr1, arr2, arr3 }; // 首元素地址
 
    int i = 0;
    for(i=0; i<3; i++) {
        int j = 0;
        for(j=0; j<5; j++) {
            printf("%d ", *(p[i] + j)); // p[i]表示遍历指针数组内的每一个指针
                                        //*(p[i]+j)则表示每一个指针向后移动j个元素所指向的元素
                                        // == p[i][j] 
        }
        printf("\n");
    }
    
    return 0;
}

 

 如下图所示:

三. 指针数组

1.数组指针的定义

定义:数组指针是指针,是指向数组的指针。

整形指针 - 是指向整型的指针

字符指针 - 是指向字符的指针

数组指针 - 是指向数组的指针

int arr[n];
int (*p)[n]=&arr;  //数组指针

前面提到过,[ ]的优先级高于*,所以加上括号,p先和*结合,说明p是一个指针变量,然后指向的是一个大小为n个整型的数组,所以p是一个指针,指向一个数组,叫数组指针。

2.数组名和&数组名的区别

先观察如下代码:

int main()
{
    int arr[10] = {0};
 
    printf("%p\n", arr);
    printf("%p\n", &arr);
 
    return 0;
}

 

 发现它们的地址是一样的,但其实:

 arr和&arr的值一样,但含义却不一样:

int main()
{
    int arr[10] = { 0 };
 
    int* p1 = arr;
    int(*p2)[10] = &arr;
 
    printf("%p\n", p1);
    printf("%p\n", p1 + 1);
 
    printf("%p\n", p2);
    printf("%p\n", p2 + 1);
 
    return 0;
}

 

我们发现arr+1跳过一个整形,而&arr+1跳过一个数组。

这是因为,arr表示数组首元素的地址,&arr表示整个数组的地址。

总结:

数组名是数组首元素的地址,但是有 2 个 例外:

①  sizeof ( 数组名 )  - 数组名表示整个数组,计算的是整个数组的大小,单位是字节。

②  &数组名 - 数组名表示整个数组,取出的是整个数组的地址。

3.数组指针的用法

二维数组以上常使用数组指针:

void print1 (int arr[3][5], int row, int col)
{
    int i = 0;
    int j = 0;
    for(i=0; i<row; i++) {
        for(j=0; j<col; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}
 
void print2 (int(*p)[5], int row, int col)
{
    int i = 0;
    int j = 0;
    for(i=0; i<row; i++) {
        for(j=0; j<col; j++) {
            printf("%d ", *(*(p + i) + j));
            // printf("%d ", (*(p + i))[j]);
            // printf("%d ", *(p[i] + j));
            // 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}};
    // print1(arr, 3, 5);
    print2(arr, 3, 5); // arr数组名,表示数组首元素的地址
                       
    return 0;
}

4.练习

分析下以下代码的含义:

int arr[5];                                                   
int* parr1[10];                                                             
int (*parr2)[10];                                                        
int (*parr3[10])[5];

解析:

//arr是一个有5个元素的整型数组
int arr[5];             

//parr1是一个指针数组,数组有10个元素,每个元素都是int*的指针                                      
int* parr1[10];                                           

//parr2是一个数组指针,该指针指向一个数组,数组有10个元素,每个元素都是int型                  
int (*parr2)[10];                                                       

//parr3是一个数组指针数组,该数组存放10个数组指针,
//每个数组指针能够指向一个数组,数组有5个元素,每个元素的类型为int型 
int (*parr3[10])[5];


四. 数组传参和指针传参

1.一维数组传参

判断下面函数的形参是否合理:

void test(int arr[]) //ok?
{}
void test(int arr[10]) //ok?
{}
void test(int *arr) //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);
}

 答:以上都合理

2.二维数组传参

判断以下函数的参数是否合理:

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

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

int main()
{
    int arr[3][5] = {0};
    test(arr); // 二维数组传参
    
    return 0;
} 

 答:只有第一,第二和第七个合理,其他都不行,理由如下:

//直接数组传参
void test(int arr[3][5])//可以 
{}

//数组传参,行可以省略
void test(int arr[][5]) //可以
{}

//数组传参,列不可以省略
void test(int arr[3][]) //不可以
{}

//同上
void test(int arr[][]) //不可以
{}

//数组名表示数组首元素的地址,二维数组的首元素为第一行一维数组的地址,
//需要一个指向数组的数组指针来接受,一个一阶指针接受不下
void test(int* arr) //不可以
{}

//参数部分为指针数组,不是指针,无法接受二维数组的首元素地址
void test(int* arr[5]) //不可以
{}

//数组指针,该指针指向的数组有5个元素,可以接受二位数组的首地址
void test(int(*arr)[5]) //可以
{}

//二维数组名表示第一行数组的地址,二级指针容纳不下
void test(int** arr) //不可以
{}
  

int main()
{
    int arr[3][5] = {0};
    test(arr); // 二维数组传参
    
    return 0;
} 

3.一级指针传参

例:

void print(int* ptr, int sz) // 一级指针传参,用一级指针接收
{
    int i = 0;
    for(i=0; i<sz; i++) {
        printf("%d ", *(ptr + i));
    }
}
 
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)
{}

int main()
{
    int a = 10;
    int* pa = &a;

    test1(&a); 
    test1(pa); 
 
    return 0;
}

4.二级指针传参

例:

void test(int** ptr)
{
    printf("num = %d\n", **ptr);
}
 
int main()
{
    int n = 10;
    int* p = &n;
    int** pp = &p;
    

    test(pp);
    test(&p); // 取p指针的地址,依然是个二级指针
 
    return 0;
}

还是那个问题,当函数的参数为二级指针时,可以接收什么样的参数呢?

如下所示:

void test(int **p) // 二级指针
{
    ;
}
 
int main()
{
    int *ptr;
    int** pp = &ptr;
    test(&ptr); // 传一级指针变量的地址 
    test(pp); // 传二级指针变量 
    
   
    int* arr[10]; //指针数组
    test(arr); // 传存放一级指针的数组,因为arr是首元素地址,元素类型为int*  
 
    return 0;
}

 
五. 函数指针

1.函数指针的定义

定义:指向函数的指针,存放函数地址的指针

int Add(int x, int y)
{
    return x + y;
}
 
int main()
{
    int (*pf)(int, int) = &Add; //pf是一个函数指针
 
    return 0;
}

int ( * pf ) (int ,int ) = & Add;

解释:*先于pf结合,表示pf是一个指针,然后pf指向一个函数,括号内表示该函数有两个参数,参数类型都为int ,对于函数来说还有返回类型,最前面的int表示函数的返回类型

2.取函数指针的地址

函数也是有地址的,取函数地址可以通过 &函数名 或者 函数名 实现。

但是,要注意:

  •  函数名  ==  &函数名  (两者的含义是一样的,都表示函数的地址)
  •  数组名  !=  &数组名

如下所示:

int Add(int x, int y)
{
    return x + y;
}
 
int main()
{
    // 函数指针 - 存放函数地址的指针
    // &函数名  - 取到的就是函数的地址
    printf("%p\n", &Add);
    printf("%p\n", Add);
 
    return 0;
}

 地址是一样的:

 

 

3.函数指针的用法

例:

int Add(int x, int y)
{
    return x + y;
}
 
int main()
{
    int (*pf)(int, int) = &Add; //创建函数指针,指向Add函数
    
    int ret = (*pf)(3, 5); // 对 pf 进行解引用操作,找到它所指向的函数,然后对其传参
   
    printf("%d\n", ret);
 
    return 0;
}

那么能不能把(*)pf(3,5)写成*pf(3,5)呢?

答案是不可以的,因为这么写会导致*对函数返回值进行解引用,所以星号一定要用括号括起来。

当然也可以选择不写*,因为前文提到过,函数名和&函数名都表示函数的地址

int Add(int x, int y)
{
    return x + y;
}
 
int main()
{
    int (*pf)(int, int) = &Add;
 
    // int ret = Add(3, 5);
    int ret = pf(3, 5);
 
    printf("%d\n", ret);
 
    return 0;
}

 结果是一样的,说明(*pf)前的*加不加都可以。

4.练习

(1)例一:

(*(void (*)())0)();

 解析:这段代码的作用其实是调用 0 地址处的函数,该函数无参,返回类型是 void

 如下图所示:

 (2)例二:

void (*signal(int, void(*)(int)))(int);

 解析:这段代码是对函数的声明

如下图所示:

1.signal先与()结合,说明signal是函数

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

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

上面的函数声明看上去过于冗杂,一眼让人难以察觉这段代码的真正含义,我们可以做如下简化:

int main()
{
    void (* signal(int, void(*)(int)) )(int);
 
    typedef void(*pfun_t)(int); // 对void(*)(int)的函数指针类型重命名为pfun_t
 
    pfun_t signal(int, pfun_t); // 和上面的写法完全等价
 
    return 0;
}

 用typedef对重复出现的函数指针进行重命名,这样该函数声明就一目了然了。


六. 函数指针数组

1.函数指针数组的定义

定义:如果要把函数的地址存到一个数组中,那这个数组就叫函数指针数组。

int Add(int x, int y) {
    return x + y;
}
 
int Sub(int x, int y) {
    return x - y;
}
 
int main()
{
    int (*pf)(int, int) = Add;
    int (*pf2)(int, int) = Sub;
 
    int (*pfArr[2])(int, int) = {Add, Sub}; //函数指针数组,数组元素为函数的地址
 
    return 0;
}

2.函数指针数组的用法

引例:实现一个计算器,可以进行简单的加减乘除运算。

代码1:

include <stdio.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;
}
 
int main()
{
    int input = 0;
    do {
        menu();
        int x = 0;
        int y = 0;
        int ret = 0;
        printf("请选择:> ");
        scanf("%d", &input);
        switch(input) {
            case 1:
                printf("请输入2个操作数:> ");
                scanf("%d %d", &x, &y);
                ret = Add(x, y);
                printf("ret = %d\n", ret);
                break;
            case 2:
                printf("请输入2个操作数:> ");
                scanf("%d %d", &x, &y);
                ret = Div(x, y);
                printf("ret = %d\n", ret);
                break;
            case 3:
                printf("请输入2个操作数:> ");
                scanf("%d %d", &x, &y);
                ret = Mul(x, y);
                printf("ret = %d\n", ret);
                break;
            case 4:
                printf("请输入2个操作数:> ");
                scanf("%d %d", &x, &y);
                ret = Div(x, y);
                printf("ret = %d\n", ret);
                break;
            case 0:
                printf("退出程序\n");
                break;
            default:
                printf("重新选择\n");
                break;
        }
    } while(input);
    
    return 0;
}

 运行结果:

当前代码看着主要功能都实现了,但是还有很多可以优化的地方:

 

  • 当前代码冗余,存在大量重复出现的语句。
  • 添加计算器的功能(比如 a & b,a^b.....)时每加一个功能都要写一段case,每写一段case都会使代码显得很长,很没技术含量,能否更方便地增加?

这时候使用函数指针数组就会方便很多:

#include <stdio.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;
}
 
int main()
{
    int input = 0;
    do {
        menu();
 
        // pfArr 就是函数指针数组
        int (*pfArr[5])(int, int) = {NULL, Add, Sub, Mul, Div};
        int x = 0;
        int y = 0;
        int ret = 0;
        printf("请选择:> ");
        scanf("%d", &input);
 
        if(input >= 1 && input <= 4) {
            printf("请输入2个操作数:> ");
            scanf("%d %d", &x, &y);
            ret = (pfArr[input])(x, y);
            printf("ret = %d\n", ret);  
        }
        else if(input == 0) {
            printf("退出程序\n");
            break;
        } else {
            printf("选择错误\n");
        }
 
    } while(input);
    
    return 0;
}

这就是函数指针数组的应用。接收一个下标,通过下标找到数组里的某个元素,这个元素如果恰好是一个函数的地址,就会去调用那个函数。它做到了一个 "跳板" 的作用,所以我们通常称这种数组叫做 转移表 。


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

1.指向函数指针数组的指针的定义

定义:指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素是函数指针。

int Add(int x, int y) {
    return x + y;
}
 
int main()
{
    int arr[10] = {0};
    int (*p)[10] = &arr; // 取出数组的地址
 
    int (*pfArr[4])(int, int); // pfArr是一个数组 - 函数指针的数组

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

2.指针总结

void add(int,int); //函数

int arr[10];  //数组

int *prr[10];  //指针数组

int (*pa)[10]; //数组指针

int (*padd)(int,int)=add;  //函数指针

int (*parr[10])(int,int);  //函数指针数组

int (*(*pparr)[10])(int,int)=&parr;  //指向函数指针的数组


八. 回调函数

1.回调函数的概念

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

2.回调函数的例子

用上面switch版本的计算器为例:

#include <stdio.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 (*pf)(int, int))
{
    int x = 0;
    int y = 0;
    printf("请输入2个操作数:>");
    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");
                break;
            default:
                printf("选择错误\n");
                break;
        }
    } while(input);
 
    return 0;
}

 解析:

3.qsort函数

定义:qsort 函数是C语言编译器函数库自带的排序函数( 需引入头文件 stdlib.h )

#include <stdlib.h>

void qsort(
    void* base,   //待排序数组的首元素
    size_t num,   //待排序数组的元素个数
    size_t size,  //待排序数组的每个元素的大小,单位是字节
    int (*compar)(const void*, const void*)  //可以实现 比较待排序数据大小的 函数
    );

 让我们回顾下冒泡排序:

#include <stdio.h>
 
void bubble_sort (int arr[], int sz)
{
    int i = 0;
    // 确认趟数
    for (i = 0; i < sz-1; i++) {
        // 一趟冒泡排序
        int j = 0;
        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;
            }
        }
    }
}
 
void print_arr(int arr[], int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
 
int main()
{
    int arr[10] = {9,8,7,6,5,4,3,2,1,0};
    int sz = sizeof(arr) / sizeof(arr[0]);
 
    print_arr(arr, sz);
    bubble_sort(arr, sz);
    print_arr(arr, sz);
 
    return 0;
}

会发现,这个冒泡排序只能实现整形数据的排序,当我们想要对字符串或者结构体排序时,这个冒泡排序就显得有些寒酸了,而qsort函数可以帮助我们实现任意数据类型的排序:

qsort 整型数据排序(升序):

#include <stdio.h>
#include <stdlib.h>
 
/*
void qsort (
    void* base,
    size_t num,
    size_t size,
    int (*cmp_int)(const void* e1, const void* e2)
    );
*/
 
int cmp_int(const void* e1, const void* e2)
{
    // 升序: e1 - e2
    return *(int*)e1 - *(int*)e2;
}
 
 
void print_arr(int arr[], int sz)
{
    int i = 0;
    for (i = 0; i < sz; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
 
void int_sort()
{
    int arr[] = {9,8,7,6,5,4,3,2,1,0};
    int sz = sizeof(arr) / sizeof(arr[0]);
    // 排序(分别填上四个参数)
    qsort(arr, sz, sizeof(arr[0]), cmp_int);
 
    // 打印
    print_arr(arr, sz);
}
 
int main()
{
    int_sort();
 
    return 0;
}

运行结果为:0 1 2 3 4 5 6 7 8 9

qsort 对结构体排序:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
 
struct Stu
{
    char name[20];
    int age;
};
 
/*
void qsort (
    void* base,
    size_t num,
    size_t size,
    int (*cmp_int)(const void* e1, const void* e2)
    );
*/
 
//按照年龄来排序
int cmp_struct_age(const void* e1, const void* e2)
{
    return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
 
//按照名字,也就是字符串来排序
int cmp_struct_name(const void* e1, const void* e2)
{
    return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
 
void struct_sort()
{
    // 使用qsort函数排序结构体数据
    struct Stu s[3] = { 
        {"Ashe", 39},
        {"Hanzo", 38},
        {"Ana", 60}
    };
    int sz = sizeof(s) / sizeof(s[0]);
    // 按照年龄排序
    qsort(s, sz, sizeof(s[0]), cmp_struct_age);
    // 按照名字来排序
    qsort(s, sz, sizeof(s[0]), cmp_struct_name);
}
 
int main()
{
    struct_sort();
 
    return 0;
}

运行结果:

按照年龄排序:

按照姓名(字符串)排序:

4.模拟实现冒泡排序版qsort函数

模拟qsort实现一个冒泡排序版本的通用排序算法:

#include <stdio.h>
#include <string.h>
 
struct Stu 
{
    char name[20];
    char age;
};
 
// 模仿qsort实现一个冒泡排序的通用算法
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_q (
    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++) {
            // 两个元素比较   arr[i] arr[j+i]
            if(cmp( (char*)base+j*width, (char*)base+(j+1)*width ) > 0) {
                //交换
                Swap((char*)base+j*width, (char*)base+(j+1)*width, width);     
            }
        }
    }
}
 
int cmp_struct_age(const void* e1, const void* e2) {
    return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int cmp_struct_name(const void* e1, const void* e2) {
    return strcmp( ((struct Stu*)e1)->name, ((struct Stu*)e2)->name );
}
void struct_sort()
{
    // 使用qsort排序结构体数据
    struct Stu s[] = {"Ashe", 39, "Hanzo", 38, "Ana", 60};
    int sz = sizeof(s) / sizeof(s[0]);
    // 按照年龄排序
    bubble_sort_q(s, sz, sizeof(s[0]), cmp_struct_age);
    // 按照名字排序
    bubble_sort_q(s, sz, sizeof(s[0]), cmp_struct_name);
}
 
void print_arr(int arr[], int sz) 
{
    int i = 0;
    for(i=0; i<sz; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}
 
int cmp_int(const void* e1, const void* e2) {
    // 升序: e1 - e2
    return *(int*)e1 - *(int*)e2;
}
void int_sort()
{
    int arr[] = {9,8,7,6,5,4,3,2,1,0};
    int sz = sizeof(arr) / sizeof(arr[0]);
    // 排序
    bubble_sort_q(arr, sz, sizeof(arr[0]), cmp_int);
    // 打印
    print_arr(arr, sz);
}
 
 
int main()
{
    int_sort();
    // struct_sort();
 
    return 0;
}

 


本篇到此结束,码文不易,还请多多支持哦!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/144187.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

十、MyBatisX插件

文章目录十、MyBatisX插件1 安装MyBatisX插件2 MybatisX代码速成3 在mapper接口中实现自定义功能【尚硅谷】MyBatisPlus教程-讲师&#xff1a;杨博超 失败&#xff0c;是正因你在距成功一步之遥的时候停住了脚步。 十、MyBatisX插件 MyBatis-Plus为我们提供了强大的mapper和ser…

Jdbc配置文件连接mysql8.0——通过拼接字符串进行批量增删改操作

目录 一、基类BaseDao 二、对dog表的批量增删改操作 (一)Dog类 (二)DogDao接口 (三)DogDaoImpl实现类 1.批量新增 2.批量删除 3.批量修改 (四)Test测试 1.新增 2.删除 3.修改 三、对master表进行批量增删改 (一)Master类 (二)MasterDao接口 (三)MasterDaoImpl实…

RK3588平台开发系列讲解(内核调试篇)oops分析

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、oops简介二、oops分析2.1、实验代码2.2、oops信息2.3、oops分析沉淀、分享、成长,让自己和他人都能有所收获!😄 📢当系统内核发生kernel panic的时候,系统会打印出oops信息,本篇主要介绍如何根据oops定位问…

2022/1/6总结

今天学习了KMP算法。 KMP算法 这是一个字符串查找的算法&#xff0c;我们之前学习的字符串查找都是暴力穷举&#xff0c;然而这个效率太低&#xff0c;于是有三位大佬发明了线性的KMP算法。 算法说难不难&#xff0c;说简单也不简单。 算法的核心思想是找到最长的相等的前…

Struts2框架之Action配置

Struts2框架之Action配置Action配置1、访问Action的三种方式1.1、method属性访问1.2、Action动态方法调用1.3、通配符调用2、配置默认的ActionAction配置 Action控制器在Struts2框架中至关重要&#xff0c;主要作用如下&#xff1a; 封装工作单元数据转移的场所返回结果字符串…

(黑马C++)L07 多态

一、多态的基本概念 多态是面向对象程序设计语言中除数据抽象和继承之外的第三个基本特征。 多态&#xff1a;父类的引用或者指针指向子类对象 C支持编译时多态&#xff08;静态多态&#xff09;和运行时多态&#xff08;动态多态&#xff09;&#xff0c;运算符重载和函数重…

纷享销客CRM让科顺营销人更容易呼唤到“炮火”

希望让听得见“炮声”的人&#xff0c;更容易呼唤到“炮火”。对于在一线做营销的人而言&#xff0c;他们就是听到“炮声”的人。让一线的人员听得到“炮声”也就是销售线索、商机&#xff0c;能呼唤到“炮火”也就是呼唤到他们需要的资源。这恐怕是所有营销人都希望达到的境界…

ubuntu Ad-Hoc组网通信

目录 WIFI通信的多种组网方式 1、AP模式 2、Ad-hoc模式 ubuntu18配置ad-hoc模式 WIFI通信的多种组网方式 1、AP模式 最常用的模式&#xff0c;需要一个节点&#xff08;一般是路由器&#xff09;作为AP&#xff0c;其他节点连接到这个AP产生的wifi网络。通信拓扑是星形&a…

11_6、Java集合之Map接口的使用

一、引入Map与Collection并列存在。用于保存具有映射关系的数据:key-value &#xff08;双列集合框架&#xff09;&#xff0c;Map 中的 key 和 value 都可以是任何引用类型的数据 。Map 中的 key 用Set来存放&#xff0c;不允许重复&#xff0c;即同一个 Map 对象所对应 的类&…

在rhel6系统部署iscsi远程存储

文章目录一 需求二 环境准备三 服务端配置3.1 添加硬盘3.2 安装软件3.3 编写配置文件3.4 启动服务3.5 检查配置信息四 客户端配置4.1 安装软件包4.2 启动服务4.3 发现目标4.4 登陆目标4.5 实现开机自动挂载五 对部署进行测试一 需求 1&#xff09;首先在服务端添加一块10G的硬…

实验二十二 配置访问控制列表AGL

实验二十二 配置访问控制列表AGL一、 ACL基础概念 1、访问控制列表根据源地址、目标地址、源端口或目标端口等协议信息对数据包进行过滤&#xff0c; 从而达到访问控制的目的 。可以在路由器、三层交换机等设备上使用 &#xff0c;目前部分新二层交换 机也支持ACL。 2、ACL由编…

十、k8s DashBoard

文章目录1 部署Dashboard2 使用DashBoard之前在kubernetes中完成的所有操作都是通过命令行工具kubectl完成的。其实&#xff0c;为了提供更丰富的用户体验&#xff0c;kubernetes还开发了一个基于web的用户界面&#xff08;Dashboard&#xff09;。用户可以使用Dashboard部署容…

超级浏览器的技术原理,超级浏览器的浏览器指纹是什么?

浏览器指纹是超级浏览器的识别信息&#xff0c;网站可以通过这些信息来识别用户&#xff0c;判断用户的唯一性。常见的浏览器指纹有IP地址、浏览器所在地区、时区&#xff1b;用户代理&#xff08;User Agent&#xff09;相关的操作系统及版本、CPU 类型、浏览器及版本、浏览器…

吴恩达《机器学习》——PCA降维

PCA降维1. 主成分分析1.1 数据降维动机1.2 PCA降维目标问题分析2. PCA数学原理分析2.1 求协方差矩阵的碎碎念2.2 PCA实现方法3. Python实现3.1 进行人脸数据压缩数据集、源文件可以在Github项目中获得 链接: https://github.com/Raymond-Yang-2001/AndrewNg-Machine-Learing-Ho…

一些实用的办公工具分享给你

ABBYY FineReader 这是一个可以转换PDF格式的图片文字识别软件&#xff0c;下载之后可以免费试用七天&#xff0c;或者选择去它的网站上传PDF进行识别转换&#xff0c;一天最多可以转换10次&#xff0c;且一次只能转换3个页面。 【操作方法】 打开软件&#xff0c;点击“图像…

(day3)自学Java——面向对象

非原创&#xff0c;为方便自己后期复习 目录 1.类和对象 2.封装 3.就近原则和this关键字 4.构造方法 5.标准的javabean类 6.三种情况的对象内存图 7.基本数据类型和引用数据类型 8.this的内存原理 9.面向对象综合训练 (1)文字版格斗游戏 (2)两个对象数组练习 (3)对…

产品上新|语音识别+主题抽取,Magic Data多人会议数据集助您打造领先智能会议系统

2020年以来&#xff0c;新冠加快了线下向线上搬迁的速度&#xff0c;使得线上办公、在线教育、远程会议得到飞速普及和发展。艾媒咨询数据显示&#xff0c;2021年中国视频会议行业市场规模达148.2亿元。各类视频会议产品价格较低、操作便捷高效&#xff0c;普及率越来越高&…

vsftp开启登录,上传,下载,删除等操作审计日志

vsftp开启登录&#xff0c;上传&#xff0c;下载&#xff0c;删除等操作审计日志 背景 今天业务告知说有人把前天下午和昨天一天的ftp上面的附件被人删除了&#xff0c;首先我是非常的惊讶&#xff0c;居然会发生这种事&#xff0c;但是好在这个ftp不是我们负责的&#xff0c;…

驱动之设备模型

1. 起源与新方案 1.1 起源 仅devfs&#xff0c;导致开发不方便以及一些功能难以支持 热插拔不支持一些针对所有设备的同意操作&#xff08;如电源管理&#xff09;不能自动mknod用户查看不了设备信息设备信息硬编码&#xff0c;导致驱动代码通用性差&#xff0c;即没有分离设…

终章:学习路线

说明 该文章来源于徒弟lu2ker转载至此处&#xff0c;更多文章可参考&#xff1a;https://github.com/lu2ker/ 文章目录说明一些废话成果路线第一阶段要点第二阶段要点第三阶段要点第四阶段要点最后一些废话 截至这篇文章前已经有150star了&#xff0c;虽然比不上大佬们K级的量…