字符串与内存拷贝函数
0. 目录
文章目录
1. 前言
在C语言中,库函数提供了能够处理字符串的一些函数,比如求取字符串长度的strlen
等等,有了这些字符串函数我们可以更加快速的实现需求,下面我们将学习各种内置字符串函数的特点及其模拟实现。
2. 函数介绍
2.1 求取字符串长度函数
网站链接:https://legacy.cplusplus.com/reference/cstring/strlen/?kw=strlen
语法格式:size_t strlen (const void* str)
作用:求取字符串的长度并返回
特点:
-
字符串根据
\0
作为标志,而strlen函数统计字符串中\0
之前的字符个数并返回 -
strlen函数参数指向的字符串必须包含
\0
,否则程序会崩溃 -
strlen函数的返回值为
size_t
类型,即是一个无符号数,这是一个易错点int main() { if (strlen("c") - strlen("c++") > 0) { printf("haha\n"); } else { printf("hehe\n"); } return 0; }
这段代码实际上会打印haha,因为strlen函数返回的是无符号整数,那么-2在内存中用无符号整型表示实际上对应相当大的数
模拟实现:
-
采用计数器方式
size_t my_strlen(const char* str) { int count_len = 0; while (*str != '\0') { count_len++; str++; } return count_len; }
-
采用指针-指针方式
size_t my_strlen(const char* str) { char* ps = str; while (*ps != '\0') { ++ps; } return ps - str; }
-
采用递归的方式
size_t my_strlen(const char* str) { if (*str == '\0') { return 0; } else { return 1 + my_strlen(str + 1); } }
2.2 字符串拷贝函数
2.2.1 strcpy函数
网站链接:https://legacy.cplusplus.com/reference/cstring/strcpy/?kw=strcpy
语法格式:char* strcpy(char* destination, const char* source)
作用:将source
指向的字符串中内容拷贝到destionation
指向的空间中
特点:
-
源字符串必须以
\0
结尾,拷贝以\0
作为结束标记 -
拷贝字符连同
\0
一起作为拷贝内容 -
目标空间必须足够大
-
目标空间必须可变,即不可以直接使用常量字符串如
char* dst = "hello world";
-
函数返回目标空间起始地址
模拟实现:
char* my_strcpy(char* dst, const char* src) {
// 当src指向\0时完成赋值拷贝,并且此时表达式结果为false停止循环
char* ret = dst;
while (*dst++ = *src++) {
;
}
return ret;
}
2.2.2strncpy函数
网站链接:https://legacy.cplusplus.com/reference/cstring/strncpy/?kw=strncpy
语法格式:char* strncpy(char* destination, const char* source, size_t num)
作用:从source
指向空间中拷贝num个字符到目标空间destination
中
模拟实现:跟strcpy
函数类似
2.3 字符串拼接函数
2.3.1 strcat函数
网站链接:https://legacy.cplusplus.com/reference/cstring/strcat/?kw=strcat
语法格式:char* strcat(char* destination, const char* source)
作用:将source
所指向的字符串拼接到destionation
所指向空间的末尾
特点:
- 源字符串和目标字符串都必须以
\0
结尾 - 目标空间必须可变
- 目标空间必须足够大,可以容纳拼接后的字符串
- 返回目标空间起始地址
模拟实现:
char* my_strcat(char* dst, const char* src) {
char* ret = dst;
while (*dst) {
++dst;
}
// 此时dst指向\0
// 开始拷贝
while (*dst++ = *src++) {
;
}
return ret;
}
注意事项:
-
当字符串自己给自己追加时,即若源目标空间与目的空间一致,容易导致错误
int main() { char arr[] = "hello"; strcat(arr, arr); // 会出现错误 return 0; }
因为当进行赋值时,结束标志
\0
已经被赋值为别的内容,那么源空间无法找到字符串结束标记,循环会一直进行,到访问未知空间,就会发生错误。
2.3.2 strncat函数
网站链接:https://legacy.cplusplus.com/reference/cstring/strncat/?kw=strncat
语法格式:char* strncat(char* destination, const char* source, size_t num)
作用:从source
指向空间中取出num个字符追加到destination
指向目标空间
模拟实现:与strcat函数类似
2.4 字符串比较函数
2.4.1 strcmp函数
网站链接:https://legacy.cplusplus.com/reference/cstring/strcmp/?kw=strcmp
语法格式:int strcmp(const char* str1, const char* str2)
作用:返回两个字符串比较后的结果
条件 | 返回值 |
---|---|
str1 > str2 | 返回大于0的数字 |
str1 == str2 | 返回0 |
str1 < str2 | 返回小于0的数字 |
模拟实现:
int my_strcmp(const char* str1, const char* str2) {
while (*str1 && *str2 && *str1 == *str2) {
// 说明当前比较字符相同,则继续比较
str1++;
str2++;
}
// 此时循环结束分别判断三种情况
if (*str1 == '\0' && *str2 == '\0') {
return 0;
} else {
// 此时说明字符串不相等
return *str1 - *str2;
}
}
2.4.2 strncmp函数
网站链接:https://legacy.cplusplus.com/reference/cstring/strncmp/?kw=strncmp
语法格式:int strncmp(const char* destination, const char* source, size_t num)
作用:比较两个字符串前num个字符的大小
模拟实现:跟strcmp函数类似
2.5 查找字符串子串函数
网站链接:https://legacy.cplusplus.com/reference/cstring/strstr/?kw=strstr
语法格式:char* strstr(const char* str1, const char* str2)
作用:该函数在str1指向字符串中查找是否包含str2
特点:
- 如果str1中包含字符串str2,那么返回str1中字符串str2的起始地址,反之返回NULL
模拟实现:
char* my_strstr(const char* str1, const char* str2) {
// 暴力枚举
while (*str1) {
while (*str1 && *str1 != *str2) {
++str1;
}
// 此时判断str1是否为\0
if (*str1 == '\0') {
// 说明没找到
return NULL;
} else {
// 开始比较
char* pstr1 = str1;
char* pstr2 = str2;
while (*pstr1 && *pstr2 && *pstr1 == *pstr2) {
++pstr1;
++pstr2;
}
// 此时判断两种情况
if (*pstr2 == '\0') {
// 说明找到了
return str1;
} else {
// 说明该轮次没找到继续查找
++str1;
}
}
}
}
2.6 字符串分隔函数
网站链接:https://legacy.cplusplus.com/reference/cstring/strtok/?kw=strtok
语法格式:char* strtok(char* str, const char* sep)
作用:在字符串str中查找是否包含sep字符集合中特定字符,并将其置为\0
作为标记,返回标记之前起始地址
特点:
sep
参数是一个字符串,作为分隔符的集合- 当strtok函数的第一个参数不为NULL,那么函数将找到字符串str中第一个标记,置为
\0
并记录当前位置,返回开始查找位置到第一个标记位置该段区间的起始地址 - 当strtok函数的第一个参数为NULL时,则以先前记录的位置开始查找后面的标记,并置为
\0
,返回该次区间的起始地址 - 当strtok函数无法找到下一个标记位时返回空指针NULL
用法:
int main() {
char arr[] = "123@163.com";
char copy_arr[20] = "";
char sep[] = "@.";
strcpy(copy_arr, arr);
char* ret;
for (ret = strtok(copy_arr, sep); ret != NULL; ret = strtok(NULL, sep)) {
printf("%s\n", ret);
}
return 0;
}
2.7 字符串错误信息函数
网站链接:https://legacy.cplusplus.com/reference/cstring/strerror/?kw=strerror
语法格式:char* strerror(int errnum)
作用:返回错误码对应的错误信息起始地址
特点:
- 当发生字符串错误信息时,比如文件路径错误,C语言会将错误信息对应的错误码保存到全局变量
errno
中,通过这个变量,我们就可以使用strerror
函数获取到对应的错误信息并打印
使用案例:
int main() {
for (int i = 0; i < 10; ++i) {
printf("%s\n", strerror(i));
}
return 0;
}
2.8 内存拷贝函数
2.8.1 memcpy函数
网站链接:https://legacy.cplusplus.com/reference/cstring/memcpy/?kw=memcpy
语法格式:void* memcpy(void* destination, void* source, size_t num)
作用:从source指向内容空间中拷贝num个字节
到destination指向内存空间
特点:
- 该函数与
strcpy
函数不同,即不关心\0
是否存在 - 参数num以字节作为单位
- 该函数的实现只要求destination与source指向两块不同的内存空间,即不要求实现内存空间一致情况下的拷贝
模拟实现:
void* my_memcpy(void* dst, void* src, size_t num) {
void* ret = dst;
// 拷贝num个字节
while (num--) {
*((char*)dst) = *((char*)src); // 强制类型转换为char*
dst = (char*)dst + 1;
src = (char*)src + 1;
}
return ret;
}
2.8.2 memmove函数
网站链接:https://legacy.cplusplus.com/reference/cstring/memmove/?kw=memmove
语法格式:void* memmove(void* destination, void* source, size_t num)
作用:从source指向内容空间中拷贝num个字节
到destination指向内存空间
特点:
- 该函数功能与
memcpy
一致 - 该函数要求与
memcpy
不一致,即一定可以实现重叠内存空间的拷贝
模拟实现:
void* my_memmove(void* dst, void* src, size_t num) {
void* ret = dst;
if (dst < src) {
// 从左往右拷贝
// 拷贝num个字节
while (num--) {
*((char*)dst) = *((char*)src); // 强制类型转换为char*
dst = (char*)dst + 1;
src = (char*)src + 1;
}
} else {
// 从有往左拷贝num个字节
while (num--) {
*((char*)dst + num) = *((char*)src + num);
}
}
return ret;
}
2.8.3 memcpy与memmove函数的区别
区别:memcpy函数不要求实现内存空间一致情况下的拷贝,一定可以实现重叠内存空间的拷贝
举例:
int main() {
char arr[] = "hello";
memmove(arr + 2, arr, 2);
printf("%s\n", arr);
return 0;
}
分析:上述代码中,我们使用memmove
函数的原因是arr + 2与arr本质上指向同一块内存空间,事实上该代码替换使用memcpy
函数在VS2019等编译器也是可以实现该功能的,但是别的编译环境不一定支持,而memmove
函数是一定可以实现该功能的。故此,如果想要拷贝同一块内存空间的数据,使用memmove
函数更加可靠。
2.9 内存比较函数
网站链接:https://legacy.cplusplus.com/reference/cstring/memcmp/?kw=memcmp
语法格式:int memcmp(const void* ptr1, const void* ptr2, size_t num)
作用:比较从ptr1与ptr2开始的num个字节内容大小
返回值:
条件 | 返回值 |
---|---|
ptr1 > ptr2 | 返回大于0的数字 |
ptr1 == ptr2 | 返回0 |
ptr1 < ptr2 | 返回小于0的数字 |
使用方式:
int main() {
int arr1[] = {1, 2, 3, 4};
int arr2[] = {1, 2, 4, 5};
if (memcmp(arr1, arr2, 9) > 0) {
printf("在前9个字节内容中, arr1大于arr2\n");
} else if (memcmp(arr1, arr2, 9) == 0) {
printf("在前9个字节内容中, arr1与arr2相等\n");
} else {
printf("在前9个字节内容中, arr1小于arr2\n");
}
return 0;
}
3. 总结
C语言提供的库函数还有很多,例如说字符串判断函数(是否为数字字符isdigit()
、是否为小写字母islower()
)与字符串转换大小写函数(转换为小写tolower()
、转换为大写toupper()
),这些函数可以大大提高我们的开发效率!所以提倡大家可以参考官网教程进行学习使用。