嵌入式Day9
C语言字符串标准库函数笔记
一、概述
C语言提供了一系列字符串标准库函数用于处理字符串,使用这些函数需要包含头文件 <string.h>
。主要函数包括求字符串长度、字符串复制、字符串拼接和字符串比较等。我们不仅要理解这些函数的行为,还需掌握手动实现的方法。
二、字符串长度计算
2.1 strlen
函数
- 函数声明
c
size_t strlen(const char *s);
- 功能 :计算字符串
s
的长度,即从首字符到\0
前的字符数,不包含\0
。 - 返回值 :
size_t
类型(无符号整数)。 - 注意事项
- 参数必须是合法字符串,确保
s
以\0
结尾,否则会越界访问,导致未定义行为。 - 与
sizeof
区别:strlen(str)
求字符串实际长度(不含\0
);sizeof(str)
求字符数组总长度(含\0
或预留空间)。
- 参数必须是合法字符串,确保
- 示例代码
c
int main() {
char str[] = "abcd"; // 长度4,数组大小5(含\0)
char str2[10] = "12345"; // 长度5,数组大小10
char str3[5] = {'a', '\0', 'c'}; // 长度1(遇\0停止)
char str4[4] = "1234"; // 非字符串(无\0),strlen(str4) 为未定义行为
printf("strlen(str) = %zu\n", strlen(str)); // 4
printf("sizeof(str) = %zu\n", sizeof(str)); // 5
return 0;
}
- 手动实现方法
- 方法1:遍历计数
c
size_t my_strlen(const char *s) {
size_t len = 0;
while (*s) { // 遇\0终止
len++;
s++;
}
return len;
}
- 方法2:指针移动计数(变种)
c
size_t my_strlen2(const char *s) {
size_t len = 0;
while (*s++) { // 先取值,后移动指针
len++;
}
return len; // 空字符不计数
}
- 方法3:指针差值法
c
size_t my_strlen3(const char *s) {
const char *p = s;
while (*p) { // p移动到\0位置
p++;
}
return p - s; // 指针差值即长度
}
- 方法4:指针差值变种
c
size_t my_strlen4(const char *s) {
const char *p = s;
while (*p++); // p移动到\0下一个位置
return p - s - 1; // 减去多余的一步移动
}
2.2 strlen
vs sizeof
对比
场景 | strlen(s) |
sizeof(s) |
---|---|---|
s 是字符数组名 |
字符串实际长度(不含 \0 ) |
数组总字节数(含 \0 /预留空间) |
s 是指针(如函数参数) |
字符串长度 | 指针变量大小(32位4字节,64位8字节) |
示例代码
c
void func(const char *str) {
printf("sizeof(str) = %zu\n", sizeof(str)); // 指针大小(如4字节)
}
int main() {
char str[] = "Clearlove"; // 长度9,数组大小10(含\0)
printf("strlen(str) = %zu\n", strlen(str)); // 9
printf("sizeof(str) = %zu\n", sizeof(str)); // 10
func(str); // 数组名退化为指针,sizeof为4字节
return 0;
}
三、字符串复制
3.1 strcpy
函数
- 函数声明
c
char *strcpy(char *dest, const char *src);
- 功能 :将
src
字符串完整复制到dest
中(含\0
)。 - 返回值 :
dest
指针(用于链式操作)。 - 注意事项
- 目标数组必须足够大,否则会越界,引发未定义行为(不安全)。
- 源必须是合法字符串,确保
src
以\0
结尾。 - 支持链式复制,如
strcpy(dest2, strcpy(dest1, src))
。
- 示例代码
c
int main() {
char src[] = "";
char dest[100], dest2[100];
// 链式复制
strcpy(dest2, strcpy(dest, src)); // 等价于两次单独复制
puts(dest); // 输出:
puts(dest2); // 输出:
return 0;
}
- 手动实现方法
- 方法1:手动添加
\0
- 方法1:手动添加
c
char *my_strcpy(char *dest, const char *src) {
char *temp = dest; // 保存目标指针初始位置
while (*src) { // 复制字符直到\0前
*dest++ = *src++;
}
*dest = '\0'; // 手动添加空字符
return temp;
}
- 方法2:利用循环条件自动复制
\0
c
char *my_strcpy2(char *dest, const char *src) {
char *temp = dest;
while (*dest++ = *src++) { // 先赋值(含\0),后移动指针
; // 循环条件为*src,复制完\0后条件为假,终止循环
}
return temp;
}
3.2 strncpy
函数
- 函数声明
c
char *strncpy(char *dest, const char *src, size_t n);
- 核心功能 :将
src
字符串中最多n
个字符复制到dest
字符数组中,可通过限制复制字符数量避免越界,比strcpy
更安全。 - 关键注意事项
n
的取值建议 :推荐取值为sizeof(dest) - 1
,为\0
预留空间,确保dest
有足够空间存储复制内容及结尾空字符,避免越界。- 复制行为与字符串完整性
- 当
n < strlen(src)+1
时,仅复制n
个字符,dest
不是字符串(无\0
),需手动添加\0
。 - 当
n = strlen(src)+1
时,完整复制src
(含\0
),dest
是合法字符串。 - 当
n > strlen(src)+1
时,复制完src
后,剩余n - (strlen(src)+1)
个位置填充\0
,确保dest
是字符串。
- 当
- 安全调用惯用法
c
strncpy(dest, src, sizeof(dest) - 1); // 限制复制长度
dest[sizeof(dest) - 1] = '\0'; // 手动添加空字符
- 函数行为详解
| 场景 | 复制行为 | 结果 |
| --- | --- | --- |
|n < strlen(src)+1
| 仅复制前n
个字符,不包含\0
|dest
非字符串,需手动补\0
|
|n = strlen(src)+1
| 完整复制src
(含\0
) |dest
是合法字符串 |
|n > strlen(src)+1
| 复制src
全部内容后,剩余位置填充\0
直至达到n
个字符 |dest
是合法字符串,尾部多\0
| - 示例代码
c
int main() {
char src[] = "hello"; // strlen(src)+1 = 6(含\0)
char dest[10]; // sizeof(dest)-1 = 9
// 场景1:n < 6(如n=3)
strncpy(dest, src, 3); // 复制"hel",dest[3]未处理
dest[3] = '\0'; // 手动补\0,否则puts(dest)会乱码
// 场景2:n = 6
strncpy(dest, src, 6); // 复制"hello\0",dest是字符串
// 场景3:n = 10(>6)
strncpy(dest, src, 10); // 复制"hello\0\0\0\0"(后4位补\0)
return 0;
}
- 手动实现方法
- 版本1:安全处理剩余空间
c
char *my_strncpy(char *dest, const char *src, size_t n) {
char *temp = dest; // 保存目标数组起始地址
size_t i;
// 复制src字符,最多n个
for (i = 0; i < n && *src; i++) {
*dest++ = *src++;
}
// 填充剩余空间为\0(若n > src长度)
if (i < n) {
for (; i < n; i++) {
*dest++ = '\0';
}
}
return temp;
}
- 版本2:循环优化
c
char *my_strncpy2(char *dest, const char *src, size_t n) {
char *temp = dest;
while (n-- && (*dest++ = *src++)) {
// 循环条件:n>0 且 src未到\0
// 自动复制\0当src结束时
}
// 若n>0(src已结束,剩余空间补\0)
while (n--) {
*dest++ = '\0';
}
return temp;
}
- 与
strcpy
的对比
| 特性 | strcpy | strncpy |
| --- | --- | --- |
| 安全性 | 不安全(需手动确保dest
足够大) | 安全(通过n
限制复制长度) |
| 空字符处理 | 自动复制\0
| 仅当n
足够时复制\0
|
| 推荐场景 | 确定dest
空间足够时 | 不确定dest
大小时 | - 常见错误与解决方案
- 错误:未手动补
\0
(当n < src
长度)
- 错误:未手动补
c
char dest[3];
strncpy(dest, "abcde", 3); // 复制"abc",无\0
puts(dest); // 输出乱码(继续读取后续内存直到遇\0)
解决方案 :复制后手动添加 dest[2] = '\0';
。
- 错误:
n
取值过大导致越界
c
char dest[5];
strncpy(dest, "hello", 10); // sizeof(dest)-1=4,正确应为4
// 错误:n=10超过dest可用空间(5-1=4),导致越界
解决方案 :始终使用 sizeof(dest)-1
作为 n
的取值。
四、字符串拼接
4.1 strcat
函数
- 函数声明
c
char *strcat(char *dest, const char *src);
- 核心功能 :将
src
字符串完整追加到dest
末尾(含\0
),原理是先定位dest
末尾的\0
,再执行strcpy(dest末尾, src)
。 - 注意事项
- 参数必须为合法字符串,
dest
和src
均需以\0
结尾,否则越界访问。 - 目标数组需足够大,需容纳
dest
原有内容 +src
内容 + 1(\0
),否则越界导致未定义行为。 - 返回值为
dest
首地址,可用于链式操作(如strcat(strcat(dest, src1), src2)
)。
- 参数必须为合法字符串,
- 错误示例
c
// 错误:dest为指针指向只读字符串,无法修改
char *dest = "hello ";
strcat(dest, "world!"); // 运行时崩溃(权限错误)
// 正确:dest为足够大的字符数组
char dest[20] = "hello ";
strcat(dest, "world!"); // 结果:"hello world!"
- 手动实现
c
char *my_strcat(char *dest, const char *src) {
char *tmp = dest;
// 定位到dest末尾的\0
while (*dest) dest++;
// 复制src到dest末尾(含\0)
while (*dest++ = *src++);
return tmp;
}
4.2 strncat
函数
- 函数声明
c
char *strncat(char *dest, const char *src, size_t n);
- 核心功能 :将
src
中最多n
个字符追加到dest
末尾,并自动添加\0
。即使越界,末尾也会补\0
;若n
大于src
长度,仅复制src
全部内容(不含后续补\0
)。 - 安全取值 :推荐
n
值为sizeof(dest) - strlen(dest) - 1
,含义为dest
总长度 - 已用长度 - 1(预留\0
)。
示例代码
c
char dest[20] = "hello "; // strlen(dest)=6
strncat(dest, "world!", sizeof(dest)-6-1); // n=13(20-6-1=13)
- 手动实现
c
char *my_strncat(char *dest, const char *src, size_t n) {
char *tmp = dest;
// 定位到dest末尾的\0
while (*dest) dest++;
// 复制最多n个字符,并手动添加\0
while (n-- && *src) {
*dest++ = *src++;
}
*dest = '\0'; // 确保dest以\0结尾
return tmp;
}
五、字符串比较
5.1 strcmp
函数
- 函数声明
c
int strcmp(const char *str1, const char *str2);
- 返回值规则
| 返回值 | 含义 | 类比 |
| --- | --- | --- |
|<0
|str1
<str2
|str1
字符编码 <str2
|
|0
|str1
==str2
| 完全相等 |
|>0
|str1
>str2
|str1
字符编码 >str2
| - 比较规则(字典序)
- 逐字符比较,从首字符开始,按ASCII码值比较,直到出现不同字符或
\0
。 - 空字符处理:
\0
的ASCII码为0,比所有字符小。例如,"abc"
<"abd"
(第三个字符c
<d
);"abc"
<"abcd"
("abc"
提前遇到\0
)。
- 逐字符比较,从首字符开始,按ASCII码值比较,直到出现不同字符或
- 示例代码
c
int main() {
char str1[] = "apple";
char str2[] = "banana";
char str3[] = "apple";
printf("%d\n", strcmp(str1, str2)); // -1('a' < 'b')
printf("%d\n", strcmp(str1, str3)); // 0(完全相等)
printf("%d\n", strcmp(str2, str1)); // 1('b' > 'a')
return 0;
}
- 手动实现
c
int my_strcmp(const char *s1, const char *s2) {
while (*s1 && *s2 && *s1 == *s2) { // 相同字符继续比较
s1++;
s2++;
}
return *s1 - *s2; // 直接返回编码差(自动处理\0情况)
}
六、函数对比表格
函数 | 功能 | 关键参数 | 安全性 | 结尾处理 |
---|---|---|---|---|
strcat |
拼接字符串 | dest , src |
不安全(需手动确保空间) | 自动复制 src 的 \0 |
strncat |
限定长度拼接 | dest , src , n |
安全(通过 n 限制) |
自动补 \0 (无论是否越界) |
strcmp |
比较字符串大小/相等 | str1 , str2 |
安全 | 无(仅比较内容) |
七、常见错误与最佳实践
7.1 strcat
越界错误
错误代码
c
char dest[5] = "abc"; // 长度3,数组大小5(含\0)
strcat(dest, "defg"); // 需空间3+4+1=8 >5,越界!
解决方案 :使用 strncat
或确保 dest
空间足够大。
7.2 strcmp
误用数值比较
错误代码
c
if (strcmp(str1, str2) == 1) // 错误:返回值可能为任意正数(如2、3等)
if (strcmp(str1, str2) > 0) // 正确:判断大小关系
最佳实践:仅通过符号(>0、<0、==0)判断,不依赖具体数值。