点赞+关注私信领取全部代码资料~

一、* 和 ++ 作用于指针(优先级核心考点)

*(解引用)和++(自增)是指针操作中最常用的运算符,二者的优先级和结合性直接决定指针操作的结果,也是新手最易混淆的知识点,核心原则:*后置 ++ 优先级高于,前置 ++ 与*优先级相同,结合性从右到左 **。

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

关键说明

  1. p数组指针变量,简称数组指针;
  2. p的类型是int (*)[3],限制其只能指向 “包含 3 个 int 元素的数组”;
  3. 数组指针的内存大小:与所有指针一致,64 位系统中为 8 字节(与数组长度无关);
    printf("sizeof(p) = %zd\n", sizeof(p)); // 输出8(64位系统)
    
  4. 数组指针访问二维数组的方式(两种等价):
    // 方式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;
}
测试方法
  1. 编译:gcc test1.c -o a.out
  2. 运行:./a.out 896,输出:转换结果:896
  3. 异常测试:./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;
}
测试方法
  1. 编译:gcc test2.c -o a.out
  2. 运行 1:./a.out 100 + 9,输出:100 + 9 = 109
  3. 运行 2:./a.out 10 x 99,输出:10 x 99 = 990
  4. 运行 3:./a.out 50 - 15,输出:50 - 15 = 35
  5. 运行 4:./a.out 100 / 5,输出:100 / 5 = 20

四、函数指针(指向函数的指针,回调函数核心)

函数指针是存储函数地址的指针,函数的地址就是函数名,其类型由 “函数的返回值类型 + 参数类型” 决定,核心应用于回调函数、接口封装、函数表等场景,是 C 语言实现多态的基础,也是校招面试必考知识点。

4.1 函数指针的核心原理

核心前提

  1. 指针的本质是存储地址,函数在内存中也有固定的首地址(函数入口地址);
  2. 存储函数地址的指针,就是函数指针;
  3. 函数的地址 = 函数名(如strlen就是strlen函数的首地址)。

函数类型的判定(关键)

函数的类型由返回值类型参数类型(含个数、顺序) 共同决定,与函数名无关:

  • 示例:size_t strlen(const char *p);
    1. 函数名:strlen(函数地址);
    2. 返回值类型:size_t
    3. 参数类型:const char *
    4. 函数类型:size_t (const char *)
    5. 函数指针类型(存储该函数地址):size_t (*)(const char *)(括号不可省略)。

4.2 函数指针的定义与初始化(标准写法)

标准格式

// 语法:返回值类型 (*函数指针变量名)(参数类型列表);
// 示例:定义函数指针ptr,指向strlen函数
#include <string.h>

size_t (*ptr)(const char *) = strlen;

关键说明

  1. ptr函数指针变量,简称函数指针;
  2. ptr的类型是size_t (*)(const char *),限制其只能指向 “返回值为 size_t、参数为 const char*” 的函数;
  3. 函数指针的赋值:直接将函数名赋值给函数指针(函数名就是函数地址),无需加&&strlenstrlen等价);
    size_t (*ptr)(const char *) = strlen;  // 正确
    size_t (*ptr)(const char *) = &strlen; // 正确,与上面等价
    

4.3 函数指针的核心特性(必记)

  1. 函数指针不做指针运算:函数指针的作用是指向函数、调用函数,其运算无实际意义,编译器通常不支持函数指针的+、-、++、--运算;
  2. *ptr == ptr:函数指针解引用*ptr与函数指针本身ptr等价,均代表函数地址,调用时可省略*
    // 以下两种调用方式完全等价,推荐写ptr("hello"),更简洁
    size_t len1 = (*ptr)("hello"); // 解引用后调用
    size_t len2 = ptr("hello");    // 直接调用,等价
    
  3. &strlenstrlen等价:函数名前加&,获取的仍是函数地址,与函数名本身无区别。

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(命令行第三个参数),均为只读;
  • 要求:① 先比较p1p2的大小(按 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));
  • 参数说明:
    1. arr:任意类型数组的起始地址(万能指针);
    2. nmemb:数组元素个数;
    3. size:每个数组元素的字节大小;
    4. 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 

六、核心知识点总结(必背)

  1. *++优先级:后置 ++ > ,前置 ++ 与同级(右结合),核心区分 “操作指针” 还是 “操作指针指向的内容”;
  2. 数组指针:int (*p)[3],指向整个数组,类型由数组元素类型和长度决定,运算能力 = 数组总大小,用于二维数组;
  3. 指针数组:char *p[10],数组元素是指针,最常用场景是存储多个字符串,灵活节省内存;
  4. 函数指针:size_t (*ptr)(const char *),存储函数地址(函数名),类型由返回值和参数类型决定,不做指针运算,核心用于回调函数;
  5. 万能指针void*:用于接收任意类型地址,需强制转换后使用,是通用函数(如 bubble_sort)的核心;
  6. 堆内存规范:动态开辟的内存(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)直接调用?

答:因为函数指针的本质是存储函数的入口地址,*ptrptr等价,均代表函数的入口地址,编译器在调用函数时,会自动解析函数地址,因此可省略解引用操作,直接用ptr(参数)调用。

5. 命令行参数 argv 是什么类型?为什么?

答:argv指针数组,类型为char *argv[];原因:命令行参数是多个字符串,每个字符串的首地址是 char类型,argv 数组的每个元素存储一个字符串的首地址,因此是存储 char指针的数组,即指针数组。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