当前位置: 首页 > news >正文

Day 10:C语言指针终极进阶:指针运算、数组指针、指针数组、函数指针(全网最细,面试必刷,含完整实战)

本文是C语言指针系列的终极收官篇,聚焦指针学习的四大核心难点——`*`与`++`混合运算、数组指针、指针数组、函数指针,结合底层内存原理、优先级拆解、多场景代码示例、易错点避坑、面试高频考点,以及命令行参数实战、作业完整实现(含注释+测试用例),补充拓展知识和工程实战技巧,内容深度、丰富度、排版完全适配CSDN爆款博客标准。无论是C语言进阶学习、校招笔试面试,还是工程开发中指针的灵活运用,本文都能提供全面、细致的指导,堪称“指针终极指南”。

本文适合:C语言入门后进阶学习者、期末复习学生、校招面试备考者、需要巩固指针基础的工程开发者,建议收藏备用,反复研读。

一、`*` 和 `++` 作用于指针(面试必考,易错点拉满)

`*`(解引用运算符)和 `++`(自增运算符)作用于指针时,是C语言初学者最容易踩坑的场景,核心矛盾在于「优先级」和「结合性」的混淆,很多人因理解偏差写出bug,甚至面试时直接翻车。本节将从优先级、结合性、执行步骤、代码示例、易错对比五个维度,彻底讲透每一种组合的本质。

核心前提(必记)

1. `*` 和 `++` 均为「单目运算符」,二者优先级相同

2. 单目运算符的「结合性为自右向左」(从右往左依次结合);

3. 后置`++`(`p++`):先使用,后自增(先取变量当前值,再执行自增操作);

4. 前置`++`(`++p`):先自增,后使用(先执行自增操作,再取变量自增后的值);

5. 关键区分:`++`作用于「指针变量本身」,还是「指针指向的内容」。

1.1 `*p++`(最常用,高频考点)

解析

结合优先级和结合性,`*p++` 等价于 `*(p++)`(括号可加可不加,不改变执行逻辑)。核心逻辑:`++` 作用于指针变量`p`(后置`++`),`*` 作用于`p`自增前的地址。

执行步骤(分3步,清晰无歧义)

1. 先取指针`p`当前指向的地址,通过`*`解引用,得到表达式的结果(即`p`当前指向的值);

2. 指针变量`p`执行自增操作(`p = p + 1`),地址向后偏移(偏移量由`p`的类型决定,如`int*`偏移4字节,`char*`偏移1字节);

3. 最终:表达式结果是`p`自增前指向的值,`p`本身的地址发生偏移(指向了下一个同类型元素)。

代码示例(带内存图解思路)

#include <stdio.h> int main() { int arr[] = {10, 20, 30, 40}; int *p = arr; // p指向arr[0],地址假设为0x00000000(int*偏移4字节) // 执行*p++ int res = *p++; printf("表达式结果res = %d\n", res); // 输出10(p自增前指向arr[0]的值) printf("p的地址:%p\n", p); // 输出0x00000004(p自增后指向arr[1]) return 0; } ​

易错点

误区:认为`*p++`是“先解引用,再让指向的值自增”——错误!`++`作用的是指针`p`,不是`*p`,指向的值不会改变,改变的是指针的地址。

1.2 `*(p++)`(与`*p++`完全等价)

解析

括号的作用是“明确结合顺序”,但由于`*`和`++`优先级相同、结合性自右向左,`*(p++)`和`*p++`的执行逻辑完全一致,没有任何区别。

补充:括号仅在“改变优先级”时有用,此处括号不改变任何执行顺序,仅提升代码可读性(建议新手加上,避免混淆)。

代码示例(验证等价性)

#include <stdio.h> int main() { int arr[] = {10, 20, 30}; int *p1 = arr; int *p2 = arr; int res1 = *p1++; // 无括号 int res2 = *(p2++); // 有括号 printf("res1 = %d, res2 = %d\n", res1, res2); // 均输出10 printf("p1地址:%p, p2地址:%p\n", p1, p2); // 均指向arr[1],地址相同 return 0; } ​

1.3 `(*p)++`(重点区分,易错)

解析

