嵌入式Linux学习C语言(Day10)C 语言指针高级应用详解(运算符优先级 + 数组指针 + 函数指针 + 实战)
点赞+关注私信领取全部代码资料~
一、* 和 ++ 作用于指针(优先级核心考点)
*(解引用)和++(自增)是指针操作中最常用的运算符,二者的优先级和结合性直接决定指针操作的结果,也是新手最易混淆的知识点,核心原则:*后置 ++ 优先级高于,前置 ++ 与*优先级相同,结合性从右到左 **。
1.1 *p++(高频考点,重点掌握)
核心解析
- 优先级:
++(后置) >*(解引用); - 结合性:从右到左,等价于
*(p++); - 执行逻辑(分两步,先取值,后移动指针):i.
++作用于指针变量p,且是后置 ++(先使用p的当前值,再让p自增);ii. 表达式的最终结果是*p(即p自增前指向的内容);iii. 表达式执行完毕后,p会自动自增 1(按p的类型偏移对应字节数)。
经典示例(直观理解)
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30};
int *p = arr; // p指向arr[0](地址对应10)
// *p++:先取*p(10),再让p指向arr[1]
printf("表达式结果:%d\n", *p++); // 输出10
printf("p指向内容:%d\n", *p); // 输出20(p已自增,指向arr[1])
return 0;
}
1.2 (p++)(与p++ 等价)
核心解析
- 括号仅改变表达式的可读性,执行逻辑与 * p++ 完全一致;
- 本质:括号强制先执行
p++(后置),但后置 ++ 的特性是 “先使用,后自增”,因此最终结果仍为p自增前的解引用值,执行后p自增。
补充说明
int *p = arr;
// 以下两句完全等价,推荐写*(p++),可读性更强
printf("%d\n", *p++);
printf("%d\n", *(p++));
1.3 (*p)++(解引用后自增,重点区分)
核心解析
- 优先级:括号
()>++(后置)、*(解引用); - 执行逻辑(分两步,先解引用,后自增内容):i. 括号强制先执行
*p(解引用),++作用于*p(即指针指向的内容),且是后置 ++;ii. 表达式的最终结果是*p(即内容自增前的值);iii. 表达式执行完毕后,*p(指向的内容)会自动自增 1,指针 p 的指向不变。
经典示例(与 * p++ 对比)
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30};
int *p = arr; // p指向arr[0](内容10)
// (*p)++:先取*p(10),再让*p自增为11
printf("表达式结果:%d\n", (*p)++); // 输出10
printf("p指向内容:%d\n", *p); // 输出11(内容自增,p指向不变)
printf("p的地址:%p\n", p); // 地址与初始一致,未移动
return 0;
}
1.4 ++*p(前置自增,解引用)
核心解析
- 优先级:
++(前置)与*(解引用)优先级相同,结合性从右到左,等价于++(*p); - 执行逻辑(分两步,先自增内容,再取值):i.
++作用于*p(指针指向的内容),且是前置 ++(先让内容自增,再使用自增后的值);ii. 表达式的最终结果是*p自增后的值。
经典示例
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30};
int *p = arr; // p指向arr[0](内容10)
// ++*p:先让*p自增为11,再取*p的值
printf("表达式结果:%d\n", ++*p); // 输出11
printf("p指向内容:%d\n", *p); // 输出11(内容已自增)
return 0;
}
1.5 *++p(前置自增,指针移动)
核心解析
- 优先级:
++(前置) >*(解引用); - 执行逻辑(分两步,先移动指针,再解引用):i.
++作用于指针p,且是前置 ++(先让p自增 1,移动指针地址,再使用移动后的p);ii. 指针p先按自身类型偏移对应字节数,再对移动后的p进行解引用,取其指向的内容。
经典示例(与 * p++ 对比)
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30};
int *p = arr; // p指向arr[0](地址对应10)
// *++p:先让p指向arr[1],再取*p的值
printf("表达式结果:%d\n", *++p); // 输出20
printf("p指向内容:%d\n", *p); // 输出20(p已移动)
return 0;
}
1.6 核心对比表(一目了然,必背)
| 表达式 | 优先级顺序 | 执行逻辑 | 指针 p 是否移动 | 指向内容是否改变 | 表达式结果 |
|---|---|---|---|---|---|
| *p++ | ++ > * | 先 * p(取值),后 p++(移动指针) | 是(自增 1) | 否 | p 自增前的 * p |
| *(p++) | ++ > *(括号不改变优先级) | 与 * p++ 完全一致 | 是(自增 1) | 否 | p 自增前的 * p |
| (*p)++ | () > ++、* | 先 * p(取值),后 (*p)++(内容自增) | 否 | 是(自增 1) | 内容自增前的 * p |
| ++*p | ++ 与 * 同级(右结合) | 先 (p)++(内容自增),后取p | 否 | 是(自增 1) | 内容自增后的 * p |
| *++p | ++ > * | 先 p++(移动指针),后 * p(取值) | 是(自增 1) | 否 | p 自增后的 * p |
二、数组指针(指向数组的指针,二维数组核心)
数组指针是指向整个数组的指针,而非指向数组元素的指针,核心应用于二维数组操作,其类型由 “数组的元素类型 + 数组长度” 决定,与指针数组容易混淆,需重点区分。
2.1 数组指针的定义与类型判定(以二维数组为例)
示例:int arr [2][3];(2 行 3 列的二维数组)
2.1.1 arr 的类型判定(核心难点)
- 二维数组
arr的本质是 “数组的数组”:arr是一个包含 2 个元素的数组,每个元素又是一个包含 3 个 int 的一维数组(arr[0]、arr[1]均为一维数组); arr[0]:一维数组名,等价于&arr[0][0],类型为int*(指向 int 元素的一级指针);&arr[0]:对一维数组arr[0]取地址,不是指向元素的地址,而是指向整个一维数组(包含 3 个 int),因此类型为int (*)[3](数组指针类型);- 结论:
arr的类型与&arr[0]一致,均为int (*)[3](指向包含 3 个 int 的数组的指针)。
2.1.2 数组指针的运算能力
数组指针的运算能力遵循指针通用规则:*运算能力 = sizeof (去掉一个)**;
- 数组指针类型
int (*)[3],去掉一个 * 后是int [3](包含 3 个 int 的数组); - 运算能力 = sizeof (int [3]) = 3 * sizeof (int) = 12 字节(64 位系统,int 为 4 字节);
- 含义:数组指针
p++时,指针会向后移动 12 字节,即跳过整个包含 3 个 int 的一维数组。
2.2 数组指针的定义与初始化(标准写法)
标准格式
// 语法:元素类型 (*指针变量名)[数组长度];
int arr[2][3] = {{1,2,3}, {4,5,6}};
int (*p)[3] = arr; // 正确:p是数组指针,指向二维数组arr
关键说明
p是数组指针变量,简称数组指针;p的类型是int (*)[3],限制其只能指向 “包含 3 个 int 元素的数组”;- 数组指针的内存大小:与所有指针一致,64 位系统中为 8 字节(与数组长度无关);
printf("sizeof(p) = %zd\n", sizeof(p)); // 输出8(64位系统) - 数组指针访问二维数组的方式(两种等价):
// 方式1:数组指针解引用 + 数组下标 printf("%d\n", (*p)[0]); // 输出1(*p等价于arr[0],(*p)[0] = arr[0][0]) // 方式2:指针运算 + 数组下标 p++; // 移动12字节,指向arr[1](第二个一维数组) printf("%d\n", (*p)[1]); // 输出5(arr[1][1])
2.3 核心注意(避免与指针数组混淆)
- 数组指针:
int (*p)[3],括号优先,p是指针,指向一个包含 3 个 int 的数组; - 指针数组:
int *p[3],[]优先级高于*,p是数组,包含 3 个 int * 类型的指针; - 记忆口诀:括号抱指针,是数组指针;指针在数组里,是指针数组。
三、指针数组(存储指针的数组,字符串存储核心)
指针数组是专门用于存储指针变量的数组,数组的每个元素都是一个指针(可指向 int、char、函数等),最常用的场景是存储多个字符串(节省内存,灵活高效)。
3.1 指针数组的定义与本质
标准格式(以 char * 为例)
char *str[10]; // 指针数组:数组str包含10个元素,每个元素都是char*类型的指针
核心本质
- 数组
str的元素类型是char*(字符指针),每个元素可指向一个字符串(字符串的首地址); - 指针数组的核心作用:存储多个不同长度的字符串,无需提前分配固定大小的内存(比二维字符数组更灵活)。
对比:指针数组与二维字符数组(存储字符串)
// 1. 二维字符数组(固定长度,浪费内存)
char str1[10][20]; // 10个字符串,每个最多19个字符,未使用的空间浪费
// 2. 指针数组(灵活,节省内存)
char *str2[10]; // 10个字符指针,每个指针指向一个字符串(长度可不同)
str2[0] = "hello";
str2[1] = "C language";
3.2 指针数组的核心应用(存储多个字符串)
经典示例
#include <stdio.h>
int main() {
// 指针数组存储5个字符串,每个元素指向字符串首地址
char *str[] = {"apple", "banana", "cherry", "date", "grape"};
int len = sizeof(str) / sizeof(str[0]); // 计算数组元素个数(5)
// 遍历指针数组,打印所有字符串
for (int i=0; i<len; i++) {
printf("str[%d] = %s\n", i, str[i]); // str[i]是字符串首地址
}
return 0;
}
运行结果
str[0] = apple
str[1] = banana
str[2] = cherry
str[3] = date
str[4] = grape
3.3 实战练习(指针数组 + 命令行参数,校招高频)
命令行参数argv是典型的指针数组(char *argv[]),argv[0]是程序名称,argv[1]及以后是用户输入的参数,以下练习结合命令行参数,实现指针数组的实际应用,代码可直接编译运行。
练习 1:将命令行第二个参数字符串转换为整型数
功能说明
运行程序时输入命令行参数(如./a.out 896),将argv[1](字符串 “896”)转换为整型数 896 并输出,核心是遍历字符串,通过 ASCII 码计算整型值。
完整代码
#include <stdio.h>
#include <string.h>
// 将字符串转换为整型数,失败返回-1
int str_to_int(const char *ptr) {
if (ptr == NULL || *ptr == '\0') {
return -1; // 空指针或空字符串,转换失败
}
int num = 0;
// 遍历字符串,逐字符转换(仅处理数字字符)
while (*ptr != '\0') {
if (*ptr < '0' || *ptr > '9') {
return -1; // 包含非数字字符,转换失败
}
num = num * 10 + (*ptr - '0'); // ASCII码转换:'0'的ASCII是48
ptr++;
}
return num;
}
int main(int argc, char *argv[]) {
// 检查命令行参数个数(至少需要2个:程序名+待转换字符串)
if (argc < 2) {
printf("用法:%s <待转换字符串>\n", argv[0]);
return -1;
}
int result = str_to_int(argv[1]);
if (result == -1) {
printf("转换失败!请输入合法的数字字符串\n");
} else {
printf("转换结果:%d\n", result);
}
return 0;
}
测试方法
- 编译:
gcc test1.c -o a.out; - 运行:
./a.out 896,输出:转换结果:896; - 异常测试:
./a.out abc,输出:转换失败!请输入合法的数字字符串。
练习 2:实现简单四则运算(命令行参数版)
功能说明
运行程序时输入命令行参数(如./a.out 100 + 9、./a.out 10 x 99),实现两个整数的加(+)、减(-)、乘(x)、除(/)运算,输出运算结果,核心是解析命令行参数、匹配运算符。
完整代码
#include <stdio.h>
#include <string.h>
// 字符串转整型(复用练习1的函数)
int str_to_int(const char *ptr) {
if (ptr == NULL || *ptr == '\0') return -1;
int num = 0;
while (*ptr != '\0') {
if (*ptr < '0' || *ptr > '9') return -1;
num = num * 10 + (*ptr - '0');
ptr++;
}
return num;
}
int main(int argc, char *argv[]) {
// 检查命令行参数个数(必须4个:程序名+数字1+运算符+数字2)
if (argc != 4) {
printf("用法:%s <数字1> <运算符> <数字2>\n", argv[0]);
printf("支持运算符:+(加)、-(减)、x(乘)、/(除)\n");
return -1;
}
// 1. 转换两个数字字符串为整型
int num1 = str_to_int(argv[1]);
int num2 = str_to_int(argv[3]);
if (num1 == -1 || num2 == -1) {
printf("错误:请输入合法的整数\n");
return -1;
}
// 2. 匹配运算符,执行运算
char *op = argv[2];
int result = 0;
if (strcmp(op, "+") == 0) {
result = num1 + num2;
} else if (strcmp(op, "-") == 0) {
result = num1 - num2;
} else if (strcmp(op, "x") == 0) {
result = num1 * num2;
} else if (strcmp(op, "/") == 0) {
if (num2 == 0) {
printf("错误:除数不能为0\n");
return -1;
}
result = num1 / num2;
} else {
printf("错误:不支持的运算符\n");
return -1;
}
// 3. 输出运算结果
printf("%d %s %d = %d\n", num1, op, num2, result);
return 0;
}
测试方法
- 编译:
gcc test2.c -o a.out; - 运行 1:
./a.out 100 + 9,输出:100 + 9 = 109; - 运行 2:
./a.out 10 x 99,输出:10 x 99 = 990; - 运行 3:
./a.out 50 - 15,输出:50 - 15 = 35; - 运行 4:
./a.out 100 / 5,输出:100 / 5 = 20。
四、函数指针(指向函数的指针,回调函数核心)
函数指针是存储函数地址的指针,函数的地址就是函数名,其类型由 “函数的返回值类型 + 参数类型” 决定,核心应用于回调函数、接口封装、函数表等场景,是 C 语言实现多态的基础,也是校招面试必考知识点。
4.1 函数指针的核心原理
核心前提
- 指针的本质是存储地址,函数在内存中也有固定的首地址(函数入口地址);
- 存储函数地址的指针,就是函数指针;
- 函数的地址 = 函数名(如
strlen就是strlen函数的首地址)。
函数类型的判定(关键)
函数的类型由返回值类型和参数类型(含个数、顺序) 共同决定,与函数名无关:
- 示例:
size_t strlen(const char *p);- 函数名:
strlen(函数地址); - 返回值类型:
size_t; - 参数类型:
const char *; - 函数类型:
size_t (const char *); - 函数指针类型(存储该函数地址):
size_t (*)(const char *)(括号不可省略)。
- 函数名:
4.2 函数指针的定义与初始化(标准写法)
标准格式
// 语法:返回值类型 (*函数指针变量名)(参数类型列表);
// 示例:定义函数指针ptr,指向strlen函数
#include <string.h>
size_t (*ptr)(const char *) = strlen;
关键说明
ptr是函数指针变量,简称函数指针;ptr的类型是size_t (*)(const char *),限制其只能指向 “返回值为 size_t、参数为 const char*” 的函数;- 函数指针的赋值:直接将函数名赋值给函数指针(函数名就是函数地址),无需加
&(&strlen与strlen等价);size_t (*ptr)(const char *) = strlen; // 正确 size_t (*ptr)(const char *) = &strlen; // 正确,与上面等价
4.3 函数指针的核心特性(必记)
- 函数指针不做指针运算:函数指针的作用是指向函数、调用函数,其运算无实际意义,编译器通常不支持函数指针的
+、-、++、--运算; *ptr == ptr:函数指针解引用*ptr与函数指针本身ptr等价,均代表函数地址,调用时可省略*;// 以下两种调用方式完全等价,推荐写ptr("hello"),更简洁 size_t len1 = (*ptr)("hello"); // 解引用后调用 size_t len2 = ptr("hello"); // 直接调用,等价&strlen与strlen等价:函数名前加&,获取的仍是函数地址,与函数名本身无区别。
4.4 函数指针的调用示例(直观理解)
#include <stdio.h>
#include <string.h>
int main() {
// 1. 定义函数指针,指向strlen函数
size_t (*strlen_ptr)(const char *) = strlen;
// 2. 用函数指针调用strlen函数
char str[] = "hello C";
size_t len = strlen_ptr(str); // 等价于strlen(str)
printf("字符串长度:%zd\n", len); // 输出7
// 3. 解引用调用(等价)
len = (*strlen_ptr)(str);
printf("字符串长度:%zd\n", len); // 输出7
return 0;
}
4.5 函数指针的核心应用场景(简单举例)
函数指针最常用的场景是回调函数(将函数作为参数传递给另一个函数),例如:
#include <stdio.h>
// 回调函数:比较两个int值,返回1(a>b)、-1(a<b)、0(a==b)
int cmp_int(const void *a, const void *b) {
return *(int *)a - *(int *)b;
}
// 通用打印函数:接收函数指针,调用回调函数
void print_cmp(int a, int b, int (*cmp)(const void *, const void *)) {
int ret = cmp(&a, &b);
if (ret > 0) {
printf("%d > %d\n", a, b);
} else if (ret < 0) {
printf("%d < %d\n", a, b);
} else {
printf("%d == %d\n", a, b);
}
}
int main() {
// 将cmp_int函数作为参数传递给print_cmp
print_cmp(10, 20, cmp_int); // 输出10 < 20
print_cmp(30, 15, cmp_int); // 输出30 > 15
return 0;
}
五、课后作业(校招高频手撕题,附完整源码)
以下作业均结合命令行参数、函数指针、堆内存动态开辟,贴合实战场景,要求代码包含判空、边界处理、内存释放,可直接编译运行,巩固指针高级应用。
作业 1:将命令行第二个参数字符串倒序,生成新字符串并返回
功能说明
- 函数原型:
char *reverse_string(const char *ptr); - 参数:
ptr指向命令行第二个参数(原字符串),const修饰保证原字符串只读; - 返回值:新字符串的地址(堆内存开辟,需手动释放);
- 要求:倒序后手动添加
'\0',避免乱码,处理空指针、空字符串异常。
完整源码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 字符串倒序,返回新字符串地址(堆内存开辟)
char *reverse_string(const char *ptr) {
// 1. 入参判空
if (ptr == NULL) {
printf("错误:空指针\n");
return NULL;
}
// 2. 计算原字符串长度
int len = strlen(ptr);
if (len == 0) { // 空字符串,直接返回空字符串
char *new_str = (char *)malloc(1);
if (new_str == NULL) {
perror("malloc failed");
return NULL;
}
new_str[0] = '\0';
return new_str;
}
// 3. 堆内存开辟新字符串(长度+1,留'\0'位置)
char *new_str = (char *)malloc(len + 1);
if (new_str == NULL) {
perror("malloc failed");
return NULL;
}
// 4. 倒序复制
for (int i=0; i<len; i++) {
new_str[i] = ptr[len - 1 - i];
}
new_str[len] = '\0'; // 补结束符
return new_str;
}
int main(int argc, char *argv[]) {
// 检查命令行参数个数
if (argc < 2) {
printf("用法:%s <待倒序字符串>\n", argv[0]);
return -1;
}
// 调用倒序函数
char *rev_str = reverse_string(argv[1]);
if (rev_str != NULL) {
printf("原字符串:%s\n", argv[1]);
printf("倒序字符串:%s\n", rev_str);
free(rev_str); // 释放堆内存,避免泄漏
rev_str = NULL;
}
return 0;
}
测试方法
运行:./a.out hello,输出:
原字符串:hello
倒序字符串:olleh
作业 2:比较两个命令行参数字符串大小,拼接成新字符串返回
功能说明
- 函数原型:
char *get_new_string(const char *p1, const char *p2); - 参数:
p1(命令行第二个参数)、p2(命令行第三个参数),均为只读; - 要求:① 先比较
p1和p2的大小(按 ASCII 码,同strcmp规则);② 将两个字符串拼接成新字符串(p1在前,p2在后);③ 返回新字符串地址(堆内存开辟);④ 处理空指针、空字符串。
完整源码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 比较p1和p2,拼接成新字符串返回
char *get_new_string(const char *p1, const char *p2) {
// 1. 入参判空(空指针视为空字符串)
const char *s1 = (p1 == NULL) ? "" : p1;
const char *s2 = (p2 == NULL) ? "" : p2;
// 2. 比较两个字符串大小
int cmp_ret = strcmp(s1, s2);
printf("字符串比较结果:");
if (cmp_ret > 0) {
printf("%s > %s\n", s1, s2);
} else if (cmp_ret < 0) {
printf("%s < %s\n", s1, s2);
} else {
printf("%s == %s\n", s1, s2);
}
// 3. 计算拼接后总长度,开辟堆内存
int len1 = strlen(s1);
int len2 = strlen(s2);
int total_len = len1 + len2;
char *new_str = (char *)malloc(total_len + 1); // +1存'\0'
if (new_str == NULL) {
perror("malloc failed");
return NULL;
}
// 4. 拼接字符串
strcpy(new_str, s1); // 先复制s1
strcat(new_str, s2); // 再拼接s2
return new_str;
}
int main(int argc, char *argv[]) {
// 检查命令行参数个数
if (argc < 3) {
printf("用法:%s <字符串1> <字符串2>\n", argv[0]);
return -1;
}
// 调用拼接函数
char *new_str = get_new_string(argv[1], argv[2]);
if (new_str != NULL) {
printf("拼接后字符串:%s\n", new_str);
free(new_str); // 释放堆内存
new_str = NULL;
}
return 0;
}
测试方法
运行:./a.out abc abcd,输出:
字符串比较结果:abc < abcd
拼接后字符串:abcabcd
作业 3(可选题):实现通用数组排序函数(函数指针应用)
功能说明
- 函数原型:
void bubble_sort(void *arr, int nmemb, int size, int (*cmp)(const void *data1, const void *data2)); - 参数说明:
arr:任意类型数组的起始地址(万能指针);nmemb:数组元素个数;size:每个数组元素的字节大小;cmp:函数指针,指向比较函数(用户自定义,决定排序规则);
- 要求:用冒泡排序算法,支持任意类型数组(int、char、float 等),通过函数指针实现排序规则的灵活切换。
完整源码
#include <stdio.h>
#include <string.h>
// 通用冒泡排序:void*接收任意类型数组,cmp函数指针决定排序规则
void bubble_sort(void *arr, int nmemb, int size,
int (*cmp)(const void *data1, const void *data2)) {
// 入参判空
if (arr == NULL || nmemb <= 1 || size <= 0 || cmp == NULL) {
return;
}
// 冒泡排序核心逻辑:逐轮比较,交换相邻元素
for (int i=0; i<nmemb-1; i++) {
int flag = 0; // 优化:标记是否发生交换,无交换则提前退出
for (int j=0; j<nmemb-1-i; j++) {
// 计算当前元素和下一个元素的地址
char *curr = (char *)arr + j * size;
char *next = (char *)arr + (j+1) * size;
// 调用比较函数,cmp返回>0则交换两个元素
if (cmp(curr, next) > 0) {
// 交换两个元素(通用交换,适配任意类型)
char temp[size];
memcpy(temp, curr, size);
memcpy(curr, next, size);
memcpy(next, temp, size);
flag = 1;
}
}
if (flag == 0) {
break; // 无交换,排序完成,提前退出
}
}
}
// 1. 比较int类型:升序排序
int cmp_int(const void *a, const void *b) {
return *(int *)a - *(int *)b;
}
// 2. 比较char类型:按ASCII码升序
int cmp_char(const void *a, const void *b) {
return *(char *)a - *(char *)b;
}
// 3. 比较float类型:升序排序(注意精度问题)
int cmp_float(const void *a, const void *b) {
float diff = *(float *)a - *(float *)b;
return (diff > 0) ? 1 : (diff < 0) ? -1 : 0;
}
// 打印int数组
void print_int_arr(int *arr, int len) {
for (int i=0; i<len; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// 打印char数组
void print_char_arr(char *arr, int len) {
for (int i=0; i<len; i++) {
printf("%c ", arr[i]);
}
printf("\n");
}
// 打印float数组
void print_float_arr(float *arr, int len) {
for (int i=0; i<len; i++) {
printf("%.2f ", arr[i]);
}
printf("\n");
}
int main() {
// 测试1:int数组排序
int int_arr[] = {5, 2, 9, 1, 5, 6};
int int_len = sizeof(int_arr)/sizeof(int);
printf("int数组排序前:");
print_int_arr(int_arr, int_len);
bubble_sort(int_arr, int_len, sizeof(int), cmp_int);
printf("int数组排序后:");
print_int_arr(int_arr, int_len);
// 测试2:char数组排序
char char_arr[] = {'c', 'a', 'f', 'b', 'e'};
int char_len = sizeof(char_arr)/sizeof(char);
printf("\nchar数组排序前:");
print_char_arr(char_arr, char_len);
bubble_sort(char_arr, char_len, sizeof(char), cmp_char);
printf("char数组排序后:");
print_char_arr(char_arr, char_len);
// 测试3:float数组排序
float float_arr[] = {3.14, 1.23, 5.67, 2.34};
int float_len = sizeof(float_arr)/sizeof(float);
printf("\nfloat数组排序前:");
print_float_arr(float_arr, float_len);
bubble_sort(float_arr, float_len, sizeof(float), cmp_float);
printf("float数组排序后:");
print_float_arr(float_arr, float_len);
return 0;
}
运行结果
int数组排序前:5 2 9 1 5 6
int数组排序后:1 2 5 5 6 9
char数组排序前:c a f b e
char数组排序后:a b c e f
float数组排序前:3.14 1.23 5.67 2.34
float数组排序后:1.23 2.34 3.14 5.67
六、核心知识点总结(必背)
*与++优先级:后置 ++ > ,前置 ++ 与同级(右结合),核心区分 “操作指针” 还是 “操作指针指向的内容”;- 数组指针:
int (*p)[3],指向整个数组,类型由数组元素类型和长度决定,运算能力 = 数组总大小,用于二维数组; - 指针数组:
char *p[10],数组元素是指针,最常用场景是存储多个字符串,灵活节省内存; - 函数指针:
size_t (*ptr)(const char *),存储函数地址(函数名),类型由返回值和参数类型决定,不做指针运算,核心用于回调函数; - 万能指针
void*:用于接收任意类型地址,需强制转换后使用,是通用函数(如 bubble_sort)的核心; - 堆内存规范:动态开辟的内存(malloc/calloc)必须手动 free,释放后置 NULL,避免内存泄漏和野指针。
七、高频面试题(校招必考,附答案)
1. *p++、(p)++、++p 的区别?(高频中的高频)
答:核心区别在于 “++ 作用的对象” 和 “执行顺序”:
*p++:++ 作用于指针 p(后置),先取 * p(自增前内容),再移动 p;(*p)++:++ 作用于p(指针指向的内容,后置),先取p,再让内容自增,p 不移动;*++p:++ 作用于指针 p(前置),先移动 p,再取 * p(自增后内容)。
2. 数组指针和指针数组的区别?(必考题)
答:① 本质不同:数组指针是指针(指向数组),指针数组是数组(存储指针);② 语法不同:数组指针int (*p)[3](括号抱指针),指针数组int *p[3](指针在数组里);③ 用途不同:数组指针用于二维数组操作,指针数组用于存储多个字符串 / 指针。
3. 函数指针的作用是什么?举一个应用场景。
答:① 作用:存储函数地址,实现函数的 “间接调用”,可将函数作为参数传递(回调函数),实现代码复用和灵活扩展;② 应用场景:通用排序函数(如 bubble_sort),通过传入不同的比较函数指针,实现任意类型数组的排序(int、char、float 等)。
4. 为什么函数指针可以省略解引用(*ptr)直接调用?
答:因为函数指针的本质是存储函数的入口地址,*ptr与ptr等价,均代表函数的入口地址,编译器在调用函数时,会自动解析函数地址,因此可省略解引用操作,直接用ptr(参数)调用。
5. 命令行参数 argv 是什么类型?为什么?
答:argv是指针数组,类型为char *argv[];原因:命令行参数是多个字符串,每个字符串的首地址是 char类型,argv 数组的每个元素存储一个字符串的首地址,因此是存储 char指针的数组,即指针数组。
更多推荐



所有评论(0)