𝙉𝙞𝙘𝙚!!👏🏻‧✧̣̥̇‧✦👏🏻‧✧̣̥̇‧✦ 👏🏻‧✧̣̥̇:Solitary-walk
⸝⋆ ━━━┓
- 个性标签 - :来于“云”的“羽球人”。 Talk is cheap. Show me the code
┗━━━━━━━ ➴ ⷯ本人座右铭 : 欲达高峰,必忍其痛;欲戴王冠,必承其重。
👑💎💎👑💎💎👑
💎💎💎自💎💎💎
💎💎💎信💎💎💎
👑💎💎 💎💎👑 希望在看完我的此篇博客后可以对你有帮助哟👑👑💎💎💎👑👑 此外,希望各位大佬们在看完后,可以互赞互关一下,看到必回
👑👑👑💎👑👑👑
WeChat_20240104155108
字符串相关的函数和内存块相关函数思维导图:
目录
一:strlen( )
二:长度不受限制的字符串函数
strcpy strcat strcmp
三:长度受限制的字符串函数
strncpy strncat strncmp
四:字符串查找
strstr strtok
五:错误信息报告
strerror
perror
六:字符分类函数
七:内存函数
memcpy
memmove
memcmp
memset
一:strlen( )
函数原型:
正确的应用中:strlen()的参数部分必须要有 \0
注意:
1)strlen只统计\0 之前的字符个数
2)strlen的函数返回类型是size_t (对应的占位符是 %u)
strlen模拟实现
方法1:借助计数的方式
size_t my_strlen(const char* start)
{
//采用计数器来模拟实现strlen
int count = 0;
while (*start )
{
count++;
start++;
}
return count;
}
int main()
{
printf("%u\n",my_strlen("abc"));
}
方法2:借助指针 - 指针
size_t my_strlen( char* str)
{
//采用指针 - 指针实现strlen 模拟
char* p = str;//遍历字符串
while (*p)
{
p++;
}
return p - str;
}
int main()
{
printf("%u\n",my_strlen("abcd"));
}
方法3:借助递归思想
二: 长度不受限制的字符串函数
strcpy
函数原型:
使用注意要点:
1)目标空间足够大并且可以修改
2)源字符串必须有 \0
3) 在拷贝的时候 源字符串的\0也拷贝
strcpy模拟实现
有了以上的注意点,相信对strcpy的模拟实现咱也是易如反掌
思路分析:
1:定义2个指向目标空间和源字符串的指针
char* dst //指向目标空间
char* s //指向源字符串
2:一个字符一个字符的拷贝
3:注意最后源字符串的\0也要拷贝过去
char* my_strcpy(char* dst, const char* s)
{
char* ret = dst;//拷贝完之后返回目标空间起始地址,所以需要先保存一下
assert(dst);
assert(s);
while (*s)
{
*dst++ = *s++;//对源字符串非\0之前的拷贝
}
//别忘了还有\0没有拷贝
*dst = *s;
return ret;//返回目标空间起始地址
}
int main()
{
/*
strcpy(char* dst,const char* sor);使用注意事项
1:目标空间足够大,源字符串有 \0
2:目标空间可以修改 const char*dst //err
3:源字符串的\0也会拷贝过去
*/
char a1[20] = {0};
char a2[11] = {"hello bit"};
my_strcpy(a1, a2);
printf("%s\n", a1);
return 0;
}
对于上面这个核心代码还可以进行再次优化
简化版之后的
while (*dst++ = *s++)
{
;
}代码分析:
先把*s赋给 *dst并进行后置加加 注意此时是对加加之前的条件进行判断真假
直到 *s == \0 赋给*dst 结束拷贝同时源字符串的\0也已经拷贝了
strcat
函数原型:
函数模拟实现
1)首先找到目标空间的\0
2)其次是对源字符串的拷贝 (和strcpy模拟实现一样的)
char* my_strcat(char* dst, const char* s)
{
/*
先找到目标空间的开始追加的地址(一般是\0)
开始拷贝
*/
char* ret = dst;
while (*dst) //找 \0
{
++dst;
}
while (*dst++ = *s++)//数据拷贝
{
;
}
*dst = *s;
return ret;
}
是否可以对字符串自己进行追加?
答案:no no no
分析如下:
假设对字符串 "hello"来进行追加
指针s,dst初始位置
经过依次拷贝之后
通过画图我们知道,此时\0已经被覆盖,这就会导致指针s一直找不到从而造成死循环
strcmp
strcmp函数介绍:
1)比较对应位置上字符的ASCII码值(a-z的ASCII码值依次增加)
2)函数的返回类型:int
strcmp模拟实现
分析:假设比较字符串str1 ,str2
int my_strcmp(const char* s1,const char* s2)
{
while (*s1 && *s2)
{
if (*s1 != *s2)
break;
s1++, s2++;//继续比较下一对字符
}
return *s1 - *s2;//注意这里是对应字符的ASCII码值进行相减
}
三:长度受限制的字符串函数(就是在原来功能的基础上加上了字数的限制)
strncpy
注意当指定拷贝的个数大于源字符串的内容时,会自动补加\0
strncat
strncat可不像strncpyhan函数一样当指定追加的字符个数大于源字符串的内容时,会自动补加\0
strncmp
注意:对num的传参决定了这个函数返回值
str1: abcd
str2 : abd
当num == 3;返回小于0
当num == 2;返回等于0
当num == 4;返回小于0
四:字符串查找
strstr
strstr 功能:在一个字符串中查找一个子字符串并返回这个子字符串第一次出现的位置
在字符串s1("abbbcdef")里面查找字符串s2("bcdef")
当 *s1= a时,显然是不匹配的。
此时开始新一轮查找
分析见下:
char* my_strstr( char* str1, char* str2)
{
char* cp = str1;
while (*cp)
{
char* s1 = cp;
char* s2 = str2;
while (*s2 && *s1 && *s1 == *s2) // 处理对应位置相等
{
s1++;
s2++;
}
if (*s2 == '\0')
return cp;
cp++;
}
//来到这:没有找到
return NULL;
}
strtok
char * strtok ( char * str, const char * sep );
1)sep参数是个字符串,定义了用作分隔符的字符集合
第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标 记。
2)strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。)
3)strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置(会把这个标记换成\0)。
4)strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。
5)如果字符串中不存在更多的标记,则返回 NULL 指针。
strtok函数的使用
solitary@walk.at.123#99 对这个字符串进行提取
int main()
{
char a1[] = "solitary@walk.at.123#999";
char copy[50];//对a1临时拷贝
strcpy(copy, a1);
char sep[] = "#@.";//分隔符集合
char* ret = strtok(copy, sep);
printf("%s\n", ret);
ret = strtok(NULL, sep);
printf("%s\n", ret);
ret = strtok(NULL, sep);
printf("%s\n", ret);
ret = strtok(NULL, sep);
printf("%s\n", ret);
ret = strtok(NULL, sep);
printf("%s\n", ret);
return 0;
}
不知道各位有没有这样的问题:对于这个字符串的具体内容我是知道的,我直接进行这种暴力的打印就可以,那要是这个字符串很长很长,你是否又知道需要具体打印多少次呢?
这里我们借助循环就可以实现,只不过需要我们对strtok这个函数非常的了解
当字符串不在存在标记时,这个函数返回NULL :依据这个点我们作为循环的判断条件
int main()
{
char a1[] = "solitary@walk.at.123#999";
char copy[50];//对a1临时拷贝
strcpy(copy, a1);
char sep[] = "#@.";//分隔符集合
char* ret = strtok(copy, sep);
//借助循环来实现字段的打印
for (ret; ret != NULL; ret = strtok(NULL, sep))
{
printf("%s\n", ret);
}
return 0;
}
五:错误信息报告
strerror
strerror函数通常用于处理系统调用库函数(记住不是自己编写的函数)出现的错误,该函数会返回一个指向错误消息字符串的指针
当库函数调用失败的时候,strerror函数会把错误码记录到errno这个变量中
注意:errno是C语言的一个全局变量,不需要用户自己定义
perror
perror这个函数功能和strerror函数功能是一样的:都是打印错误码对应的错误信息
perror这个错误码也是从errno这个全局变量里面拿到的
perror 和 strerror 区别
首先这两个函数都是当调用标准库里面的函数失败的时候,打印对应的错误信息。
唯一区别就是在打印的形式上有区别:
strerror()这个函数:在打印输出的时候,用户可以根据需要自己添加冒号和换行符
perror()这个函数:在打印输出的时候,不需要自己添加冒号和换行符(打印的时候自动添加冒号和换行符)
六:字符分类函数
七:内存函数
memcpy
memcpy的前2个参数都是 vod*类型:因为内存块里存放的数据你不知道是什么类型,所以是void* 类型
第三个参数:拷贝源数据总的字节数
memcpy的模拟实现分析:
1)对数据进行一个字节一个字节的拷贝
2)注意:对void* 类型指针不能直接解引用或者是++操作
3)对指针进行强转具有临时性
4)对于内存块重叠的拷贝是不可以的(严格意义来说),一般涉及内存块重叠交给memmove函数
#include<stdio.h>
#include<string.h>
void* my_memcpy(void* dst, const void* sor, size_t num)
{
char* ret = dst;//对目标空间起始地址保留
while (num--)
{
//一个字节的拷贝
*(char*) dst = *(char*)sor;
//dst++, sor++; // err 强转具有临时性
//(char*)dst++; // err
dst = (char*)dst + 1;
sor = (char*)sor + 1;
}
return ret;
}
int main()
{
char a1[20] = "xxxxxxxxxxxxxxxx";
char a2[] = "yyyyy";
my_memcpy(a1, a2, 4);
printf("%s\n",a1);
return 0;
}
memmove
memmove模拟实现的分析
1)拷贝:从前往后拷贝还是从后往前拷贝
若是从后往前拷贝 7拷贝到5的位置, 6拷贝到4的位置 ,原来的5已经被覆盖掉了
从前往后拷贝:5拷贝到3 的位置, 6拷贝到4的位置,7拷贝到5 的位置,刚好解决了数据覆盖的问题
注意看,此时 dst 的起始位置 要 > sor的起始位置 ,若果要是从前往后拷贝会不会出现问题
从前往后拷贝:3拷贝到5 的位置,4拷贝到6 的位置,原来5 的已经被覆盖掉了
从后往前拷贝:5拷贝到7 的位置,4拷贝到6 的位置,3拷贝到5的位置刚刚好解决了数据覆盖的问题
此时无论从前往后还是从后往前拷贝都会出现数据覆盖问题
不知道聪明的你,是否已经注意到一个问题就是到底从前往后还是从后往前拷贝其实是取决于dst 与sor的起始相对位置
对于这个函数的模拟其实是有2种方法实现
方法1:
dst < sor 从前往后拷贝
dst >= sor && dst <= sor+num 从后往前拷贝
dst > sor+num 从前往后拷贝
方法2:
dst < sor 从前往后拷贝
dst >= sor 从后往前拷贝
2) 从前往后拷贝的逻辑(这个就和memcpy模拟实现一样的)
while (num--)
{
//从前往后拷贝 ,和memcpy模拟实现一样
//一个字节一个字节拷贝
*(char*)dst = *(char*)sor;
dst = (char*)dst + 1;
sor = (char*)sor + 1;
}3)从后往前拷贝的逻辑
这里我们需要考虑如何拿到5这个数据对应在最后一个字节?
注意:num代表要拷贝字节的总数目
我知道sor+num 是从sor起始位置 跳过num个字节,对于上面的图而言就是指向6 的起始位置,sor+num-1不就是指向原内存块的最后一个字节嘛
代码逻辑:
//先找到源内存块的最后一个字节 跳过num-1个字节指向最后一个字节
while (num -- ) //先用后减减 (num不为0 ; 进入下面的同时-1)
{
*((char*)dst +num)= *((char*) sor+num);
}
这里借助方法2来实现模拟
void* my_memmove(void* dst, const void* sor, size_t num)
{
char* ret = dst;
assert(dst && sor);
// 从前往后还是从后往前取决于 dst 与 sor 起始位置
if (dst > sor)
{
//从后往前拷贝,避免数据覆盖
//先找到源内存块的最后一个字节 跳过num-1个字节指向最后一个字节
while (num -- ) //先用后减减 (num不为0 ; 进入下面的同时-1)
{
*((char*)dst +num)= *((char*) sor+num);
}
}
else
{
while (num--)
{
//从前往后拷贝 ,和memcpy模拟实现一样
//一个字节一个字节拷贝
*(char*)dst = *(char*)sor;
dst = (char*)dst + 1;
sor = (char*)sor + 1;
}
}
return ret;
}
科普一下:对于内存块数据重叠拷贝按理说memmove就可以实现,但是当我们在VS的编译器来测试的时候,也能实现对 内存块数据重叠拷贝
注意并不是所有 的编译器在调用标准库的memcpy都能实现内存块数据重叠拷贝,具体情况还是取决于编译器
memcmp
memmcmp函数原型: int memcmp ( const void * ptr1, const void * ptr2, size_t num );
参数介绍:
1)ptr1 和ptr2 分别指向各自对应内存块的起始地址
2) num : ptr1 和ptr2 指向内存块 的前个num字节进行比较
注意啦,友友们:这里的参数num(要比较的字节数) 和函数strncmp参数的num含义可不同,后者代表:2个字符串要比较的字符个数
还是惯例,接下来我们进行memcmp的模拟实现
memcmp函数模拟实现
int my_memcmp(const void* str1, const void* str2, size_t num)
{
assert(str1 && str2);
//注意memcmp比较的过程是:;一个字节一个字节的比较(是对内存里的数据)
while (num--)
{
/*if (*(char*)str1 > *(char*)str2)
return 1;
else if (*(char*)str1 < *(char*)str2)
return -1;*/
if ((*(char*)str1 == *(char*)str2))
{
str1 = (char*)str1 + 1;
str2 = (char*)str2 + 1;
}
else
return *(char*)str1 - *(char*)str2;
}
return *(char*)str1 - *(char*)str2;
}
int main()
{
/*int a1[] = { 1,8,3,4,5,6,7,8,9 };
int a2[] = { 1,2,12 };*/
char a1[] = "abcd";
char a2[] = "abmnj";
int ret = memcmp(a1, a2, 2); //注意这里memcmp的第三个参数num表示要比较的前num个字节数而不是个数
printf("%d", ret);
return 0;
}
memset
memset函数原型: void * memset ( void * ptr, int value, size_t num );
ptr:起始地址
value:要设置的值
num:要设置的字节数
1)这个函数对内存块的设置,所以说value我们多数传参为字符(在C语言里,字符的本质也是数值)
2)对memset 模拟实现的关键也是在于第二参数
我们需要考虑如何将int 类型数据转换成char 类型数据???
对于这个问题我暂时有2 个解决方案(目前理论以及实践上都可以说的过去)
方法1:对第二个参数进行改变:char val
方法2:第二个参数依然是int 类型
把int 类型数据转换成char 类型数据:对val % 256
*((char*)p) = val % 256;
对应完整代码
方法2代码:
方法1代码:
结语:
以上就是关于字符串以及内存块相关函数的share,相信到这个,各位老铁们的知识量已经大增了吧,这些函数在我们日常的模拟题中也会涉及到,若是到时候用上,那岂不是很香嘛。在此我也衷心希望各位铁子们能够收获满满。
那话不多,你懂的,咱接下来走起!