在日常敲代码的过程中,当我们处理字符串时经常会使用字符串函数,调用字符串函数则需要包含头文件 <string.h>,对于头文件 <string.h> 中包含的函数你了解多少呢?本篇博客总结了头文件 <string.h> 中一些常用的函数及其使用实例和部分函数的模拟实现,相信跟随本篇博客一起探讨,能让你对头文件 <string.h> 中的某些函数有不一样的理解。
目录
1. strlen ( 求字符串长度 )
size_t strlen( const char* string );
● 字符串以 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数 ( 不包括 '\0' )
● 参数指向的字符串必须要以 '\0' 结束,否则strlen函数的返回值是随机值
● strlen函数的返回值的类型为size_t,是无符号类型 ( 易错点 )
| 例 |
#include <stdio.h> #include<string.h> int main() { const char* str1 = "bdc"; const char* str2 = "abcdef"; if (strlen(str1) - strlen(str2) > 0) { printf(">\n"); } else { printf("<\n"); } return 0; }
如果对strlen函数返回值的类型不加注意,就会产生BUG,上面这段代码就是一个非常经典的案例。
1.1. strlen函数使用实例
#include <stdio.h>
#include<string.h>
int main()
{
char str[] = "hello world!";
int len = strlen(str);
printf("len = %d\n", len);
return 0;
}
1.2. 实现my_strlen函数
#include<stdio.h>
#include<assert.h>
//size_t my_strlen(const char* p) //1. 计数器型
//{
// assert(p);
// size_t count = 0;
// while (*p)
// {
// count++;
// p++;
// }
// return count;
//}
//size_t my_strlen(const char* p) //2. 指针减指针型
//{
// assert(p);
// char* start = p;
// while (*p)
// {
// p++;
// }
// return p - start;
//}
size_t my_strlen(const char* p) //3. 递归型
{
assert(p);
if (*p)
{
return 1 + my_strlen(p + 1);
}
else
{
return 0;
}
}
int main()
{
char str[] = "hello world!";
int len = my_strlen(str);
printf("len = %d\n", len);
return 0;
}
2. strcpy ( 字符串拷贝 )
char* strcpy( char* strDestination, const char* strSource );
● strcpy函数的返回值为目标字符串空间的起始地址
● 源字符串必须以 '\0' 为结尾,strcpy函数会将源字符串中的字符一一拷贝至目标字符串中,包括 '\0' ( 在拷贝过程中第一次将 '\0' 拷贝完后则停止拷贝 )
● 目标字符串的空间必须足够大,以确保能存放源字符串
● 目标字符串必须可修改 ( 即不被 const 修饰 )
2.1. strcpy函数使用实例
#include<stdio.h>
#include<string.h>
int main()
{
char str1[] = "hello world!";
char str2[20] = { 0 };
strcpy(str2, str1);
printf("%s\n", str2);
return 0;
}
2.2. 实现my_strcpy函数
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dst, const char* src)
{
assert(dst && src);
char* start = dst;
while (*dst++ = *src++)
{
;
}
return start;
}
int main()
{
char str1[] = "hello world!";
char str2[20] = { 0 };
my_strcpy(str2, str1);
printf("%s\n", str2);
return 0;
}
3. strcat ( 字符串追加 )
char* strcat( char* strDestination, const char* strSource );
● strcat函数的返回值为目标字符串空间的起始地址
● 目标字符串的空间必须足够大,以确保能存放追加后的字符串
● 目标字符串必须可修改 ( 即不被 const 修饰 )
● 源字符串必须以 '\0' 为结尾,strcat函数以将源字符串的 '\0' 追加至目标字符串空间作为追加停止条件
● strcat函数的目标字符串空间和源字符串空间不能为同一块内存空间,即不能自己给自己追加 ( 易错点 )
以上动图是使用strcat函数自己给自己追加的情况,当dst指向目标字符串的 '\0' 时开始追加,因为目标字符串空间与源字符串空间为同一内存空间,当开始追加后,源字符串中的 '\0' 会被覆盖,从而导致一直循环追加下去,无法按照预期停止追加。( 在VS2022的环境下,编译器对该情况继续了优化,不会出现上述动图的情况,但仍然不建议使用strcat函数自己追加自己 )
3.1. strcat函数使用实例
#include<stdio.h>
#include<string.h>
int main()
{
char str1[30] = "hello ";
char str2[] = "world!";
strcat(str1, str2);
printf("%s\n", str1);
return 0;
}
3.2. 实现my_strcat函数
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dst, const char* src)
{
assert(dst && src);
char* start = dst;
while (*dst)
{
dst++;
}
while (*dst++ = *src++)
{
;
}
return start;
}
int main()
{
char str1[30] = "hello ";
char str2[] = "world!";
my_strcat(str1, str2);
printf("%s\n", str1);
return 0;
}
4. strcmp ( 字符串比较 )
int strcmp( const char* string1, const char* string2 );
● 标准规定:
▢ string1 大于 string2,则返回大于0的数字;
▢ string1 等于 string2,则返回0;
▢ string1 小于 string2,则返回小于0的数字;
4.1. strcmp函数使用实例
#include<stdio.h>
#include<string.h>
int main()
{
char str1[] = "abcd";
char str2[] = "efg";
if (strcmp(str1, str2) > 0)
{
printf("str1 > str2\n");
}
else if (strcmp(str1, str2) < 0)
{
printf("str1 < str2\n");
}
else
{
printf("str1 = str2\n");
}
return 0;
}
4.2. 实现my_strcmp函数
#include<stdio.h>
#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++;
}
return (*str1 - *str2);
}
int main()
{
char str1[] = "abcd";
char str2[] = "efg";
if (my_strcmp(str1, str2) > 0)
{
printf("str1 > str2\n");
}
else if (my_strcmp(str1, str2) < 0)
{
printf("str1 < str2\n");
}
else
{
printf("str1 = str2\n");
}
return 0;
}
5. strncpy ( 限制个数的字符串拷贝 )
char* strncpy( char* strDestination, const char* strSource, size_t num );
● strncpy函数的返回值为目标字符串空间的起始地址
● 从源字符串中拷贝num个字符到目标字符串空间中
● 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标字符串后面追加0,直到num个。
5.1. strncpy函数使用实例
#include<stdio.h>
#include<string.h>
int main()
{
char str1[] = "hello world!";
char str2[20] = { 0 };
strncpy(str2, str1, 5);
printf("%s\n", str2);
return 0;
}
5.2. 实现my_strncpy函数
#include<stdio.h>
#include<assert.h>
char* my_strncpy(char* dst, const char* src, size_t num)
{
assert(dst && src);
char* start = dst;
while (*src && num--)
{
*dst++ = *src++;
}
while (num--)
{
*dst++ = 0;
}
return start;
}
int main()
{
char str1[] = "hello world!";
char str2[20] = { 0 };
my_strncpy(str2, str1, 8);
printf("%s", str2);
return 0;
}
6. strncat ( 限制个数的字符串追加 )
char* strncat( char* strDestination, const char* strSource, size_t num );
● strncat函数的返回值为目标字符串空间的起始地址
● 当源字符串长度小于num时,strncat函数不会像strncpy那样在目标字符串后面补充0
● 在目标字符串后面追加完成后,会自动在目标字符串末尾加上 '\0'
6.1. strncat函数使用实例
#include<stdio.h>
#include<string.h>
int main()
{
char str1[20] = "hello ";
char str2[] = "world!";
strncat(str1, str2, 3);
printf("%s\n", str1);
return 0;
}
6.2. 实现my_strncat函数
#include<stdio.h>
#include<assert.h>
char* my_strncat(char* dst, const char* src, size_t num)
{
assert(dst && src);
char* start = dst;
while (*dst)
{
dst++;
}
while (*src && num--)
{
*dst++ = *src++;
}
*dst = '\0';
return start;
}
int main()
{
char str1[20] = "hello ";
char str2[] = "world!";
my_strncat(str1, str2, 3);
printf("%s\n", str1);
return 0;
}
7. strncmp ( 限制个数的字符串比较 )
int strncmp( const char* string1, const char* string2, size_t num );
● 标准规定:
▢ string1 大于 string2,则返回大于0的数字;
▢ string1 等于 string2,则返回0;
▢ string1 小于 string2,则返回小于0的数字;
7.1. strncmp函数使用实例
#include<stdio.h>
#include<string.h>
int main()
{
char str1[] = "abbdff";
char str2[] = "abbcde";
if (strncmp(str1, str2, 4) > 0)
{
printf("str1 > str2\n");
}
else if (strncmp(str1, str2, 4) < 0)
{
printf("str1 < str2\n");
}
else
{
printf("str1 = str2\n");
}
return 0;
}
7.2. 实现my_strncmp函数
#include<stdio.h>
#include<assert.h>
int my_strncmp(const char* str1, const char* str2, size_t num)
{
assert(str1 && str2);
for (num -= 1; num && *str1++ == *str2++; num--)
{
if (*str1 == '\0')
{
return 0;
}
}
return (*str1 - *str2);
}
int main()
{
char str1[] = "abbdff";
char str2[] = "abbcde";
if (my_strncmp(str1, str2, 3) > 0)
{
printf("str1 > str2\n");
}
else if (my_strncmp(str1, str2, 3) < 0)
{
printf("str1 < str2\n");
}
else
{
printf("str1 = str2\n");
}
return 0;
}
8. strstr ( 查找子字符串 )
char* strstr( const char* string1, const char* string2 );
● string1为目标字符串,string2为待查的子字符串
● 若在string1中有子字符串string2,则返回在string1中子字符串的第一个字符的地址;若在string1中没有找到对应的子字符串string2,则返回NULL ( 例如:string1 = abaacde,string2 = baa,string1中存在子字符串string2,则返回在string1中的字符 'b' 的地址 )
8.1. strstr函数使用实例
#include<stdio.h>
#include<string.h>
int main()
{
char str1[] = "hello world!";
char str2[] = "lo wo";
char* pc = strstr(str1, str2);
printf("%s\n", pc);
return 0;
}
8.2. 实现my_strstr函数
#include<stdio.h>
#include<assert.h>
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
const char* s1 = str1;
const char* s2 = str2;
const char* p = str1;
while (*p)
{
s1 = p;
s2 = str2;
while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return p;
}
p++;
}
return NULL;
}
int main()
{
char str1[] = "hello world!";
char str2[] = "lo wo";
char* pc = my_strstr(str1, str2);
printf("%s\n", pc);
return 0;
}
9. strtok ( 切割字符串 )
char* strtok( char* strToken, const char* strDelimit );
● 参数strDelimit是一个字符串,定义了用作分隔符的字符集合
● 第一个参数strToken是一个字符串,它包含了0个或者多个由strDelimit字符串中一个或者多个分隔符分割的标记
● strtok函数找到strToken中的下一个标记,并将其用 '\0' 结尾,返回一个指向这个标记的指针 ( 注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改 )
● 若strtok函数的第一个参数不为NULL,函数将找到strToken中第一个标记,strtok函数将保存它在字符串中的位置
● 若strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记
● 如果strToken中不存在更多的标记,则返回NULL指针
9.1. strtok函数使用实例
#include<stdio.h>
#include<string.h>
int main()
{
char email[] = "NICKnK579008@outlook.com";
char key[] = "@.";
char cp[30] = { 0 };
strcpy(cp, email);
//char* ret = strtok(cp, key);
//printf("%s\n", ret);
//ret = strtok(NULL, key);
//printf("%s\n", ret);
//ret = strtok(NULL, key);
//printf("%s\n", ret);
//以上代码过于冗余,可对以上代码进行优化
char* ret = NULL;
for (ret = strtok(cp, key); ret != NULL; ret = strtok(NULL, key)) 1.利用for循环的初始化只执行一次的逻辑,匹配第
一次调用strtok函数第一个参数要传字符串地址这一规定
2.利用for循环的调整部分,后续调用strtok函数时
将NULL作为第一个参数传递
3.以strtok返回值为NULL作为for循环的终止条件
{
printf("%s\n", ret);
}
return 0;
}
10. memcpy ( 拷贝两块独立内存 )
void* memcpy( void* strDestination, const void* strSource, size_t num );
● memcpy函数的返回值为目标空间的起始地址
● strDestination的空间要足够大,以至于能存放得下strSource的内容
● 从strSource的位置开始向后复制num个字节的数据到strDestination的内存位置
● memcpy函数在拷贝过程中遇到 '\0' 的时候并不会停下来
● 如果strSource和strDestination有任何的重叠部分,拷贝的结果都是标准未定义的
10.1. memcpy函数使用实例
#include<stdio.h>
#include<string.h>
int main()
{
double farr[] = { 1.2,4.5,3.3,5.7,0.2,3.0 };
double cp[10] = { 0.0 };
memcpy(cp, farr, sizeof(farr[0])*4);
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%.1lf\n", cp[i]);
}
return 0;
}
10.2. 实现my_memcpy函数
#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* dst, const void* src, size_t num)
{
assert(dst && src);
void* start = dst;
while (num--)
{
*(char*)dst = *(char*)src;
dst = (char*)dst + 1;
src = (char*)src + 1;
}
return start;
}
int main()
{
double farr[] = { 1.2,4.5,3.3,5.7,0.2,3.0 };
double cp[10] = { 0.0 };
my_memcpy(cp, farr, sizeof(double) * 4);
int i = 0;
for (i = 0; i < 4; i++)
{
printf("%.1lf\n", cp[i]);
}
return 0;
}
11. memmove ( 可拷贝重复内存 )
void* memmove( void* strDestination, const void* strSource, size_t num );
● 和memcpy函数的差别就是memmove函数处理的源内存空间和目标内存空间是可以重叠的
● 如果源内存空间和目标内存空间有重叠部分,就得使用memmove函数处理
11.1. memmove函数使用实例
#include<stdio.h>
#include<string.h>
int main()
{
char str[] = "hello world!";
memmove(str + 7, str + 1, 4);
printf("%s\n", str);
return 0;
}
11.2. 实现my_memmove函数
#include<stdio.h>
#include<assert.h>
void* my_memmove(void* dst, const void* src, size_t num)
{
assert(dst && src);
void* start = dst;
if (dst < src)
{
while (num--)
{
*(char*)dst = *(char*)src;
dst = (char*)dst + 1;
src = (char*)src + 1;
}
}
else
{
while (num--)
{
*((char*)dst + num) = *((char*)src + num);
}
}
return start;
}
int main()
{
char str[] = "hello world!";
my_memmove(str + 7, str + 1, 4);
printf("%s\n", str);
return 0;
}
12. memcmp ( 内存比较 )
int memcmp( const void* ptr1, const void* ptr2, size_t num );
● 从指针变量ptr1和指针变量ptr2开始向后对num个字节的数据按字节一一进行对比
● 遇到不同的数据时,会提前终止比较,后面的数据不再进行比较
● 标准规定:
▢ *ptr1 大于 *ptr2,则返回大于0的数字;
▢ *ptr1 等于 *ptr2,则返回0;
▢ *ptr1 小于 *ptr2,则返回小于0的数字;
12.1. memcmp函数使用实例
#include<stdio.h>
#include<string.h>
int main()
{
float farr1[] = { 3.1f,4.4f,5.3f,6.2f,0.2f };
float farr2[] = { 3.1f,4.4f,2.5f,4.8f };
if (memcmp(farr1, farr2, sizeof(float) * 4) > 0)
{
printf("farr1 > farr2\n");
}
else if (memcmp(farr1, farr2, sizeof(float) * 4) < 0)
{
printf("farr1 < farr2\n");
}
else
{
printf("farr1 = farr2\n");
}
return 0;
}
13. memset ( 内存初始化 )
void* memset( void* ptr, int value, size_t num );
● memset函数的返回值为ptr空间的起始地址
● 对num个字节初始化,每个字节的值初始化成value
13.1. memset函数使用实例
#include<stdio.h>
#include<string.h>
int main()
{
char str[10] = { 0 };
memset(str, '$', 10);
return 0;
}
13.2. 实现my_memset函数
#include<stdio.h>
#include<assert.h>
void* my_memset(void* ptr, int value, size_t num)
{
assert(ptr);
void* start = ptr;
while (num--)
{
*(char*)ptr = value;
ptr = (char*)ptr + 1;
}
return start;
}
int main()
{
char str[10] = { 0 };
my_memset(str, '$', 10);
return 0;
}
本次与大家一起探讨头文件 <string.h> 中常用的函数到这就已经接近尾声了,期待下次与你相遇。
< 你的关注,点赞,评论,收藏都是对我创作最大的鼓励 >
( 若本篇博客存在错误,望指出,感谢! )