【C语言】字符函数的易错点及其模拟实现
在上一章为大家简单介绍了字符函数的种类和基础运用,语法。那么在本章为大家分享一些易错点和自己如何编写一个自定义函数来实现相同的作用。(点个关注呗)
一·strlen:计算字符串长度
1. 函数原型
size_t strlen(const char *str);
2. 核心功能
-
计算从
str
指向地址到第一个'\0'
的字符数 -
不包含终止符
'\0'
本身
3. 使用示例
char s[] = "Hello";
printf("%zu", strlen(s)); // 输出5
4.易错点
-
未初始化指针:传入未初始化的指针导致段错误
-
非字符串参数:传入没有
'\0'
结尾的字符数组 -
修改const数据:错误使用非常量指针修改只读区字符串
-
格式化输出:在使用
printf
函数输出size_t
类型的值时,应使用%zu
格式说明符,而不是%d
或%u
。这是因为%zu
是专门为size_t
类型设计的格式说明符。 - 由于
strlen
的返回值是size_t
类型,这是一个无符号整数,因此在进行比较或算术运算时需要注意。例如,以下代码可能会导致意外的结果: -
if (strlen(x) - strlen(y) >= 0) { // 这个条件总是为真,因为 size_t 类型不可能是负数 }
#include <stdio.h>
#include <string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcde";
if (strlen(arr1) - strlen(arr2) > 0)//这里比较字符串的长度大小,无法直接比较,所以需要strlen函数。
{
printf("arr1>arr2\n");
}
else
{
printf("arr1 <= arr2\n");
}
return 0;
}
5.模拟实现
这里用递归的方法来实现。(还有计算器,指针-指针)
函数是求字符串的长度的,我们是从str指向的字符串中,统计\0之前的字符个数的
函数本身是不期望,修改str指向的字符串的
my_strlen("abcdef");
1 + my_strlen("bcdef")
1 + 1 + my_strlen("cdef")
1 + 1 + 1 + my_strlen("def")
1 + 1 + 1 + 1 + my_strlen("ef")
...
1 + 1 + 1 + 1 + 1 + 1 + my_strlen("");
a b c d e f 0
递归
#include <assert.h>
size_t my_strlen(const char* str)
{
assert(str != NULL);//断言输入的是正确的。
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str+1);
}
int main()
{
char arr[] = "abcdef";
size_t len = my_strlen(arr);
printf("%zu\n", len);
return 0;
}
二.strcpy:字符串拷贝
1. 函数原型
char *strcpy(char *dest, const char *src);
2. 核心功能
-
将
src
指向的字符串(包含'\0'
)拷贝到dest
-
返回
dest
指针
3. 使用示例
char src[] = "Hello";
char dest[6];
strcpy(dest, src); // dest内容为"Hello\0"
4. 易错点
-
缓冲区溢出:目标数组空间不足
-
地址重叠:源与目标内存区域重叠导致未定义行为
-
忘记终止符:手动实现时漏拷贝
'\0'
5. 模拟实现
1.
char *my_strcpy(char *dest, const char *src)
{
char *ret = dest;
while((*dest++ = *src++)); // 包含'\0'的拷贝
return ret;
}
2.
char* my_strcat( char* dest, char* src)
{
//assret(dest != NULL);
char* ret = dest;
while (*dest++ = *src++)//1.数据的拷贝 2.拷贝完\0 循环停止
{
;// 这里是一个空语句
}
return ret;//返回的是目标空间的地址
}
int main()
{
char e1[] = "qwerti";
char e2[20] = "hhhhhhhhhhhh";
my_strcat(e2, e1);
printf("%s", e2);
return 0;
}
三.strcat :字符串拼接
1. 函数原型
char *strcat(char *dest, const char *src);
2. 核心功能
-
将
src
字符串追加到dest
末尾(覆盖原'\0'
) -
返回
dest
指针
3. 使用示例
char str[20] = "Hello";
strcat(str, " World!"); // 结果:"Hello World!\0"
4. 易错点
-
缓冲区溢出:目标剩余空间不足,目标空间一定要大
-
未初始化目标:目标字符串没有正确初始化
-
多次拼接忘记重置:重复使用导致意外结果
5. 模拟实现
(*dest
等价于 *dest != '\0'
('\0'
的ASCII值为0,即逻辑假))
char* my_strcat(char* dest, const char* src)
{
char* ret = dest;
// 定位到dest结尾
while (*dest) dest++;
// 执行拷贝
while ((*dest++ = *src++));
//或者是
//while(*dest++ = *src++)
//{
// ;
// }
return ret;
}
int main()
{
char arr1[] = "hello";
char arr2[] = "ge wei xiong di ";
//stract(arr2, arr1);
printf("%s",my_strcat(arr1, arr2));
return 0;
}
四.strcmp:字符串比较
1. 函数原型
int strcmp(const char *s1, const char *s2);
2. 核心功能
按ASCII码值比较字符串内容
返回值:
<0:s1 < s2
=0:s1 == s2
>0:s1 > s2
3. 使用示例
printf("%d", strcmp("apple", "banana")); // 输出负数
4. 易错点
-
误解返回值:认为返回-1/0/1(负数表示小于,0表示等于,正数表示大于)
-
比较非字符串:参数未以
'\0'
结尾 -
忽略大小写:A(65)与a(97)会被认为不同
-
比较内容:不是比较字符串长短,而是字符对应的ASCLL值
5. 模拟实现
#include <assert.h>
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')
return 0;//相等
str1++;
str2++;
}
if (*str1 > *str2)
return 1; //大于
else
return -1;//小于
}
int main()
{
//abc\0
//abc\0
int r = my_strcmp("abcdef", "abq");
if (r > 0)
printf("abcdef > abq\n");
else if(r == 0)
printf("abcdef == abq\n");
else
printf("abcdef < abq\n");
int w = my_strcmp("abr", "abq");
if (w > 0)
printf("abr > abq\n");
else if (w == 0)
printf("abr == abq\n");
else
printf("abr < abq\n");
return 0;
}
通过这两个比较,可以清楚的理解比较的内容了吧。
五.对比总结表
函数 | 功能 | 关键点 | 风险点 | 时间复杂度 |
---|---|---|---|---|
strlen | 计算长度 | 不包含'\0' | 非字符串输入 | O(n) |
strcpy | 字符串拷贝 | 包含'\0'的完整拷贝 | 缓冲区溢出 | O(n) |
strcat | 字符串连接 | 覆盖原终止符 | 目标空间不足 | O(n+m) |
strcmp | 字符串比较 | ASCII值逐字符比较 | 误解返回值含义 | O(n) |
六.优化方案
一、字符串拷贝函数组
strcpy | char* strcpy(char* dest, const char* src) 完全拷贝src到dest(含'\0') |
strncpy | char* strncpy(char* dest, const char* src, size_t n) 最多拷贝n个字符 |
关键区别:
-
strncpy
不会自动添加终止符,需手动保证:char buf[10]; strncpy(buf, src, sizeof(buf)-1); buf[sizeof(buf)-1] = '\0'; // 强制终止
经典错误案例:
// 错误:缓冲区溢出 规定的大小为5,实际输入大于5 char dest[5]; strcpy(dest, "HelloWorld"); // 崩溃! // 错误:未终止字符串 无\0 char dest[10]; strncpy(dest, "Hello", 5); // dest内容为'H','e','l','l','o',?,?,?,?,?
二、字符串连接函数组
strcat | char* strcat(char* dest, const char* src) | 将src追加到dest末尾 |
strncat | char* strncat(char* dest, const char* src, size_t n) | 最多追加n个字符+自动补'\0' |
安全用法公式:
// 安全追加算法
size_t free_space = dest_size - strlen(dest) - 1; // 计算剩余可用空间
strncat(dest, src, free_space);
示例对比:
// 危险操作
char buf[10] = "Hello";
strcat(buf, "World!"); // 需要11字节空间(溢出)
// 安全操作
char buf[10] = "Hello";
strncat(buf, "World!", sizeof(buf)-strlen(buf)-1); // 追加4字符+"\0"
三、字符串比较函数组
strcmp | int strcmp(const char* s1, const char* s2) | 比较到任意字符串出现'\0'为止 |
strncmp | int strncmp(const char* s1, const char* s2, size_t n) | 只比较前n个字符 |
七.结语
好嘞,本章就到此结束啦,感谢观看,代码不仅要多看,还要多敲。希望大家自己去实现一下模拟实现。