暑期自学嵌入式——Day05补充(C语言阶段)
接续上文:暑期自学嵌入式——Day05(C语言阶段)-CSDN博客
主页点关注不迷路哟。你的点赞、收藏,一键三连,是我持续更新的动力哟!!!
主页:
一位搞嵌入式的 genius-CSDN博客https://blog.csdn.net/m0_73589512?spm=1010.2135.3001.5343
Day05(补充)
字符数组和字符串:字符串逆序输出详解
一、字符串逆序输出的实现思路与方法
1. 两种核心实现思路
实现方式 | 核心逻辑 | 优缺点分析 |
---|---|---|
仅逆序显示(不修改原串) | 不改变字符串存储顺序,仅从最后一个有效字符倒序打印到第一个字符 | 优点:简单直接,不破坏原数据;缺点:未真正修改字符串(仅输出层面逆序) |
原地逆序(修改原串后显示) | 通过交换字符位置,将原字符串改为逆序(如 "abc"→"cba"),再正常打印 | 优点:真正实现字符串逆序存储;缺点:需修改原数据,逻辑稍复杂 |
2. 基础实现:仅逆序显示(不修改原串)
(1)核心步骤
-
获取输入:用
gets
或scanf
读取字符串(gets
支持空格,scanf
遇空格停止)。 -
计算有效长度:用
strlen
获取字符串实际长度(不含'\0'
)。 -
倒序循环输出:从
len-1
(最后一个字符下标)循环到0
,逐个输出字符。
(2)示例代码
#include <stdio.h> #include <string.h> #define N 20 // 数组大小 int main() {char arr[N];printf("Please input a string: ");gets(arr); // 获取输入(支持空格,如"hello world") int len = strlen(arr); // 计算有效长度(不含'\0')printf("逆序输出:");// 从最后一个字符(len-1)倒序输出到第一个(0)for (int i = len - 1; i >= 0; i--) {putchar(arr[i]); // 逐个输出字符}putchar('\n'); // 手动换行return 0; }
(3)关键技巧
-
strlen
vssizeof
:-
strlen(arr)
:返回字符串有效长度(仅'\0'
之前的字符数,如 "hello" 返回 5)。 -
sizeof(arr)
:返回数组总空间(定义为N=20
,则返回 20,包含未使用的空间)。 -
必须用
strlen
:避免打印未初始化的垃圾字符(如数组定义为 20,但输入仅 5 个字符)。
-
3. 进阶实现:原地逆序(修改原串)
(1)核心算法:双指针法
-
指针定义:
-
i
:从头部开始(初始i=0
)。 -
j
:从尾部开始(初始j = strlen(arr) - 1
)。
-
-
操作逻辑:交换
arr[i]
和arr[j]
,然后i++
、j--
,直到i >= j
(两指针相遇,所有字符交换完成)。
(2)示例代码
#include <stdio.h> #include <string.h> #define N 20 int main() {char arr[N];printf("Please input a string: ");gets(arr);int len = strlen(arr); // 双指针交换(原地逆序)int i = 0; // 头指针int j = len - 1; // 尾指针while (i < j) { // 两指针未相遇时循环char temp = arr[i]; // 临时变量辅助交换arr[i] = arr[j];arr[j] = temp;i++; // 头指针后移j--; // 尾指针前移} // 输出逆序后的字符串printf("逆序后的字符串:%s\n", arr); // 此时arr已变为逆序return 0; }
(3)过程演示(以输入 "apple" 为例)
步骤 | i | j | 交换前 | 交换后 | 操作说明 |
---|---|---|---|---|---|
1 | 0 | 4 | "apple" | "eppla" | 交换a[0] ('a')和a[4] ('e') |
2 | 1 | 3 | "eppla" | "eplpa" | 交换a[1] ('p')和a[3] ('l') |
3 | 2 | 2 | "eplpa" | (停止) | i >= j ,循环终止 |
最终结果 | - | - | - | "elppa" | 完成逆序 |
二、输入函数的选择与注意事项
1. gets
、scanf
、fgets
对比
函数 | 特点 | 安全隐患 / 注意事项 |
---|---|---|
gets(arr) | 读取整行字符串(支持空格),直到换行符为止(自动忽略换行符,添加'\0' ) | 无长度限制,若输入超过数组大小会导致越界(已弃用,编译器会警告) |
scanf("%s", arr) | 遇空格、换行等分隔符停止读取,自动添加'\0' | 无法读取带空格的字符串(如 "hello world" 只能读到 "hello") |
fgets(arr, N, stdin) | 读取最多N-1 个字符(预留'\0' ),保留换行符,更安全 | 需手动处理换行符(如arr[strcspn(arr, "\n")] = '\0' 去除换行);推荐使用 |
2. 输入函数使用建议
-
读取带空格的字符串:优先用
fgets
(安全),避免gets
(有溢出风险)。 -
读取无空格的字符串:
scanf("%s", arr)
更简洁(注意限制输入长度,如scanf("%19s", arr)
,数组大小为 20 时)。 -
示例(
fgets
替代gets
):#include <stdio.h> #include <string.h> int main() {char arr[20];printf("请输入字符串(支持空格):");fgets(arr, 20, stdin); // 最多读19个字符(留1个给'\0')// 去除fgets保留的换行符(若有)arr[strcspn(arr, "\n")] = '\0'; // strcspn计算到'\n'的长度 printf("你输入的是:%s\n", arr);return 0; }
三、知识小结
知识点 | 核心内容 | 考试重点 / 易混淆点 | 难度系数 |
---|---|---|---|
逆序显示(方法 1) | 倒序循环输出(i从len-1到0 ),不修改原串;依赖strlen 获取有效长度 | sizeof (总空间)与strlen (有效长度)的区别;避免打印未初始化字符 | ⭐⭐⭐ |
原地逆序(方法 2) | 双指针法交换字符(i=0 与j=len-1 交换,直到i>=j );需临时变量辅助 | 循环终止条件(i < j 而非i <= j ,避免重复交换);奇 / 偶长度字符串的处理(自动兼容) | ⭐⭐⭐⭐ |
输入函数对比 | gets 支持空格但不安全;scanf 遇空格停止;fgets 安全且支持空格 | gets 的溢出风险;fgets 保留换行符的处理方法 | ⭐⭐ |
字符串长度计算 | strlen 返回有效长度(不含'\0' ),需包含<string.h> ;也可手动循环计算 | 手动计算长度的循环逻辑(while(arr[i] != '\0') i++ ) | ⭐⭐ |
数组越界预防 | 输入时限制长度(如fgets 的N 参数、scanf 的%19s );初始化数组避免垃圾值 | gets 无长度限制导致的溢出;未初始化数组的随机值打印 | ⭐⭐⭐ |
四、编程技巧
-
调试技巧: 逆序逻辑出错时,可打印中间过程(如双指针交换时的
i
、j
值和数组内容):while (i < j) {printf("交换前:i=%d, j=%d, arr=%s\n", i, j, arr);// 交换代码printf("交换后:i=%d, j=%d, arr=%s\n", i, j, arr); }
-
边界条件处理: 考虑特殊输入(如空字符串
""
、单字符"a"
),确保程序不崩溃:-
空字符串:
strlen
返回 0,循环不执行(正确)。 -
单字符:
i=0
,j=0
,i < j
不成立,循环不执行(无需交换,正确)。
-
-
函数封装: 将逆序逻辑封装为函数,提高复用性:
// 原地逆序函数 void reverseString(char* arr) {int len = strlen(arr);int i = 0, j = len - 1;while (i < j) {char temp = arr[i];arr[i] = arr[j];arr[j] = temp;i++;j--;} }
通过以上内容,可掌握字符串逆序的两种核心方法,理解输入函数的特性及安全用法,同时规避数组越界等常见问题。
字符串函数:从基础到应用
一、字符串函数概述
1. 核心概念
-
定义:C 语言标准库(
string.h
头文件)中封装的专用函数,用于简化字符串操作(如计算长度、拷贝、比较等)。 -
重要性:字符串是编程中最常用的数据形式之一,直接手写逻辑(如遍历计算长度)低效且易出错,函数封装可大幅提升开发效率。
-
使用前提:所有字符串函数必须通过
#include <string.h>
声明头文件,否则编译器会报错(“未声明的函数”)。
二、常用字符串函数详解
1. 字符串长度函数 strlen
(1)功能与原型
-
功能:计算字符串中 有效字符的个数(从首字符开始,直到遇到
'\0'
为止,不含'\0'
)。 -
函数原型:
size_t strlen(const char *s); // size_t是无符号整数类型(unsigned int)
-
参数
s
:字符串的首地址(字符数组名或字符串常量)。 -
返回值:有效字符数(无符号整数,如
"abc"
返回 3)。
-
(2)应用示例与解析
基础示例
#include <stdio.h> #include <string.h> int main() {// 示例1:显式包含'\0'的字符数组char s1[10] = {'A', '\0', 'B', 'C', '\0', 'D'};printf("s1长度:%zu\n", strlen(s1)); // 结果:1(遇到第一个'\0'即停止) // 示例2:字符串常量初始化(隐含'\0')char s2[] = "maker"; // 存储为'm','a','k','e','r','\0'printf("s2长度:%zu\n", strlen(s2)); // 结果:5(不含'\0')return 0; }
转义字符处理
转义字符(如 \t
、\n
)虽由多个字符组成(反斜杠 + 字母),但在字符串中视为 单个字符,strlen
计为 1。
// 示例:转义字符的长度计算 char s3[] = "\tab\n\v\w\e"; // 包含\t、\a、\n、\v、\w、\e(共6个字符) printf("s3长度:%zu\n", strlen(s3)); // 结果:6
特殊编码(十六进制 / 八进制)
字符串中可通过 \xhh
(十六进制)或 \ooo
(八进制)表示字符,strlen
计为 1 个字符。
// 示例:十六进制和八进制编码 char s4[] = "\x69\141"; // \x69是十六进制69(ASCII码105,对应'i');\141是八进制141(ASCII码97,对应'a') printf("s4内容:%s\n", s4); // 输出"ia" printf("s4长度:%zu\n", strlen(s4)); // 结果:2
(3)strlen
与 sizeof
的核心区别
对比项 | strlen(s) | sizeof(s) |
---|---|---|
本质 | 函数,计算 有效字符数(不含 '\0' ) | 运算符,计算 变量 / 类型的总内存字节数(含 '\0' 及未使用空间) |
示例(char s[] = "maker" ) | 返回 5('m','a','k','e','r' ) | 返回 6(5 个字符 + 1 个 '\0' ) |
示例(char s[10] = "maker" ) | 返回 5(有效字符不变) | 返回 10(数组总空间固定为 10 字节) |
计算时机 | 运行时计算(遍历字符串直到 '\0' ) | 编译时计算(已知类型 / 数组大小) |
2. 其他重要字符串函数(基础认知)
函数 | 功能 | 原型示例 | 关键注意事项 |
---|---|---|---|
strcpy | 将源字符串拷贝到目标数组(包括 '\0' ) | char* strcpy(char *dest, const char *src); | 目标数组需足够大(否则越界);源字符串必须以 '\0' 结尾 |
strcat | 将源字符串连接到目标字符串末尾(覆盖目标原 '\0' ,添加新 '\0' ) | char* strcat(char *dest, const char *src); | 目标数组需预留足够空间;源和目标不能重叠(如 strcat(s, s) 会出错) |
strcmp | 按 ASCII 码比较两个字符串(逐个字符对比,直到不同或 '\0' ) | int strcmp(const char *s1, const char *s2); | 返回值:s1 > s2 为正,s1 < s2 为负,相等为 0;不能用 == 直接比较字符串 |
三、知识小结
知识点 | 核心内容 | 考试重点 / 易混淆点 | 难度系数 |
---|---|---|---|
字符串函数基础 | 需包含 <string.h> ;封装高频操作(如 strlen 计算长度) | 忘记包含头文件导致的编译错误;函数参数类型(const char* 表示只读) | ⭐⭐ |
strlen 函数 | 计算有效字符数(不含 '\0' );转义字符、特殊编码均计为 1 个字符 | 与 sizeof 的区别(运行时 vs 编译时;有效字符 vs 总内存);遇到第一个 '\0' 即停止 | ⭐⭐⭐ |
转义字符与特殊编码 | \t 、\n 等转义字符计为 1;\x69 (十六进制)、\141 (八进制)对应单个字符 | 八进制 / 十六进制编码的转换(如 \141 对应 'a' );转义字符的计数规则 | ⭐⭐⭐⭐ |
其他函数(strcpy 等) | strcpy 拷贝、strcat 连接、strcmp 比较;均依赖 '\0' 作为终止标志 | strcpy 的越界风险;strcmp 的返回值判断(非 0 即不等,正 / 负表示大小) | ⭐⭐⭐ |
四、编程建议
-
strlen
使用注意:-
仅用于以
'\0'
结尾的字符串(字符数组无'\0'
时,strlen
会越界读取,结果随机)。 -
返回值是
size_t
(无符号),避免与有符号整数比较(如if (strlen(s) >= -1)
永远成立,因无符号不会为负)。
-
-
sizeof
与strlen
对比记忆:-
记口诀:
strlen
算 “内容长度”(有效字符),sizeof
算 “房子大小”(总内存)。 -
示例:
char s[10] = "abc"
→strlen=3
(内容),sizeof=10
(房子)。
-
-
函数学习方法: 先掌握
strlen
等基础函数的用法和原理,遇到未知函数(如strstr
查找子串)时,学会查阅 C 语言手册(如cplusplus.com),重点关注功能、参数、返回值和注意事项。
通过以上内容,可掌握 strlen
的核心用法及字符串函数的基础认知,明确 strlen
与 sizeof
的关键区别,为后续学习字符串拷贝、比较等函数奠定基础。
字符串函数:字符串拷贝函数strcpy
详解
一、strcpy
函数的基本介绍
1. 函数格式与核心功能
-
函数原型:
char *strcpy(char *dest, const char *src);
-
核心功能:将源字符串(
src
)完整拷贝到目标字符数组(dest
)中,包括字符串结束符'\0'
,最终目标数组成为源字符串的副本。 -
参数与返回值:
-
dest
:目标字符数组(必须是可修改的左值,如char arr[20]
,不能是字符串常量)。 -
src
:源字符串(可以是字符数组或字符串常量,如"hello"
,必须以'\0'
结尾)。 -
返回值:目标数组
dest
的首地址(方便链式调用,如printf("%s", strcpy(dest, src))
)。
-
2. 关键使用规则(必须遵守)
-
目标数组空间必须足够大: 需能容纳源字符串的所有字符(包括
'\0'
),否则会导致缓冲区溢出(覆盖相邻内存,程序可能崩溃或输出乱码)。-
示例:源字符串
"hello"
(长度 5,含'\0'
共 6 字节),目标数组至少需 6 字节(如char dest[6]
)。
-
-
源字符串必须以
'\0'
结尾:strcpy
通过'\0'
判断拷贝结束,若源字符串无'\0'
,会越界拷贝(直到意外遇到'\0'
)。 -
源和目标不能重叠: 如
strcpy(arr+2, arr)
(目标是源的一部分)会导致拷贝结果异常(标准未定义行为)。 -
数组名不能直接赋值: 错误写法:
dest = src;
(数组名是地址常量,不能被修改),必须用strcpy
逐字符拷贝。
二、strcpy
函数的使用示例与原理
1. 基础使用示例
#include <stdio.h> #include <string.h> int main() {char src[] = "hello world"; // 源字符串(含'\0'共12字节)char dest[20]; // 目标数组(空间足够大) // 拷贝src到deststrcpy(dest, src); // 输出结果(两者内容相同)printf("源字符串:%s(地址:%p)\n", src, src);printf("目标数组:%s(地址:%p)\n", dest, dest);return 0; }
-
输出结果: 源字符串和目标数组均输出
hello world
,证明拷贝完整(包括'\0'
)。
2. strcpy
的手动实现(理解底层原理)
strcpy
本质是通过循环逐字符拷贝,直到遇到源字符串的'\0'
并拷贝它。
// 手动实现strcpy功能(简化版) char* my_strcpy(char* dest, const char* src) {// 保存目标首地址(用于返回)char* original_dest = dest; // 逐字符拷贝(包括'\0')while (*src != '\0') {*dest = *src; // 拷贝当前字符dest++; // 目标指针后移src++; // 源指针后移}*dest = '\0'; // 拷贝结束符(关键!) return original_dest; // 返回目标首地址 }
-
核心逻辑: 循环拷贝每个字符(
*src
)到目标地址(*dest
),直到*src
为'\0'
,最后单独拷贝'\0'
(确保目标数组以'\0'
结尾)。
3. 常见错误与规避
(1)目标数组空间不足(高危)
char src[] = "this is a long string"; // 长度21(含'\0'共22字节) char dest[10]; // 空间不足(仅10字节) strcpy(dest, src); // 缓冲区溢出!覆盖相邻内存,程序可能崩溃
-
规避方法:
拷贝前检查目标数组长度是否 ≥ 源字符串长度 + 1(
strlen(src)+1
):
if (sizeof(dest) >= strlen(src) + 1) {strcpy(dest, src); // 安全拷贝 } else {printf("目标空间不足!\n"); }
(2)忘记拷贝'\0'
(手动实现时)
// 错误示例:循环条件遗漏'\0' void bad_copy(char* dest, const char* src) {int i = 0;while (src[i] != '\0') { // 仅拷贝到'\0'前的字符dest[i] = src[i];i++;}// 未拷贝'\0',dest不是合法字符串
-
后果:目标数组无
'\0'
,用%s
输出时会越界读取(乱码)。 -
正确做法:循环结束后手动添加'\0':
dest[i] = '\0'; // 必须添加结束符
(3)直接赋值数组名(语法错误)
char dest[20], src[] = "hello"; dest = src; // 错误!数组名是地址常量,不能被赋值
-
原因:数组名代表首地址(常量),不能作为左值修改,必须通过
strcpy
逐字符拷贝。
二、strcpy
函数的总结与对比
核心要点 | 说明 | 易错点提醒 |
---|---|---|
拷贝范围 | 从src[0] 到src[n] (n 为strlen(src) ),包括src[n] (即'\0' ) | 手动实现时需确保'\0' 被拷贝(否则目标数组不是合法字符串) |
目标数组要求 | 必须可修改(非字符串常量)、空间足够(≥strlen(src)+1 ) | 字符串常量(如"test" )不能作为dest (const 类型不可修改) |
与直接赋值的区别 | strcpy 是逐字符拷贝内容,dest = src 是地址赋值(语法错误) | 数组名是常量,不能直接赋值,必须用strcpy |
安全性 | 本身不检查空间(需程序员手动确保),存在溢出风险 | 实际开发中可使用strncpy (指定最大拷贝长度)替代,更安全 |
三、知识小结
知识点 | 核心内容 | 考试重点 / 易混淆点 | 难度系数 |
---|---|---|---|
strcpy 基本用法 | 格式:strcpy(dest, src) ;拷贝源字符串(含'\0' )到目标数组;返回dest 首地址 | 参数顺序(dest 在前,src 在后);必须包含'\0' 的拷贝 | ⭐⭐⭐ |
空间要求 | 目标数组长度 ≥ strlen(src) + 1 (否则溢出) | 缓冲区溢出的后果(程序崩溃、乱码);如何提前检查空间(sizeof(dest) >= ... ) | ⭐⭐⭐⭐ |
手动实现原理 | 循环逐字符拷贝,最后拷贝'\0' ;返回目标首地址 | 循环终止条件(需包含'\0' );手动添加'\0' 的必要性 | ⭐⭐⭐ |
常见错误 | 目标空间不足、忘记拷贝'\0' 、直接赋值数组名 | 数组名不能直接赋值(dest = src 错误);'\0' 对字符串的重要性(%s 输出依赖) | ⭐⭐⭐ |
安全性提升 | 可使用strncpy(dest, src, n) 限制拷贝长度(n 为目标数组最大容量 - 1) | strncpy 需手动添加'\0' (若源字符串超长);与strcpy 的适用场景区别 | ⭐⭐⭐ |
四、编程建议
-
优先使用标准库函数: 理解
strcpy
原理后,实际开发中直接使用标准库版本(经过优化和测试,更可靠),无需重复造轮子。 -
强制检查空间: 拷贝前必须验证目标数组长度 ≥ 源字符串长度 + 1,例如:
#define DEST_LEN 20 char dest[DEST_LEN]; char src[] = "example"; if (strlen(src) + 1 <= DEST_LEN) {strcpy(dest, src); // 安全 } else {// 处理空间不足(如截断或提示错误) }
-
了解替代函数:
strncpy(dest, src, n)
可指定最大拷贝长度(n
),避免溢出(但需手动添加'\0'
):strncpy(dest, src, DEST_LEN - 1); // 最多拷贝19个字符(留1个给'\0') dest[DEST_LEN - 1] = '\0'; // 确保结尾有'\0'
通过以上内容,可掌握strcpy
的用法、原理及安全注意事项,明确其在字符串操作中的核心作用 —— 实现字符串的完整拷贝,同时规避缓冲区溢出等高危错误。
字符串函数(进阶):strcat 与 strcmp 详解
一、字符串连接函数strcat
1. 函数基础与核心功能
-
函数原型:
char *strcat(char *dest, const char *src);
-
功能:将源字符串(
src
)连接到目标字符数组(dest
)的末尾,形成新的字符串。 -
核心逻辑:
-
在
dest
中寻找第一个'\0'
(原字符串的结尾); -
从该位置开始,将
src
的字符(包括src
的'\0'
)逐个拷贝到dest
; -
最终
dest
以src
的'\0'
结尾,原dest
的'\0'
被覆盖。
-
-
示例:
#include <stdio.h> #include <string.h> int main() {char dest[20] = "Hello"; // 目标数组(初始字符串:"Hello\0...")char src[] = " World"; // 源字符串(" World\0")strcat(dest, src); // 连接:在dest的'\0'位置开始拷贝srcprintf("连接后:%s\n", dest); // 输出"Hello World"return 0; }
2. 使用规则与注意事项
(1)必须满足的前提条件
-
dest
和src
均为合法字符串(均以'\0'
结尾):-
若
dest
无'\0'
,strcat
会越界寻找结尾(结果随机); -
若
src
无'\0'
,会越界拷贝(覆盖dest
后续内存)。
-
-
dest
空间必须足够大: 所需空间 =strlen(dest) + strlen(src) + 1
(原dest
长度 +src
长度 + 新'\0'
)。-
示例:
dest
初始长度 5("Hello"),src
长度 6("World"),需至少 5+6+1=12 字节(dest
定义为[20]
足够)。
-
-
dest
必须是可修改的字符数组: 不能是字符串常量(如strcat("Hello", "World")
错误,字符串常量不可修改)。
3. 常见错误与规避
(1)空间不足导致溢出
char dest[10] = "Hello"; // 空间10字节(当前已用6字节:5字符+'\0') char src[] = " World"; // 需6字节(5字符+'\0') strcat(dest, src); // 总需6+6=12字节 > 10 → 溢出!
-
后果:覆盖
dest
相邻内存,可能导致程序崩溃或输出乱码。 -
规避:连接前检查空间:
if (strlen(dest) + strlen(src) + 1 <= sizeof(dest)) {strcat(dest, src); // 安全连接 } else {printf("目标空间不足!\n"); }
(2)dest
不是合法字符串(无'\0'
)
char dest[10] = {'H', 'i'}; // 未显式添加'\0',且未初始化的元素可能非0 char src[] = "!"; strcat(dest, src); // 寻找'\0'时越界,可能将src连接到随机位置
-
后果:连接位置错误(可能覆盖
dest
的有效字符)。 -
规避:确保
dest
初始化为合法字符串:
char dest[10] = "Hi"; // 双引号初始化,自动添加'\0'(安全)
4. 综合应用:组合strcpy
与strcat
通过先拷贝、再连接,可构建复杂字符串:
// 示例:构建"Turbo C++" char result[20]; // 步骤1:先拷贝"Turbo"到result strcpy(result, "Turbo"); // 步骤2:连接空格 strcat(result, " "); // 步骤3:连接"C++" strcat(result, "C++"); printf("结果:%s\n", result); // 输出"Turbo C++"
-
优势:每次操作自动维护
'\0'
位置,无需手动管理结尾。
二、字符串比较函数strcmp
1. 函数基础与比较规则
-
函数原型:
int strcmp(const char *str1, const char *str2);
-
功能:按 ASCII 码值逐字符比较两个字符串,返回比较结果。
-
比较规则:
-
从第一个字符开始,逐个比较对应位置的字符(
str1[i]
vsstr2[i]
); -
若遇到不同字符,返回两者 ASCII 码的差值(
str1[i] - str2[i]
); -
若所有字符相同,比较长度:
-
长度相同(均到
'\0'
):返回 0(相等); -
长度不同(一个先到
'\0'
):返回 “较长字符串 - 较短字符串” 的差值(通常简化为 ±1)。
-
-
-
返回值约定:
-
>0
:str1 > str2
(str1
在字典序中更靠后); -
=0
:str1 == str2
(完全相同); -
<0
:str1 < str2
(str1
在字典序中更靠前)。
-
2. 比较示例与解析
示例 | 比较过程 | 返回值 | 结论 |
---|---|---|---|
strcmp("ABC", "ABC") | 所有字符相同,均到'\0' | 0 | 相等 |
strcmp("BBC", "ABC") | 第一个字符:'B'(66) > 'A'(65),停止比较 | 1 | str1 > str2 |
strcmp("ABD", "ABC") | 前两位相同,第三位 'D'(68) > 'C'(67),停止比较 | 1 | str1 > str2 |
strcmp("AB", "ABC") | 前两位相同,str1 先到'\0' (str2 还有 'C') | -1 | str1 < str2 |
strcmp("a", "A") | 'a'(97) > 'A'(65) | 32 | str1 > str2 (ASCII 差值) |
3. 使用注意事项
(1)不能用==
直接比较字符串
// 错误示例:比较的是地址,而非内容 if ("hello" == "world") { ... } // 比较两个字符串常量的地址(永远为假) if (str1 == str2) { ... } // 比较数组首地址(非内容)
-
正确做法:用
strcmp
比较内容:
if (strcmp(str1, str2) == 0) { // 内容相等printf("两字符串相同\n"); }
(2)返回值不一定是 ±1(依赖实现)
标准仅规定返回 “正 / 负 / 零”,未规定具体数值(如strcmp("a", "A")
可能返回 32,而非 1)。
-
编程建议
:判断时用
>0
/
<0
/
==0
,而非固定值:
if (strcmp(s1, s2) > 0) { ... } // 正确:s1 > s2 // 错误:依赖具体返回值(如1) if (strcmp(s1, s2) == 1) { ... }
(3)必须传入合法字符串(以'\0'
结尾)
若str1
或str2
无'\0'
,strcmp
会越界比较(结果随机):
char s1[] = {'a', 'b'}; // 无'\0' char s2[] = "abc"; strcmp(s1, s2); // 越界比较(结果不可预测)
三、知识小结
知识点 | 核心内容 | 考试重点 / 易混淆点 | 难度系数 |
---|---|---|---|
strcat 函数 | 连接src 到dest 末尾(覆盖dest 的'\0' ,添加src 的'\0' );需dest 空间足够 | 空间计算(strlen(dest)+strlen(src)+1 );dest 和src 必须有'\0' | ⭐⭐⭐ |
strcat 常见错误 | 空间不足导致溢出;dest 无'\0' 导致连接位置错误 | 如何提前检查空间(sizeof(dest) >= ... );初始化dest 为合法字符串 | ⭐⭐⭐⭐ |
strcmp 比较规则 | 逐字符比较 ASCII 码;遇不同字符或'\0' 停止;返回正 / 负 / 零表示大小关系 | 与== 的区别(地址比较 vs 内容比较);返回值的判断(>0 而非==1 ) | ⭐⭐⭐ |
strcmp 特殊案例 | 前序字符相同但长度不同时,较短字符串更小(如 "AB" < "ABC") | 区分 “长度” 与 “字典序”(短字符串不一定小,需前序字符相同) | ⭐⭐⭐ |
综合应用 | strcpy 拷贝初始内容 + strcat 连接后续内容,构建复杂字符串 | 每次操作的空间检查;'\0' 的自动维护机制 | ⭐⭐⭐ |
四、编程建议
-
strcat
使用流程:// 1. 定义足够大的目标数组 char dest[100]; // 2. 初始化目标数组(确保有'\0') strcpy(dest, "初始内容"); // 3. 连接前检查空间 if (strlen(dest) + strlen(src) + 1 <= sizeof(dest)) {strcat(dest, src); // 4. 安全连接 }
-
strcmp
正确判断方式:int res = strcmp(s1, s2); if (res == 0) {printf("相等\n"); } else if (res > 0) {printf("s1 > s2\n"); } else {printf("s1 < s2\n"); }
-
替代函数(更安全):
-
strncat(dest, src, n)
:限制连接的最大字符数(n
),避免溢出; -
strncmp(s1, s2, n)
:仅比较前n
个字符,适合长字符串部分比较。
-
通过以上内容,可掌握strcat
(连接)和strcmp
(比较)的核心用法,理解字符串操作中 “空间管理” 和 “'\0'
维护” 的重要性,规避缓冲区溢出、比较错误等常见问题。
字符串函数(扩展):带长度限制与查找类函数详解
一、带长度限制的拷贝与连接函数
1. 字符串部分拷贝函数strncpy
(1)函数功能与核心区别
-
函数原型:
char *strncpy(char *dest, const char *src, size_t n);
-
核心功能:从源字符串
src
拷贝最多n
个字符到目标数组dest
(区别于strcpy
的 “完整拷贝”)。 -
与
strcpy
的关键差异:特性 strcpy(dest, src)
strncpy(dest, src, n)
拷贝范围 完整拷贝(直到 src
的'\0'
)最多拷贝 n
个字符(无论src
是否结束)'\0'
处理自动拷贝 src
的'\0'
仅当 src
长度≤n
时,才用'\0'
填充剩余空间(否则不补'\0'
)目标串剩余部分 被覆盖(直到 '\0'
)未被覆盖的部分保留原值(仅替换前 n
个字符)
(2)示例与特殊行为解析
#include <stdio.h> #include <string.h> int main() {char dest[10] = "dot.com"; // 初始:d o t . c o m \0 ...char src[] = "make"; // src长度4(不含'\0')strncpy(dest, src, 4); // 拷贝src的前4个字符到destprintf("dest结果:%s\n", dest); // 输出"make.com"(前4个字符被替换,剩余保留)return 0; }
-
行为分析:
-
仅替换
dest
的前 4 个字符('d','o','t','.'
→'m','a','k','e'
); -
src
长度(4)等于n
(4),且src
无'\0'
("make"
的'\0'
未被拷贝),但dest
原有'\0'
在第 7 位('m','a','k','e','.','c','o','m','\0'
),因此可正常输出。
-
(3)使用注意事项
-
'\0'
可能缺失:若src
长度≥n
,拷贝后dest
前n
个字符无'\0'
(需手动添加,否则不是合法字符串)。char dest[5], src[] = "hello"; strncpy(dest, src, 4); // dest为'h','e','l','l'(无'\0') dest[4] = '\0'; // 手动添加结束符(必须!)
-
空间仍需足够:目标数组
dest
长度至少为n
(否则拷贝时越界)。 -
短源字符串的填充:若
src
长度<n
,剩余位置会自动用'\0'
填充(如src
长度 2,n=5
,则拷贝 2 个字符后补 3 个'\0'
)。
2. 字符串部分连接函数strncat
(1)函数功能与使用
-
函数原型:
char *strncat(char *dest, const char *src, size_t n);
-
功能:在
dest
的末尾追加src
的前n
个字符(或src
的全部字符,以较短者为准),自动添加'\0'
。 -
与
strcat
的区别:strcat
追加完整src
,strncat
可限制追加长度(避免溢出)。 -
示例:
char dest[20] = "Hello"; char src[] = "World!"; strncat(dest, src, 3); // 追加src的前3个字符"Wor" printf("结果:%s\n", dest); // 输出"HelloWor"(自动添加'\0')
(2)关键特性
-
自动添加
'\0'
:无论追加多少字符,最终dest
都会以'\0'
结尾(安全)。 -
目标空间要求:
dest
总长度需≥strlen(dest) + min(n, strlen(src)) + 1
(原长度 + 追加长度 +'\0'
)。
3. 字符串部分比较函数strncmp
(1)函数功能与应用
-
函数原型:
int strncmp(const char *str1, const char *str2, size_t n);
-
功能:比较
str1
和str2
的前n
个字符(区别于strcmp
的 “比较到'\0'
”)。 -
返回值:与
strcmp
一致(正 / 负 / 零表示大于 / 小于 / 等于),但仅基于前n
个字符。 -
典型应用:忽略字符串末尾的无关字符(如换行符)。
// 比较"quit"和"quit\n"的前4个字符(结果相等) if (strncmp(input, "quit", 4) == 0) {printf("检测到退出指令\n"); }
(2)注意事项
-
若
n
大于两字符串的长度,比较到较短字符串的'\0'
时停止(与strcmp
逻辑一致)。 -
适用于 “前缀匹配” 场景(如判断字符串是否以特定前缀开头)。
二、忽略大小写比较函数strcasecmp
-
函数原型:
int strcasecmp(const char *str1, const char *str2);
-
功能:比较两个字符串,忽略字母大小写(如
'A'
与'a'
视为相等)。 -
应用场景:用户输入容错(如 “QUIT”“Quit”“quit” 均视为相同指令)。
-
示例:
if (strcasecmp(input, "quit") == 0) {printf("退出程序\n"); // 匹配"quit"、"QUIT"、"Quit"等 }
-
注意:非字母字符(如数字、符号)仍按 ASCII 码严格比较(如
'!'
与'!'
相等,'1'
与'2'
不等)。
三、字符与子串查找函数
1. 字符查找函数strchr
与strrchr
(1)strchr
:查找字符首次出现位置
-
函数原型:
char *strchr(const char *s, int c); // c为字符的ASCII码(如查找'a'可传97或'a')
-
功能:在字符串
s
中查找字符c
首次出现的位置,返回该位置的地址;未找到返回NULL
。 -
示例:
char s[] = "hello world"; char *p = strchr(s, 'o'); // 查找首个'o' if (p != NULL) {printf("找到字符,位置:%ld\n", p - s); // 输出4(下标从0开始)printf("从该位置开始的字符串:%s\n", p); // 输出"o world" }
(2)strrchr
:查找字符最后一次出现位置
-
函数原型:
char *strrchr(const char *s, int c);
-
功能:查找字符c在s中最后一次出现的位置区别于
strchr
的 “首次”。
char s[] = "hello world"; char *p = strrchr(s, 'o'); // 查找最后一个'o' printf("位置:%ld\n", p - s); // 输出7("world"中的'o')
(3)关键技巧
-
计算下标:找到的地址与字符串首地址的差值即为字符下标(
p - s
)。 -
查找
'\0'
:strchr(s, '\0')
会返回s
末尾'\0'
的地址(可用于计算字符串长度:strchr(s, '\0') - s
等价于strlen(s)
)。
2. 子串查找函数strstr
(1)函数功能与应用
-
函数原型:
char *strstr(const char *haystack, const char *needle);
-
功能:在长字符串
haystack
中查找子串needle
首次出现的位置(如在 “hello world” 中查找 “lo”)。 -
返回值:找到则返回子串首字符地址;未找到返回
NULL
。 -
示例:
char str[] = "ho are you"; char sub[] = "are"; char *p = strstr(str, sub); // 查找"are"在str中的位置 if (p != NULL) {printf("子串位置:%ld\n", p - str); // 输出4(从0开始计数)printf("子串及后续:%s\n", p); // 输出"are you" }
(2)使用注意事项
-
子串为空:若
needle
是空字符串(""
),返回haystack
的首地址(标准规定)。 -
区分大小写:如需忽略大小写,使用
strcasestr
(非标准但多数编译器支持)。 -
空指针检查:未找到时返回
NULL
,使用前必须判断(否则访问NULL
会崩溃)。
三、知识小结
知识点 | 核心内容 | 考试重点 / 易混淆点 | 难度系数 |
---|---|---|---|
strncpy | 拷贝src 的前n 个字符到dest ;若src 长≥n ,需手动加'\0' | 与strcpy 的区别(是否限制长度);'\0' 的手动添加(否则目标串不合法) | ⭐⭐⭐ |
strncat | 追加src 的前n 个字符到dest 末尾,自动加'\0' ;需目标空间足够 | 目标空间计算(原长度 + 追加长度 + 1);与strcat 的长度限制差异 | ⭐⭐ |
strncmp | 比较前n 个字符,用于前缀匹配或忽略末尾字符(如"quit" 与"quit\n" ) | 比较范围仅限前n 个字符;返回值规则与strcmp 一致 | ⭐⭐⭐ |
strcasecmp | 忽略大小写比较字符串(字母不区分大小写,符号严格比较) | 应用场景(用户输入容错);非字母字符的严格比较 | ⭐⭐ |
strchr /strrchr | 查找字符首次 / 最后一次出现位置(返回地址);通过地址差值计算下标 | 地址差值转下标(p - s );查找'\0' 的特殊用法(等价于strlen ) | ⭐⭐⭐ |
strstr | 在长串中查找子串首次出现位置(返回地址);未找到返回NULL | 子串位置计算(p - haystack );NULL 返回值的检查(避免崩溃) | ⭐⭐⭐⭐ |
四、编程实践建议
-
优先使用带长度限制的函数:
strncpy
、strncat
、strncmp
比无长度限制的函数更安全(减少溢出风险)。 -
手动添加
'\0'
:使用strncpy
时,若拷贝长度等于n
且src
无'\0'
,务必手动添加(dest[n] = '\0'
)。 -
检查返回值:查找类函数(
strchr
、strstr
)返回NULL
时,需避免访问(如if (p != NULL) { ... }
)。 -
结合指针运算:通过 “找到的地址 - 首地址” 快速计算下标(无需循环遍历)。