【维生素C语言】第十章 - 指针的进阶(下)(二)

简介: 本章将继续对继续讲解指针的进阶部分,并对指针知识进行一个总结。并且介绍qsort函数的用法以及模拟实现qsort函数。本章学习完毕后C语言指针专题就结束了,配备了相应的练习和讲解,强烈推荐做一做。另外,C语言的指针靠这个专题并不能完全讲完,还有更多指针的用法需要通过书籍、实战进行学习,不断地积累才能学好C语言最具代表性的东西——指针。

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


0x00 函数指针数组的指针定义

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

883c30b75245de010c124a1a15c5d2c9_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


0x01 函数指针数组的例子

💬 ppfArr 就是一个函数指针数组:


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


0x02 指针的总结

🔺 指针:

f20adfcb5c3978de23801438c9260913_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


四、回调函数(call back)


0x00 回调函数的概念

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

88236aed2dcc54c5cc47eb1be9cda43d_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


0x01 回调函数的例子

💬 用刚才的 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;
}

🔑 解析:上面的代码做到了想要做什么计算就做什么计算的目的,这就是函数指针能够做到的事。一个 Calc 函数就可以做很多的功能,给它传递不同的参数,它就可以做不同的事情。

c436b6272676d1cb7db0ffb43978a6e3_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


0x02 无指针类型 void*

void*

0x03 qsort 函数

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

2ef353874bf0dac4ec27435fc68b0782_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

💬 回顾冒泡排序:


【维生素C语言】第四章 - 数组 ( 3 - 0x01 )


#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 函数的四个参数:

c7eeece318a22e38a2561701cc561e00_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


💬 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


❓ 如果我想测试一个结构体数据呢?


💬 那我们就写结构体的cmp函数(升序):


( 需求:结构体内容为 " 姓名 + 年龄 ",使用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;
}

🔑 解析:按照年龄排序则比较年龄的大小,按照名字排序本质上是比较Ascii码的大小。

c14222118e7d86aa0a4bbf4fc3873916_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

❓ 现在是升序,如果我想实现降序呢?


💡 很简单,只需要把 e1 - e2 换为 e2 - e1 即可:


int cmp_int(const void* e1, const void* e2)
{
    // 降序: e2 - e1
    return( *(int*)e2 - *(int*)e1 );
}
int cmp_struct_age(const void* e1, const void* e2)
{
    return( ((struct Stu*)e2)->age - ((struct Stu*)e1)->age );
}
int cmp_struct_name(const void* e1, const void* e2)
{
    return( strcmp(((struct Stu*)e2)->name, ((struct Stu*)e1)->name) );
}

0x04 模拟实现 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;
}


🚩 0 1 2 3 4 5 6 7 8 9

相关文章
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
271 1
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
461 7
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
11月前
|
存储 人工智能 Java
一文轻松拿捏C语言的指针的基础使用
本文介绍了C语言中的指针概念,包括直接访问和间接访问内存的方式、指针变量的定义与使用、取址运算符`&`和取值运算符`*`的应用,帮助读者深入理解指针这一C语言的核心概念。君志所向,一往无前!
251 0
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
1473 9
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
1334 13
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
437 7
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
476 11
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
821 3