括号改变了优先级:先执行`*p`(解引用),再执行`++`(自增)。核心逻辑:`++`作用于`*p`(指针指向的内容),而非指针`p`本身。

执行步骤

1. 括号优先,先对`p`解引用,得到`*p`(指向的值);

2. 对`*p`执行后置自增(`*p = *p + 1`),表达式的结果是「自增前的值」;

3. 指针`p`本身的地址不发生任何变化(依旧指向原来的元素)。

代码示例(对比`*p++`)

#include <stdio.h> int main() { int arr[] = {10, 20, 30}; int *p = arr; // p指向arr[0],值为10 int res = (*p)++; printf("表达式结果res = %d\n", res); // 输出10(自增前的值) printf("*p = %d\n", *p); // 输出11(指向的值自增1) printf("p的地址:%p\n", p); // 地址不变,仍指向arr[0] return 0; } ​

面试考点

高频提问:`*p++` 和 `(*p)++` 的区别?(核心回答:前者改变指针地址,后者改变指向的值;表达式结果均为自增前的值)

1.4 `++*p`(前置自增,易错)

解析

结合性自右向左,`++*p` 等价于 `++(*p)`。核心逻辑:`++`作用于`*p`(指向的内容),且是前置自增——先自增,后取值。

执行步骤

1. 先对`p`解引用,得到`*p`(指向的值);

2. 对`*p`执行前置自增(`*p = *p + 1`);

3. 表达式的结果是「自增后的值」,指针`p`的地址不变。

代码示例(对比`(*p)++`)

#include <stdio.h> int main() { int arr[] = {10, 20, 30}; int *p = arr; int res1 = (*p)++; // 后置自增,结果为自增前的值 int res2 = ++*p; // 前置自增,结果为自增后的值 printf("res1 = %d, res2 = %d\n", res1, res2); // 输出10, 12 printf("*p = %d\n", *p); // 输出12 return 0; } ​

1.5 `*++p`(前置自增,指针先移)

解析

结合性自右向左,`*++p` 等价于 `*(++p)`。核心逻辑:`++`作用于指针`p`(前置自增),`*`作用于`p`自增后的地址。

执行步骤

1. 先对指针`p`执行前置自增(`p = p + 1`),地址向后偏移;

2. 对偏移后的`p`解引用,得到表达式的结果(自增后指向的值);

3. 最终:指针`p`地址改变,表达式结果是偏移后指向的值。

代码示例(对比`*p++`)

#include <stdio.h> int main() { int arr[] = {10, 20, 30}; int *p1 = arr; int *p2 = arr; int res1 = *p1++; // 后置自增,结果10,p1指向arr[1] int res2 = *++p2; // 前置自增,p2先指向arr[1],结果20 printf("res1 = %d, res2 = %d\n", res1, res2); // 输出10, 20 printf("p1地址:%p, p2地址:%p\n", p1, p2); // 均指向arr[1],地址相同 return 0; } ​

1.6 总结表格(面试必背,一目了然)

表达式++作用对象执行顺序表达式结果指针p是否偏移指向的值是否改变
*p++指针p(后置)先取值,后p自增p自增前指向的值
*(p++)指针p(后置)先取值,后p自增p自增前指向的值
(*p)++指向的值*p(后置)先取值,后*p自增*p自增前的值
++*p指向的值*p(前置)先*p自增,后取值*p自增后的值
*++p指针p(前置)先p自增,后取值p自增后指向的值

二、数组指针(指向数组的指针,行指针,适配二维数组)

数组指针是指针的重要应用场景,也是面试高频考点,很多人会把它和“指针数组”混淆(后续会详细区分)。核心记住:数组指针是一个指针,它指向的是一整个数组,而非数组的单个元素,常用于操作二维数组。

2.1 本质与核心定义

数组指针 → 指针变量,专门用于存储「数组的地址」,指向的是“一整个数组”,而非单个元素。

语法格式(必须加括号,否则会变成指针数组):

// 格式:元素类型 (*指针名)[数组长度]; int (*p)[3]; // p是数组指针,指向包含3个int类型元素的数组 ​

关键:括号`()`的作用是提升`p`的优先级,确保`p`先被定义为指针,再结合`[3]`表示“指向包含3个int的数组”。

