指针详解3

运行环境以Dev-C++、Visual Studio 2022、MacOS的命令行和Xcode为主

1.字符指针变量

// 代码1:将字符变量的地址赋值给字符指针变量
#include <stdio.h>

int main(int argc, const char * argv[]) {
    char ch = 'w';
    char *pc = &ch;
    *pc = 'b';
    printf("%c\n", *pc);

    return 0;
}

// 代码2:将字符串赋值给字符指针变量,实际上字符串"abcdef"并没有存放在字符指针pstr中
// 本质上是将字符串"abcdef"首字符'a'的地址存到了指针变量pstr中
#include <stdio.h>

int main(int argc, const char * argv[]) {
    const char *pstr = "abcdef";
    printf("%s\n", pstr);
    return 0;
}

// 代码3:对比分析字符串常量和字符串数组。str3和str4指向的是同一个常量字符串,C语言会把常量字符串存储到单独的一个内存区域
// 当几个指针指向同一个字符串时,它们会指向同一块内存。但用相同的字符串常量初始化不同的数组时,会开辟出不同的内存块
#include <stdio.h>

int main(int argc, const char * argv[]) {
    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 same\n");
    }
    else {
        printf("str1 and str2 are not same\n");
    }
        
    if (str3 == str4) {
        printf("str3 and str4 are same\n");
    }
    else {
        printf("str3 and str4 are not same\n");
    }
    
    return 0;
}

image

2.数组指针变量

  • 2.1 定义和指向原理

    • 整型指针变量存放整型变量的地址,指向整型数据,如int *p_int;;浮点型指针变量存放浮点型变量的地址,指向浮点型数据,如float *pf;

    • 数组指针变量存放数组的地址,是能够指向数组的指针变量,如int (*p_arr)[10];

      • p_arr先和*结合,说明p_arr是一个指针,指针指向的是一个大小为 10 个整型的数组

      • p_arr是一个指针,指向一个数组,称为数组指针

      • []的优先级高于*,所以必须加上()来保证p_arr先和*结合

  • 2.2 数组指针的初始化

    • 前面的知识中详细分析了数组名、数组首元素的地址、数组的地址间的区别和联系。&数组名便获得了数组的地址。若要存放该地址,要用到数组指针变量
    int arr[10] = {0};
    int (*p)[10] = &arr;
    // 调试后查看&arr和p的类型完全一致
    
    int        (*p)        [10]        =        &arr;
     |           |           |
     |           |           |
     |           |          p指向数组的元素个数
     |          p是数组指针变量名
    p指向的数组元素的类型
    

    image

3.二维数组传参的本质

  • 3.1 数组形式的形参

    • 这是在第 6 章中二维数组作为形参的主要形式

    • 实参是二维数组,形参也写成二维数组的形式

    #include <stdio.h>
    
    void print_arr(int arr[3][5], int r, int c) {    // int arr[][5] 也可以
        int i = 0;
        for (i = 0; i < r; i++) {
            int j = 0;
            for (j = 0; j < c; j++) {
                printf("%d ", arr[i][j]);
            }
            printf("\n");
        }
    }
    
    int main(int argc, const char * argv[]) {
        int arr[3][5] = {{1, 2, 3, 4, 5}, {2, 3, 4, 5, 6}, {3, 4, 5, 6, 7}};
        print_arr(arr, 3, 5);
    
        return 0;
    }
    
  • 3.2 数组指针形式的形参

    • 二维数组是一维数组的集合,是由一维数组组成的数组,它的首元素就是第一行,也是个一维数组

    • 根据数组名是数组首元素地址这个规则,二维数组的数组名表示第一行,是一维数组的地址

    • 第一行的一维数组的类型为int [5],所以第一行的地址类型为数组指针类型int (*)[5]

    • 二维数组传参本质上也是传递了地址,且是第一行这个一维数组的地址

    #include <stdio.h>
    
    void print_arr(int (*p)[5], int r, int c) {
        int i = 0, j = 0;
        for (i = 0; i < r; i++) {
            for (j = 0; j < c; j++) {
                printf("%d ", *(*(p + i) + j));
                // printf("%d ", p[i][j]);
                // printf("%d ", (*(p + i))[j]);
            }
        }
    }
    
    int main(int argc, const char * argv[]) {
        int arr[3][5] = {{1, 2, 3, 4, 5}, {2, 3, 4, 5, 6}, {3, 4, 5, 6, 7}};
        print_arr(arr, 3, 5);
    
        return 0;
    }  
    

