C语言模拟实现字符串库函数的示例讲解
叫我小秦就好了 人气:0字符串检验
strlen
函数原型
/// @brief 返回给定空终止字符串的长度,即首元素为 str 所指,且不包含首个空字符的字符数组中的字符数 /// @param str 指向要检测的字符串的指针 /// @return 字符串 str 的长度 size_t strlen( const char *str );
空终止字符串即 C 语言中以 ‘\0’ 作为终止符的字符串,strlen 计算字符串的长度并返回,返回的长度不包含 ‘\0’。
char arr[] = "qgw"; // num 的值是 3 int num = strlen(arr);
模拟实现
下面的实现方式比较简单,采用遍历字符串的方式,遇到 ‘\0’ 就退出循环,返回结果。
size_t my_strlen(const char* str) { size_t cnt = 0; while (*str != '\0') { ++str; ++cnt; } return cnt; }
strcmp
函数原型
/// @brief 以字典序比较二个字符串 /// @param str rhs 要比较的两字符串 /// @return 相等返回 0 int strcmp(const char* lhs, const char* rhs);
返回值:
- 两字符串字典序相同,返回 0
- lhs 字典序大于 rhs,返回一个正数
- lhs 字典序小于 rhs,返回一个负数
模拟实现
实现的思路是:遍历两个字符串,直到出现不同或有字符串结束。
res 存储两字符串比较的结果,如果等于 0 循环继续,不等于 0 直接退出,lhs 指向的字符大于 rhs 指向的字符就为正数,否则反之。
若 lhs 先结束,rhs 还没结束,此时 res 为负数,\0 的 ASCII 码为 0,是最小的。若 rhs 先结束,lhs 还没结束,此时 res 为正数。若同时结束,res 刚好为 0 返回。
int my_strcmp(const char* lhs, const char* rhs) { int res = 0; while ((res = *lhs - *rhs) == 0 && *lhs != '\0' && *rhs != '\0') { ++lhs; ++rhs; } return res; }
strstr
函数原型
/// @brief 在 str 中查找 substr 子串 /// @param str 指向要检验的空终止字符串的指针 /// @param substr 指向要查找的空终止字节字符串的指针 /// @return 指向于 str 中找到的子串首字符的指针,或若找不到该子串则为空指针 char *strstr( const char* str, const char* substr );
若 substr 指向空,则会返回 str。
模拟实现
下面实现的方法为暴力匹配子串,实际中可以使用 KMP 或 BM 等字符串搜索函数优化这一过程。
char* my_strstr(const char* str, const char* substr) { if (substr == NULL) { return str; } // 遍历 str 所有字符,看以其起始字符是否匹配 while (*str != '\0') { const char* backStr = str; const char* backSub = substr; while (*backStr != '\0' && *backSub != '\0' && *backStr == *backSub) { ++backStr; ++backSub; } // 如果 substr 走完了,说明匹配成功了,返回此时的 str if (*backSub == '\0') { return str; } ++str; } }
字符串操作
strcpy
函数原型
/// @brief 复制 src 所指向的空终止字符串,包含空终止符,到首元素为 dest 所指的字符数组 /// @param dest 指向要写入的字符数组的指针 /// @param src 指向要复制的空终止字符串的指针 /// @return 返回 dest 的副本 char *strcpy( char *dest, const char *src );
需要注意的是:
1.若 dest 数组长度不足则行为未定义
即 dest 数组不足以包含 src 中所有元素,此时大概率会因越届访问崩溃
2.若字符串覆盖则行为未定义
现在主流编译器都可以处理有覆盖的情况,比如:MSVC、GCC
3.若 dest 不是指向字符数组的指针或 src 不是指向空终止字符串的指针则行为未定义
模拟实现
下面的方法先记录要返回的地址,最后遍历 src 遇到 \0,此时退出循环。
注意:该实现方法并不能解决字符串有覆盖的情况。
char* my_strcpy(char* dest, const char* src) { char* res = dest; while (*dest = *src) { ++dest; ++src; } return res; }
strcat
函数原型
/// @brief 后附 src 所指向的空终止字符串的副本到 dest 所指向的空终止字符串的结尾 /// @param dest 指向要后附到的空终止字符串的指针 /// @param src 指向作为复制来源的空终止字符串的指针 /// @return 返回 dest 的副本 char *strcat( char *dest, const char *src );
需要注意的是:
会用字符 src[0] 替换 dest 末尾的 \0
若目标数组对于 src 和 dest 的内容以及空终止符不够大,则行为未定义
若字符串重叠,则行为未定义
若 dest 或 src 不是指向空终止字符串的指针,则行为未定义
模拟实现
因为会先用 src 第一个字符替换 dest 结尾的 \0,所以要先找到 dest 结尾 \0。然后再遍历 src,将其添加到 dest 结尾。
char* my_strcat(char* dest, const char* src) { char res = dest; // 先找 \0 的位置 while (*dest != '\0') { ++dest; } // 追加到 dest 后面 while (*dest = *src) { ++dest; ++src; } return res; }
内存操作
memcpy
函数原型
/// @brief 从 src 所指向的对象复制 count 个字符到 dest 所指向的对象 /// @param dest 指向要复制的对象的指针 /// @param src 指向复制来源对象的指针 /// @param count 复制的字节数 /// @return 返回 dest 的副本 void* memcpy(void* dest, const void* src, size_t count);
要注意的是:
若访问发生在 dest 数组结尾后则行为未定义
若对象重叠,则行为未定义
也就是说在标准中 memcpy 也不能处理对象重叠的情况
若 dest 或 src 为非法或空指针则行为未定义
模拟实现
下面给出的实现方式与 strcpy 相似,不过是改用 count 变量来控制循环次数。
memcpy 是最快的内存到内存复制子程序。它通常比必须扫描其所复制数据的 strcpy,或必须预防以处理重叠输入的 memmove更高效。
void* my_memcpy(void* dest, const void* src, size_t count) { void* res = dest; while (count--) { *(char*)dest = *(char*)src; ++(char*)dest; ++(char*)src; } return res; }
memmove
函数原型
/// @brief 从 src 所指向的对象复制 count 个字节到 dest 所指向的对象 /// @param dest 指向要复制的对象的指针 /// @param src 指向复制来源对象的指针 /// @param count 复制的字节数 /// @return 返回 dest 的副本 void* memmove(void* dest, const void* src, size_t count);
memmove 是不是看起来和 memcpy 一模一样,但 memmove 可以处理内存重叠的情况,这也就说明它要做一些检查,来保证能够处理内存重叠的情况。
1.无重叠
直接调用更高校的 memcpy
2.有重叠,dest 在 src 之前,正常正向复制
3.有重叠,dest 在 src 之后,需要反向复制
模拟实现
通过对上图的观察,我们可以发现:若 dest 在 src 的后面并且存在内存重叠,就需要反向复制。
我们可以简化这一函数,对无重叠的情况不去调用 memcpy 而是包含在下面两种情况中。
dest 在 src 之前,采用正向复制
dest 在 src 之后,采用反向复制
void* my_memmove(void* dest, const void* src, size_t count) { void* res = dest; if (dest < src) { while (count--) { *(char*)dest = *(char*)src; ++(char*)dest; ++(char*)src; } } else { while (count--) { *((char*)dest + count) = *((char*)src + count); } } }
加载全部内容