C语言指针高阶——二级指针、函数指针、qsort与动态内存(13)

C语言指针高阶——二级指针、函数指针、qsort与动态内存(13) 一、回顾指针操作数组的总结指针操作数组只需要知道数组首元素地址即可。函数传参时所谓传数组本质就是传首元素地址所以形参对应的是一个指针变量。访问数组元素的四种方式a[i] // 数组名 下标 *(ai) // 数组名 指针偏移 p[i] // 指针变量 下标 *(pi) // 指针变量 指针偏移指针操作二维数组的步骤回顾cint a[3][4] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};Step1确定首元素类型定义指针首元素: a[0]数据类型为 int[4] 首元素地址: a[0]地址类型为 int(*)[4]int (*p)[4] a; // 数组名即首元素地址 int (*p)[4] a[0]; // 等价写法Step2通过指针访问二维数组元素p[i] → 第i行的地址行地址类型 int(*)[4] *(p[i]) → 行地址转换为列地址相当于第i行的数组名类型 int* *(p[i]) j → 列地址偏移到第j个元素 *(*(p[i])j) → 访问第i行第j列的元素p[i][j]⇔*(*(pi)j)⇔a[i][j]三种写法等价。字符型二维数组char s[3][10] {hello1, hello2, hello3}; // 二维字符数组 char (*p)[10] s; p[i] // 第i行地址类型 char(*)[10] *(p[i]) // 第i行首字符地址类型 char*指针数组char *s[3] {hello1, hello2, hello3}; // 数组中存放的是 char* 类型的指针每个指针指向一个字符串常量二、二级指针基本概念指针级别含义示例一级指针存放普通变量的地址int a 10; int *p a;二级指针存放一级指针变量的地址int **pp p;char *s hello; char **p s; // p 是二级指针存放的是 char* 变量 s 的地址指针数组与二级指针的关系char *s[3] {hello, world, china}; s[0] // 首元素数据类型 char* s[0] // 首元素地址数据类型 char** // 二级指针接收指针数组首地址 void outputStr(char **p, int len) { for (int i 0; i len; i) { printf(%s\n, *(p i)); // *(pi) s[i] } }理解s[0]本身是char*类型取它的地址s[0]就是char**类型。所以指针数组的数组名首元素地址可以直接传给char **形参。三、函数指针基本概念函数名就是函数的入口地址可以用一个指针变量来保存这个地址。int add(int a, int b) { return a b; }函数类型去掉函数名add剩下int (int, int)表示返回值为int、参数为两个int的一类函数。函数指针的正确写法int (*p)(int, int) add; // p 是一个函数指针指向 add 函数 int result p(3, 5); // 通过函数指针调用函数等价于 add(3, 5)注意int (*p)(int, int)中*p必须加括号否则int *p(int, int)变成了返回指针的函数声明。用途回调函数函数指针最核心的用途是回调函数——把函数作为参数传给另一个函数实现灵活的策略定制。示例加减乘除的回调#include stdio.h int add(int a, int b) { return a b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a * b; } int div(int a, int b) { return b ! 0 ? a / b : 0; } void processData(int a, int b, int (*pfunc)(int, int)) { printf(结果: %d\n, pfunc(a, b)); } int main() { int a 10, b 5; processData(a, b, add); // 结果: 15 processData(a, b, sub); // 结果: 5 processData(a, b, mul); // 结果: 50 processData(a, b, div); // 结果: 2 return 0; }指针声明辨析一个有10个指针的数组该指针指向整型数int *a[10]指针数组一个指向有10个整型数数组的指针int (*a)[10]数组指针一个指向函数的指针函数有一个int参数返回intint (*a)(int)函数指针一个有10个指针的数组指针指向函数函数有一个int参数返回intint (*a[10])(int)函数指针数组四、qsort——系统提供的快速排序qsort是标准库stdlib.h提供的通用排序函数基于快速排序实现。函数原型void qsort( void *base, // 数组起始地址 size_t nmemb, // 元素个数 size_t size, // 单个元素大小字节 int (*compar)(const void *, const void *) // 比较函数回调 );比较函数规则int compare(const void *a, const void *b)参数a、b待比较的两个元素的地址返回值 0元素 a 大于元素 b 0元素 a 小于元素 b 0两元素相等使用示例整型数组排序升序#include stdio.h #include stdlib.h int cmpInt(const void *a, const void *b) { return *(int *)a - *(int *)b; // 升序 } int main() { int a[] {10, 3, 8, 7, 6, 5, 2, 9, 1, 4}; int len sizeof(a) / sizeof(a[0]); qsort(a, len, sizeof(int), cmpInt); for (int i 0; i len; i) printf(%d , a[i]); printf(\n); // 输出: 1 2 3 4 5 6 7 8 9 10 return 0; }字符串数组排序#include stdio.h #include stdlib.h #include string.h int cmpStr(const void *a, const void *b) { return strcmp(*(const char **)a, *(const char **)b); } int main() { char *s[] {hello, world, china, english, america}; int len sizeof(s) / sizeof(s[0]); qsort(s, len, sizeof(char *), cmpStr); for (int i 0; i len; i) printf(%s , s[i]); printf(\n); // 输出: america china english hello world return 0; }五、void * 万能指针void * // 空类型指针void *可以接收任意类型的地址相当于万能指针常用在函数形参中使函数能处理不同类型的数据限制不能直接用*p做间接访问需要先强制转换为具体类型int a 10; void *p a; // ✓ 可以接收 // printf(%d, *p); // ✗ 不能直接访问 printf(%d, *(int *)p); // ✓ 强转后可以访问六、堆区与动态内存分配堆区空间特点空间大需要程序员手动申请手动释放mallocvoid *malloc(size_t size);功能在堆区申请size字节的连续空间参数size—— 要申请的字节数返回值成功返回申请到的空间首地址void *失败返回NULLint *p (int *)malloc(10 * sizeof(int)); // 申请10个int的空间freevoid free(void *ptr);功能释放由malloc申请的堆区空间参数ptr——malloc返回的首地址使用注意事项.free后置NULL—— 释放后指针变为悬空指针仍指向已释放空间建议free(p); p NULL;内存泄漏—— 只malloc没有free空间一直被占用造成内存泄漏malloc和free要成对出现malloc申请的空间是连续的不要多次free同一块空间指针函数中不能返回局部变量的地址—— 局部变量空间在栈上函数结束后空间已释放示例输入字符串存到堆上#include stdio.h #include stdlib.h int main() { int n 100; char *p (char *)malloc(n * sizeof(char)); if (p NULL) { printf(内存申请失败\n); return 1; } printf(请输入字符串: ); fgets(p, n, stdin); printf(你输入的是: %s\n, p); free(p); p NULL; return 0; }示例堆上定义数组并求和#include stdio.h #include stdlib.h int main() { int *p (int *)malloc(10 * sizeof(int)); if (p NULL) return 1; printf(请输入10个偶数:\n); int sum 0; for (int i 0; i 10; i) { scanf(%d, p i); sum *(p i); } printf(10个偶数的和: %d\n, sum); free(p); p NULL; return 0; }七、main 函数参数int main(int argc, const char *argv[])argc命令行参数的个数argv命令行参数的指针数组每个元素是一个字符串以NULL结尾// 假设执行: ./program hello world // argc 3 // argv[0] ./program // argv[1] hello // argv[2] world // argv[3] NULL八、指针函数返回值为指针的函数函数返回值类型为指针的函数称为指针函数char *strcpy(...) // 返回 char* char *strcat(...) // 返回 char*注意指针函数中不能返回局部变量的地址因为局部变量在栈上函数结束后空间已释放。如需返回地址应使用堆区空间。九、全局总结C语言指针体系1. 指针基础 ├── 指针概念指针 地址 ├── 指针变量定义基类型 * 指针变量名 ├── 指针运算加减、关系 ├── 指针变量访问 *p间接访问 └── 指针的用途——被调函数修改主调函数数据 2. 指针进阶 ├── 指针操作一维数组整型、字符型 ├── const 与指针 └── 手写字符串函数strlen, strcpy, strcmp... 3. 指针高阶 ├── 指针操作二维数组数组指针 int(*p)[4] ├── 指针数组与二级指针char *s[], char **p ├── 函数指针与回调函数int (*p)(int, int) ├── qsort 通用排序 ├── void * 万能指针 ├── 堆区动态内存malloc / free └── main 函数参数argc, argv