4.函数指针变量

  • 4.1 定义和指向原理

    • 数组名表示数组首元素的地址,通过它可以定位到数组元素。类似地,函数名表示函数的地址,也可以通过&函数名的方式获得函数的地址
    #include <stdio.h>
    
    void test(void) {
        printf("test_function\n");
    }
    
    int main(int argc, const char * argv[]) {
        printf("test:  %p\n", test);
        printf("&test: %p\n", &test);
        
        return 0;
    }
    

    image

    • 既然函数名就是函数的地址,就可以用指针变量存储该地址,这样的指针变量称为函数指针,它的写法和数组指针类似
    void test(void) {
        printf("test_function\n");
    }
    
    void (*pf1)(void) = &test;
    void (*pf2)(void) = test;
    
    int Add(int x, int y) {
        return x + y;
    }
    
    int (*pf3)(int, int) = Add;
    int (*pf4)(int x, int y) = &Add;    // 加不加x和y都可以
    
    int        (*pf3)    (int x, int y)    =        &arr;
     |           |             |
     |           |             |
     |           |            pf3指向函数的参数类型和参数数量
     |          函数指针变量名
    pf3指向函数的返回类型
    
  • 4.2 函数指针的使用

    • 通过函数指针调用指针指向的函数

    • 由于&函数名函数名的打印结果一致,所以通过函数指针调用函数时函数指针旁加不加解引用操作符*均可

    #include <stdio.h>
    
    int Add(int x, int y) {
        return x + y;
    }
    
    int main(int argc, const char * argv[]) {
        int (*pf3)(int, int) = Add;
        printf("%d\n", (*pf3)(2, 3));    // 打印结果:5
        printf("%d\n", pf3(4, 5));        // 打印结果:9
        
        return 0;
    }
    
  • 4.3 typedef关键字

    • typedef用于类型重命名,可以将复杂的类型简单化
    // 1.普通变量类型重命名
    typedef unsigned int uint;
    // 将unsigned int重命名为uint
    
    // 2.指针变量类型重命名
    typedef int * ptr_t;
    // 将int * 重命名为 ptr_t
    
    // 3.数组指针重命名
    typedef int(*parr_t) [5];
    // 将int (*)[5] 重命名为parr_t,新类型名必须在 * 的右边
    
    // 4.函数指针重命名
    typedef void (*pf_t)(int);
    // 将void (*) (int) 重命名为pf_t,新类型名必须在 * 的右边
    
    // 5.分析以下代码
    (*(void (*)())0)();
    // 是一次函数调用,调用0地址处存放的那个函数。0地址处的函数没有参数,返回类型为void
    
    // 6.分析以下代码并用typedef重命名
    void (*signal(int, void(*)(int)))(int);
    // 是一次函数声明,函数名为signal,参数有两个,类型分别是整型int和函数指针void(*)(int)
    // signal函数的返回值也是一个函数指针,类型为 void (*) (int),它指向的函数参数是,范回类型为void
    
    typedef void (*pf_t) (int)
    pf_t signal(int, pf_t)
    