2.2 数组指针的类型(结合二维数组讲解)

以二维数组为例,彻底理解数组指针的类型:

int arr[2][3] = {{1,2,3}, {4,5,6}}; ​

二维数组`arr`的内存布局(连续存储):

0x00000000:1(arr[0][0]) → 0x00000004:2(arr[0][1]) → 0x00000008:3(arr[0][2]) → 0x0000000C:4(arr[1][0]) → 0x00000010:5(arr[1][1]) → 0x00000014:6(arr[1][2])

核心分析

1. 二维数组名`arr`的本质:`arr`代表「二维数组第一行的地址」,而第一行本身是一个“包含3个int的一维数组”(`int [3]`类型);

2. `arr[0]`的本质:`arr[0]`是第一行一维数组的数组名,代表「第一行首元素的地址」(即`&amp;arr[0][0]`),类型是`int *`;

3. `&amp;arr[0]`的本质:对第一行一维数组取地址,得到的是「整个第一行数组的地址」,类型是`int (*)[3]`(这就是数组指针的类型);

4. 结论:`arr` 和 `&amp;arr[0]` 地址相同,但类型不同(`arr` 类型是`int (*)[3]`,`arr[0]` 类型是`int *`)。

2.3 数组指针的运算能力(偏移规律)

指针的运算能力(偏移步长)遵循核心公式:指针+1 偏移字节数 = sizeof(去掉一个*之后的类型)

以数组指针`int (*p)[3]`为例:

1. 去掉一个`*`,剩余类型是`int [3]`(包含3个int的数组);

2. 偏移步长 = sizeof(int [3]) = 3 * 4 = 12 字节(32位系统,int占4字节);

3. 核心规律:`p + 1` 会直接跳过一整个行(3个int元素),指向二维数组的下一行。

代码示例(数组指针操作二维数组)

#include <stdio.h> int main() { int arr[2][3] = {{1,2,3}, {4,5,6}}; int (*p)[3] = arr; // 数组指针p指向二维数组第一行(类型匹配) // 访问第一行元素(p指向第一行,*p等价于arr[0],类型int*) printf("第一行:%d %d %d\n", *(*p), *(*p + 1), *(*p + 2)); // 1 2 3 // p+1 指向第二行 p++; // 访问第二行元素 printf("第二行:%d %d %d\n", *(*p), *(*p + 1), *(*p + 2)); // 4 5 6 return 0; } ​

2.4 数组指针的应用场景(工程实战)

数组指针最核心的应用是「二维数组作为函数参数传递」,避免数组整体拷贝(节省内存),同时能正确访问二维数组的每一行。

错误写法(无法正确访问二维数组):

// 错误:形参是int*,无法接收二维数组首地址(类型不匹配) void print_arr(int *arr, int row, int col) { // 无法正确访问arr[i][j] } ​

正确写法(用数组指针作为形参):

#include <stdio.h> ​ // 形参p是数组指针,指向包含col个int的数组 void print_arr(int (*p)[3], int row, int col) { for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { // p+i 指向第i行,*(p+i) 是第i行首地址,*(p+i)+j 是第i行第j个元素地址 printf("%d ", *(*(p + i) + j)); } printf("\n"); } } ​ int main() { int arr[2][3] = {{1,2,3}, {4,5,6}}; print_arr(arr, 2, 3); // 实参是二维数组名,类型匹配 return 0; } ​

2.5 易错点:数组指针 vs 指针数组(面试高频区分)

很多人混淆二者,核心区分:看括号位置,判断是“指针”还是“数组”,用一句话记住:

「括号包着指针名 → 数组指针(是指针);括号不包指针名 → 指针数组(是数组)」

类型定义格式本质类型示例核心区别
数组指针int (*p)[3]指针(指向数组)int (*)[3]p是指针,存储数组地址,+1跳一行
指针数组int *p[3]数组(存放指针)int *[3]p是数组,存放3个int*指针,+1跳一个指针

三、指针数组(存放指针的数组,核心用于存储多字符串)

指针数组的本质是「数组」,和普通数组的区别在于:普通数组存储的是数据(int、char等),而指针数组存储的是「指针变量」(地址)。由于字符指针可以存储字符串首地址,所以指针数组最常用的场景是「存储多个字符串」(比二维字符数组更节省内存)。

3.1 本质与定义

指针数组 → 数组,数组的每一个元素都是「指针变量」,可以存储任意类型的地址(int*、char*、void*等)。

语法格式(无括号,指针名直接跟数组下标):

// 格式:元素类型 *数组名[数组长度]; char *str[10]; // str是指针数组,包含10个char*类型的指针(每个指针可存字符串首地址) int *arr[5]; // arr是指针数组,包含5个int*类型的指针(每个指针可存int变量地址)

关键:没有括号,`*` 优先级低于 `[]`,所以`str`先被定义为数组,再结合`char *`表示“数组的每个元素是char*指针”。

3.2 指针数组的内存特点(优势)

以存储多个字符串为例,对比「指针数组」和「二维字符数组」的内存占用:

// 1. 二维字符数组(固定长度,浪费内存) char str1[3][10] = {"hello", "world", "csdn"}; // 占用内存:3*10=30字节,即使字符串长度不足10,也会占用10字节 // 2. 指针数组(灵活,节省内存) char *str2[3] = {"hello", "world", "csdn"}; // 占用内存:3*8=24字节(64位系统,指针占8字节),每个指针指向字符串常量(按需占用内存)

优势:指针数组存储多个字符串时,无需提前指定固定长度,每个字符串按需占用内存,比二维字符数组更灵活、更节省空间。

3.3 核心应用场景(实战重点)

指针数组最核心的应用有两个:

1. 存储多个字符串(工程开发中最常用);

2. 接收命令行参数(`argv` 本质就是一个`char *`类型的指针数组)。

应用1:存储多个字符串

#include <stdio.h> int main() { // 指针数组存储3个字符串,每个元素是字符串首地址 char *str[] = {"C语言", "指针进阶", "CSDN博客"}; // 遍历指针数组,访问每个字符串 for (int i = 0; i < 3; i++) { printf("str[%d] = %s\n", i, str[i]); } return 0; }

输出结果:

str[0] = C语言

str[1] = 指针进阶

str[2] = CSDN博客

应用2:接收命令行参数(`argv` 详解)

C语言main函数的标准写法(支持命令行参数):

int main(int argc, char *argv[]);

参数解析:

- `argc`:命令行参数的个数(包含程序名本身,最少为1);

- `argv`:指针数组,`char *argv[]`,每个元素是一个命令行参数的字符串首地址;

- `argv[0]`:程序名本身(如`./a.out`);

- `argv[1]`:第一个命令行参数,`argv[2]`:第二个命令行参数,以此类推。

3.4 实战练习(结合命令行参数,面试常考)

本节两个练习均基于命令行参数实现,补充完整代码、注释、测试用例,直接可运行。

练习1:将 `argv[1]` 字符串转换为整型数

需求:运行程序时,传入一个字符串形式的数字(如`./a.out 896`),程序将其转换为整型数并输出。

核心知识点:`atoi` 函数(`stdlib.h` 头文件),用于将字符串转整型;命令行参数`argv[1]` 是字符串首地址。

#include <stdio.h> #include <stdlib.h> // atoi函数所需头文件 int main(int argc, char *argv[]) { // 校验参数个数:至少需要传入1个命令行参数(argv[1]) if (argc < 2) { printf("请传入一个字符串形式的数字!\n"); printf("用法:./a.out 数字字符串\n"); return -1; // 异常退出 } // atoi(argv[1]):将argv[1]指向的字符串转为整型 int num = atoi(argv[1]); // 输出结果 printf("传入的字符串:%s\n", argv[1]); printf("转换后的整型数:%d\n", num); return 0; }

测试用例:

1. 编译:`gcc test.c -o a.out`

2. 运行:`./a.out 896`

3. 输出:

传入的字符串:896

转换后的整型数:896

拓展:若传入非数字字符串(如`./a.out abc`),`atoi` 会返回0,可添加参数校验逻辑(可选)。

练习2:实现四则运算(命令行参数传入运算数和运算符)

需求:运行程序时,传入两个运算数和一个运算符(如`./a.out 10 + 9`、`./a.out 10 x 99`),程序计算结果并输出。