5.函数指针数组

  • 5.1 定义和指向原理

    • 将函数的地址存入一个数组中,该数组称为函数指针数组
    int (*parr1[3])();
    // parr1先和[]结合,说明parr1是数组,数组的内容是int (*)()类型的函数指针
    
  • 5.2 转移表

    • 使用一般的方法编写程序实现一个计算器
    #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(void) {
        printf("**********************\n");
        printf("**** 1.Add  2.Sub ****\n");
        printf("**** 3.Mul  4.Div ****\n");
        printf("******* 0. Exit ******\n");
        printf("**********************\n");
    }
    
    int main(int argc, const char * argv[]) {
        int x = 0, y = 0;
        int input = 0;
        int ret = 0;
        
        do {
            menu();
            printf("请输入你的选择:");
            scanf("%d", &input);
            
            switch (input) {
                case 1:
                    printf("请输入两个数:");
                    scanf("%d %d", &x, &y);
                    ret = Add(x, y);
                    printf("ret = %d\n", ret);
                    break;
                case 2:
                    printf("请输入两个数:");
                    scanf("%d %d", &x, &y);
                    ret = Sub(x, y);
                    printf("ret = %d\n", ret);
                    break;
                case 3:
                    printf("请输入两个数:");
                    scanf("%d %d", &x, &y);
                    ret = Mul(x, y);
                    printf("ret = %d\n", ret);
                    break;
                case 4:
                    printf("请输入两个数:");
                    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;
    }
    

    image

    • 使用函数指针数组编写程序实现一个计算器
    // 代码1:通过函数指针数组实现
    #include <stdio.h>
    
    void menu(void) {
        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 argc, const char * argv[]) {
        int x = 0, y = 0;
        int input = 0;
        int ret = 0;
        int (*pf[5])(int, int) = {0, Add, Sub, Mul, Div};
        
        do {
            menu();
            printf("请输入你的选择:");
            scanf("%d", &input);
            
            if (input >= 1 && input <= 4) {
                printf("请输入两个整数:");
                scanf("%d %d", &x, &y);
                ret = pf[input](x, y);
                printf("ret = %d\n", ret);
            }
            else if (input == 0) {
                printf("退出...\n");
            }
            else {
                printf("输入错误,重新输入!\n");
            }
        } while (input);
    
        return 0;
    }
    
    
    
    // 代码2:通过函数指针实现
    #include <stdio.h>
    
    void menu(void) {
        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, y = 0;
        int ret = 0;
        
        printf("输入两个整数:");
        scanf("%d %d", &x, &y);
        ret = pf(x, y);
        printf("ret = %d\n", ret);
    }
    
    int main(int argc, const char * argv[]) {
        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;
    }
    

6.回调函数

  • 6.1 定义和实例对比

    • 回调函数就是通过函数指针调用的函数。如果将函数指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,被调用的函数就是回调函数

    • 回调函数不是由该函数的实现方法直接调用,而是在特定的事件或条件发生时由另外的一方调用,用于对该事件或条件进行响应

    • 在 5.2 节实现计算器的案例中,最开始使用的是普通方法,在switch结构中输入输出操作重复出现,代码有较多冗余。这些代码只有调用函数的逻辑略有区别,因此将调用函数的地址以实参的形式传递给calc()函数,它的参数是一个函数指针,该指针指向什么函数就调用什么函数。这里使用的就是回调函数的功能

    image

  • 6.2 qsort函数

    • 1.函数原型
    // 对base指向数组的num个元素进行排序,每个元素的大小为bytes字节,排序规则由compar函数决定
    // 此函数使用的排序算法通过调用指定的compar函数来比较元素对,调用时会将指向这两个元素的指针作为参数传递给compar函数
    // 该函数无返回值,但会修改base所指向数组的内容,按照compar函数定义的规则对元素重新排序
    // 对于值相等的元素,它们的最终顺序是未定义的(即不保证稳定排序)
     void qsort(void* base,   // 指向待排序数组的第1个元素的指针
                size_t num,   // base指向数组中的元素个数
                size_t size,  // base指向数组中各个元素的大小,单位是字节
                int (*compare)(const void *, const void *)   // 函数指针,传递函数的地址
                );
    

    image

    • 2.使用qsort()函数排序整型数据
    
    
    #include <stdio.h>
    #include <string.h>
    
    //int cmp_int(const void *p1, const void *p2) {
    //    if (*(int *)p1 > *(int *)p2) {
    //        return 1;
    //    }
    //    else if(*(int *)p1 < *(int *)p2) {
    //        return -1;
    //    }
    //    else {
    //        return 0;
    //    }
    //}
    
    int cmp_int(const void *p1, const void *p2) {
        return *(int *)p1 - *(int *)p2;
    }
    
    void print_arr(int arr[], int sz) {
        int i = 0;
        for (i = 0; i < sz; i++) {
            printf("%d ", arr[i]);
        }
        printf("\n");
    }
    
    void test1(void) {
        int arr[] = {2, 4, 6, 8, 0, 5, 3, 7, 9};
        int sz = sizeof(arr) / sizeof(arr[0]);
        qsort(arr, sz, sizeof(int), cmp_int);
        print_arr(arr, sz);
    }
    
    int main(int argc, const char * argv[]) {
        // insert code here...
        test1();
    
        return 0;
    }
    

    image

    • 3.使用qsort()函数排序结构数据
    #include <stdio.h>
    #include <string.h>
    
    struct Stu {
        char name[20];
        int age;
    };
    
    void print_stu(struct Stu arr[], int sz) {
        int i = 0;
        for (i = 0; i < sz; i++) {
            printf("%s--%d\n", arr[i].name, arr[i].age);
        }
        printf("\n");
    }
    
    // 按照年龄排序
    int cmp_stu_by_age(const void *p1, const void *p2) {
        return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
    }
    
    // 测试qsort函数排序结构体数据,按照名字比较两个结构体数据
    // 名字是字符串,字符串比较时使用strcmp函数
    int cmp_stu_by_name(const void *p1, const void *p2) {
        // ->的优先级1高于强制类型转换2,所以要将转换后的指针整体使用->
        return strcmp(((struct Stu *)p1)->name, ((struct Stu*)p2)->name);
    }
    
    void test2(void) {
        // 按姓名排序时需要将结构体数组中的姓名写成英文,写中文的话可能会因为编码导致预期跟实际结果不一致
        struct Stu arr[] = {{"zhangsan", 34}, {"lisi", 24}, {"wangwu", 40} };
        int sz = sizeof(arr) / sizeof(arr[0]);
        qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
        print_stu(arr, sz);
    }
    
    void test3(void) {
        struct Stu arr[] = {{"张三", 34}, {"李四", 24}, {"王五", 40} };
        int sz = sizeof(arr) / sizeof(arr[0]);
        qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
        print_stu(arr, sz);
    }
    
    int main(int argc, const char * argv[]) {
        // insert code here...
        printf("sorted by name:\n");
        test2();
        printf("sorted by age:\n");
        test3();
        return 0;
    }
    

    image

    • 4.使用冒泡排序模拟实现qsort()函数
    #include <stdio.h>
    #include <string.h>
    
    struct Stu {
        char name[20];
        int age;
    };
    
    void swap(char *buff1, char *buff2, size_t width) {
        int i = 0;
        char temp = 0;
        for (i = 0; i < width; i++) {
            temp = *buff1;
            *buff1 = *buff2;
            *buff2 = temp;
            
            buff1++;
            buff2++;
        }
    }
    
    void bubble_sort(void *base, size_t sz, size_t width, int (*cmp)(const void *p1, const void *p2)) {
        int i = 0, j = 0;
        for (i = 0; i < sz - 1; i++) {
            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 cmp_int(const void *p1, const void *p2) {
        return *(int *)p1 - *(int *)p2;
    }
    
    int cmp_stu_by_name(const void *p1, const void *p2) {
        return strcmp(((struct Stu *)p1)->name, ((struct Stu*)p2)->name);
    }
    
    int cmp_stu_by_age(const void *p1, const void *p2) {
        return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
    }
    
    void print_arr(int arr[], int sz) {
        int i = 0;
        for (i = 0; i < sz; i++) {
            printf("%d ", arr[i]);
        }
        printf("\n");
    }
    
    void print_stu(struct Stu arr[], int sz) {
        int i = 0;
        for (i = 0; i < sz; i++) {
            printf("%s--%d\n", arr[i].name, arr[i].age);
        }
        printf("\n");
    }
    
    void test1(void) {
        int arr[] = {2, 4, 6, 8, 0, 5, 3, 7, 9};
        int sz = sizeof(arr) / sizeof(arr[0]);
        bubble_sort(arr, sz, sizeof(int), cmp_int);
        print_arr(arr, sz);
    }
    
    void test2(void) {
        struct Stu arr[] = {{"zhangsan", 34}, {"lisi", 24}, {"wangwu", 40} };
        int sz = sizeof(arr) / sizeof(arr[0]);
        bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
        print_stu(arr, sz);
    }
    
    void test3(void) {
        struct Stu arr[] = {{"张三", 34}, {"李四", 24}, {"王五", 40} };
        int sz = sizeof(arr) / sizeof(arr[0]);
        bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
        print_stu(arr, sz);
    }
    
    int main(int argc, const char * argv[]) {
        // insert code here...
    //    test1();
    //    test2();
        test3();
        return 0;
    }
    
    
posted @ 2025-08-16 09:47  pycoder_666  阅读(17)  评论(0)    收藏  举报