核心知识点:命令行参数解析、`atoi` 转换、`switch` 判断运算符。

#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { // 校验参数个数:需要传入3个命令行参数(运算数1、运算符、运算数2),总个数为4 if (argc != 4) { printf("参数个数错误!\n"); printf("用法:./a.out 运算数1 运算符 运算数2\n"); printf("支持运算符:+(加)、-(减)、x(乘)、/(除)\n"); return -1; } // 1. 将命令行参数中的字符串转为整型(运算数) int a = atoi(argv[1]); int b = atoi(argv[3]); int res = 0; // 存储运算结果 // 2. 判断运算符,执行对应运算 // argv[2]是字符串,取第一个字符(argv[2][0])作为运算符 switch (argv[2][0]) { case '+': res = a + b; break; case '-': res = a - b; break; case 'x': // 用x表示乘法(*在命令行中有特殊含义,避免冲突) res = a * b; break; case '/': // 校验除数不为0 if (b == 0) { printf("错误:除数不能为0!\n"); return -1; } res = a / b; break; default: printf("无效运算符!支持:+、-、x、/\n"); return -1; } // 3. 输出运算结果 printf("%d %c %d = %d\n", a, argv[2][0], b, res); return 0; }

测试用例:

1. 运行:`./a.out 10 + 9` → 输出:10 + 9 = 19

2. 运行:`./a.out 10 x 99` → 输出:10 x 99 = 990

3. 运行:`./a.out 100 - 25` → 输出:100 - 25 = 75

4. 运行:`./a.out 50 / 5` → 输出:50 / 5 = 10

易错点:命令行中`*`是通配符,不能直接作为运算符,所以用`x`表示乘法。

四、函数指针(指向函数的指针,回调函数基础)

函数指针是C语言中最灵活、最强大的特性之一,也是面试中的“重头戏”。核心记住:函数指针是一个指针,它存储的是函数的入口地址,通过函数指针可以间接调用函数,常用于回调函数、通用算法(如通用排序)、模块化开发。

4.1 核心概念(必懂)

1. 函数的地址:C语言中,函数名本身就是函数的入口地址(和数组名类似),无需用`&amp;`取地址(`&amp;函数名` 和 `函数名` 等价);

2. 函数的类型:函数的类型由「返回值类型」和「参数列表」共同决定,与函数名无关;

3. 函数指针的作用:存储函数地址,间接调用函数,实现“函数作为参数传递”(回调函数)。

4.2 函数指针的定义语法(重点,易错)

语法格式(必须加括号,否则会变成函数声明):

// 格式:返回值类型 (*函数指针名)(参数列表); // 示例:对应函数 size_t strlen(const char *p); size_t (*ptr)(const char *); // ptr是函数指针,指向返回值为size_t、参数为const char*的函数

关键解析:

- 括号`()`提升`ptr`的优先级,确保`ptr`先被定义为指针;

- 指针名后面的`(const char *)` 是函数的参数列表;

- 最前面的`size_t` 是函数的返回值类型;

- 函数指针的类型必须和它指向的函数类型完全匹配(返回值、参数列表一致)。

4.3 函数指针的赋值与调用(实战示例)

以标准库函数`strlen`为例,演示函数指针的赋值、调用过程:

#include <stdio.h> #include <string.h> // strlen函数所需头文件 int main() { // 1. 定义函数指针ptr,类型匹配strlen函数 // strlen函数原型:size_t strlen(const char *p); size_t (*ptr)(const char *); // 2. 赋值:让ptr指向strlen函数(函数名就是地址) ptr = strlen; // 等价写法:ptr = &strlen;(&可加可不加,效果一致) // 3. 调用函数指针(三种写法,均等价) // 写法1:直接用函数指针调用(最常用) size_t len1 = ptr("Hello C语言"); // 写法2:解引用函数指针调用(*ptr和ptr等价) size_t len2 = (*ptr)("Hello C语言"); // 写法3:解引用多次,仍等价(无意义,但合法) size_t len3 = (**ptr)("Hello C语言"); // 输出结果(三者一致) printf("len1 = %zu, len2 = %zu, len3 = %zu\n", len1, len2, len3); // 均输出8 return 0; }

核心结论:函数指针的调用,`ptr()`、`(*ptr)()`、`(**ptr)()` 完全等价,因为函数名本身就是地址,解引用不影响调用逻辑。

4.4 函数指针的核心特性(面试必记)

1. 函数指针不能进行 `+1、-1` 等运算:函数地址是固定的,偏移后无意义,编译器会报错或警告;

2. `*ptr == ptr`:函数指针解引用和不解引用完全等价,均可调用函数;

3. `&amp;函数名 == 函数名`:函数名本身就是地址,取地址操作不改变其值;

4. 函数指针的类型必须和指向的函数类型完全匹配(返回值、参数个数、参数类型一致),否则会出现未定义行为。

4.5 函数指针的应用场景(工程实战)

函数指针最核心的应用是「回调函数」——将一个函数作为参数,传递给另一个函数,在另一个函数中通过函数指针调用该函数,实现灵活的逻辑扩展。

示例:用函数指针实现简单的回调函数(加法、减法回调):

#include <stdio.h> // 回调函数类型:返回int,参数为两个int typedef int (*calc_func)(int, int); // 加法函数(符合回调函数类型) int add(int a, int b) { return a + b; } // 减法函数(符合回调函数类型) int sub(int a, int b) { return a - b; } // 通用计算函数:接收两个运算数和一个回调函数,通过函数指针调用回调函数 int calc(int a, int b, calc_func func) { return func(a, b); // 调用回调函数 } int main() { // 调用calc,传入add函数作为回调(加法) int res1 = calc(10, 5, add); printf("10 + 5 = %d\n", res1); // 输出15 // 调用calc,传入sub函数作为回调(减法) int res2 = calc(10, 5, sub); printf("10 - 5 = %d\n", res2); // 输出5 return 0; }

拓展:后面的“通用冒泡排序”作业,就是函数指针回调的典型应用。

五、作业【完整版代码实现(含注释+测试用例+易错点说明)】

本节所有作业均实现完整代码,补充详细注释、测试用例、易错点分析,直接可编译运行,同时贴合工程开发规范(如动态内存释放、参数校验)。

作业:将命令行第二个参数字符串倒序,生成一个新的字符串并返回

函数原型:`char *reverse_string(const char *ptr);`

参数说明:`ptr` 指向原来的字符串,返回值是新字符串的地址(动态开辟,需手动释放)。

实现思路

1. 先计算原字符串长度(`strlen`);

2. 动态开辟新字符串空间(长度=原长度+1,预留`\0`空间);

3. 从原字符串末尾开始,逐字符拷贝到新字符串;

4. 给新字符串末尾添加`\0`(字符串结束标志);

5. 返回新字符串地址,注意:调用者需手动`free`释放内存,避免内存泄漏。

完整代码

#include <stdio.h> #include <stdlib.h> #include <string.h> /** * @brief 将字符串倒序,生成新字符串并返回 * @param ptr 指向原字符串的指针(不可修改) * @return 新字符串的地址(动态开辟,需手动free) */ char *reverse_string(const char *ptr) { // 校验参数:ptr为NULL,直接返回NULL(避免野指针) if (ptr == NULL) { printf("错误:传入的字符串指针为NULL!\n"); return NULL; } // 1. 计算原字符串长度 int len = strlen(ptr); // 2. 动态开辟新字符串空间:len+1(预留\0的位置) char *new_str = (char *)malloc(len + 1); // 校验内存开辟是否成功 if (new_str == NULL) { perror("malloc fail"); // 打印错误信息 return NULL; } // 3. 倒序拷贝:从原字符串末尾开始,逐字符拷贝到新字符串 char *start = new_str; // 保存新字符串起始地址(用于返回) for (int i = len - 1; i >= 0; i--) { *new_str = ptr[i]; new_str++; // 新字符串指针后移 } // 4. 给新字符串添加结束标志\0 *new_str = '\0'; // 5. 返回新字符串起始地址 return start; } // 测试函数(结合命令行参数) int main(int argc, char *argv[]) { // 校验命令行参数个数:至少传入1个参数字符串(argv[1]) if (argc < 2) { printf("请传入一个字符串!\n"); printf("用法:./a.out 字符串\n"); return -1; } // 调用倒序函数 char *reverse_str = reverse_string(argv[1]); if (reverse_str == NULL) { return -1; // 开辟内存失败,退出 } // 输出结果 printf("原字符串:%s\n", argv[1]); printf("倒序后:%s\n", reverse_str); // 手动释放动态开辟的内存,避免内存泄漏 free(reverse_str); reverse_str = NULL; // 避免野指针 return 0; }

测试用例

1. 编译:`gcc reverse.c -o reverse`

2. 运行:`./reverse hello` → 输出:原字符串:hello,倒序后:olleh

3. 运行:`./reverse CSDN` → 输出:原字符串:CSDN,倒序后:NDSC

易错点

1. 忘记给新字符串添加`\0`,导致输出乱码;

2. 动态开辟内存后未校验是否成功(`new_str == NULL`);

3. 调用函数后未`free`内存,导致内存泄漏;

4. 未校验`ptr`是否为NULL,导致野指针访问。

http://www.jsqmd.com/news/677397/

相关文章:

  • 别再手写Comparator了!用Java 8的comparingInt()让对象排序代码清爽三倍
  • 机器人应用-楼宇室内巡逻
  • 别再死记公式了!从FOC磁场控制本质出发,彻底搞懂ST电机库电角度校准为什么是-90度
  • 5G NR PDSCH资源映射实战:手把手教你理解VRB到PRB的交织与非交织(附38.211协议解读)
  • 进口品质,国产价格:普拉勒CO2培养箱如何重新定义实验室“性价比”? - 品牌推荐大师
  • 海南鑫典雅广告:海南显示屏安装电话 - LYL仔仔
  • PPOCRLabel标注结果总出错?试试这3个模型调优和标注技巧,提升自动标注准确率
  • 载誉前行!柠萌旅行荣登国家旅业「品质旅行商 100 佳」榜单 - 速递信息
  • 云端云手机具体是指什么
  • 安全帽试验机哪家强?源头厂家与专业制造商实力对比 - 品牌推荐大师
  • 别再只跑仿真了!聊聊Formal Verification(形式验证)在芯片设计中的那些“高光时刻”
  • Beyond Compare 5密钥生成器:轻松解决评估期过期的专业工具
  • 从电商订单到安全日志:手把手教你用Kibana 7.17搭建你的第一个业务监控仪表板
  • 株洲旺成搬家:靠谱做株洲厂房搬迁的企业 - LYL仔仔
  • Android开发避坑:华为手机改了分辨率,你的App布局就乱了?一个BaseActivity搞定
  • 别再搞错了!ERA5-Land小时数据里的辐射值,原来不是你想的那个‘瞬时值’
  • 如何高效实现OFD转PDF:Ofd2Pdf专业转换工具实战指南
  • 破解消防泵控制柜三大痛点:DBK三位一体智能合规方法论如何保障验收与运维? - 速递信息
  • 网盘下载加速终极指南:八大平台直链获取完整解决方案
  • FPGA实战:手把手教你用DDS生成1MHz正弦波(附完整代码)
  • 手把手教你用MATLAB跑通ESKF:从IMU原始数据到3D姿态可视化(附完整数据集)
  • 数字化转型浪潮下的西安样本:从“摩高互动”看企业级技术服务的破局之道
  • 2026年工程造价专业公司品牌推荐:数控技术专业/现代物流管理专业/计算机网络技术专业/工业互联网技术专业/现代移动通信技术专业 - 品牌策略师
  • 从画线到策略:用Python复现MT5 ZigZag算法,并实战检验其交易信号可靠性
  • Python老师福音:用xlwings+requests自动抓取iCode学生刷题数据,解放双手
  • 别再手动跑脚本了!用Docker Compose 5分钟搞定Apache DolphinScheduler 3.1.3部署
  • 15分钟精通OCAT:黑苹果OpenCore配置的终极可视化方案
  • 2026年山东广告投流与短视频代运营深度横评:极迅传媒、腾讯广告授权商对比指南 - 年度推荐企业名录
  • ComfyUI Impact Pack深度解析:AI图像增强的终极指南与高级技巧
  • Markmap架构深度分析:基于D3.js的思维导图可视化引擎技术实现