网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
在这一过程里,源字符串内容并不会被修改,而目标字符串则会被源字符串内容所覆盖。这也是括号中(无限制)的含义。
(1)strcpy函数原型解析
char *strcpy( char *strDestination, const char *strSource );
这个函数将参数strSource字符元素拷贝到strDestination字符串参数中,由于在此过程strSource参数将会被持续的访问元素并将其拷贝给strDestination。
因此它必须是字符数组或者是指向动态内存分配内存的数组的指针,不能使用字符串常量,不然会发生意想不到的的错误,未定义。
*strcpy函数是以(源字符串)*strSource末尾的NULL(‘\0’)为结束标志,并且将NULL也拷贝至目标字符串中。
(2)strcpy函数的使用
在探索strcpy函数的使用中,我们同时也思考着这两种情境:
1、如果目标字符串比源字符串长时,strcpy函数怎么处理?
2、如果目标字符串比源字符串短时,strcpy函数怎么处理?
情境一:
如果目标字符串比源字符串长时,strcpy函数怎么处理?
int main()
{
char vate1_char[] = "Sorrowful life";
char vate2_char[] = "Happiness";
char* ret = strcpy(vate1_char, vate2_char);
printf("%s\n", ret);
return 0;
}
结果:
情境二:
如果目标字符串比源字符串短时,strcpy函数怎么处理?
int main()
{
char vate1_char[] = "Happiness";
char vate2_char[] = "Sorrowful life";
char* ret = strcpy(vate1_char, vate2_char);
perror("strcpy:");
printf("输出:%s\n", ret);
return 0;
}
结果:
情境一分析:
问题1:如果目标字符串比源字符串长时,strcpy函数怎么处理?
char *strcpy( char *strDestination, const char *strSource );
目标参数中的内容将被覆盖丢失,即使源字符串参数比目标字符串参数要短,但它的元素末尾是包含着NULL字节结尾的,所以在拷贝的过程中,在读取到源字符串中的NULL时,目标字符串后面的内容都会被有效的删除。
情境二分析:
问题2:如果目标字符串比源字符串短时,strcpy函数怎么处理?
char *strcpy( char *strDestination, const char *strSource );
在目标字符串参数短于源字符串参数的情况下,strcpy函数仍会继续拷贝,进而越界修改使用未知内存的参数,虽然它会将源字符串元素拷贝进目标字符串中,但这种操作是违法的。strcpy函数无法有效的解决这个问题,因为它无法判断目标字符串起始空间的大小。
但这种错误是有办法规避的,那就是需要各位伙伴们在使用前细心的检查一遍目标空间大小是否足够。不要小看这一步,它可以为我们节省去很多调试排错的时间!
(3)strcpy函数的模拟实现
抓住核心:
当源字符串中的’\0’被拷贝至目标字符串中时,即停止拷贝!
模拟实现:
char* my_strcpy(char* vate1_char, const char* vate2_char)
{
assert(vate1_char && vate2_char);
char* temp = vate1_char;
while (*vate1_char = *vate2_char)//当vate1_char为NULL时停止拷贝
{
vate1_char++;
vate2_char++;
}
return temp;
}
int main()
{
char vate1_char[20] = "我爱编程";
char vate2_char[] = "编程爱我!";
char* ret = my_strcpy(vate1_char, vate2_char);
printf("%s", ret);
return 0;
}
结果:
愿所有的努力都不会被辜负,加油吧伙伴们!
(4)strcpy函数的缺陷
1、拷贝函数一旦运行,它就会自主的将所有的源字符串元素拷贝至目标字符串,缺乏调控性,局限了使用范围。
2、容易造成越界操作。
(二)、strncpy函数 · (有限制)字符串拷贝
strncpy函数与strcpy函数不能说毫无差别,只能说功能一模一样。唯一不同的是加入了:限制数。
这也使得strcpy函数的弊端得到了根本优化!
(1)strncpy函数原型解析
char *strcpy( char *strDestination, const char *strSource ,size_t len);
size_t len的出现,就意味着这个函数将不再“失控”。虽然功能与strcpy不尽相同,但实现的步骤却有了不同:size_t len提供给拷贝任务运行限制,如果符合限制条件,那么拷贝任务继续。如果触及限制条件,那么拷贝停止。
注意!它的结果将不再以源字符串中的NULL字节结尾。
(2)strncpy函数的使用
同样的,在开始进行函数使用前,我们思考这三个情境:
1、如果限制数len的数比源字符串的元素位数大,结果是如何?
2、如果源字符串的元素位数比限制数len的数大,结果是如何?
3、如果限制数len的数比目标字符串的元素位数大,结果是如何?
情境一:
如果限制数len的数比源字符串的元素位数大,结果是如何?
//1、如果限制数len的数比源字符串的元素位数大,结果是如何?
int main()
{
char vate1_char[15] = "wwwwwwwwwwwwww";
char vate2_char[] = "ssssss";
int len = 10;
char* ret = strncpy(vate1_char, vate2_char, len);//10为限制数
printf("%s", ret);
return 0;
}
结果:
情境二:
如果源字符串的元素位数比限制数len的数大,结果是如何?
//2、如果源字符串的元素位数比限制数len的数大,结果是如何?
int main()
{
char vate1_char[15] = "wwwwwwwwwwwwww";
char vate2_char[] = "ssssss";
int len = 3;
char* ret = strncpy(vate1_char, vate2_char, len);
printf("%s", ret);
return 0;
}
结果:
情境三:
如果限制数len比目标字符串的元素位数大,结果是如何?
int main()
{
char vate1_char[15] = "wwwwwwwwwwwwww";
char vate2_char[] = "ssssss";
int len = 20;
char* ret = strncpy(vate1_char, vate2_char, len);
printf("%s", ret);
return 0;
}
结果:
情境一分析:
**问题1:**如果限制数len的数比源字符串的元素位数大,结果是如何?
char *strcpy( char *strDestination, const char *strSource ,size_t len);
如果限制数len的大小比strlen(strSource)->(源字符串)大,那么strncpy函数会将源字符串中的元素拷贝到目标字符串中,而len限制数未运行的拷贝数,也将会由等量的NULL来填充。
这是函数拷贝进行时vate1_char字符串内部的变化,而当调用结果以字符指针被传回时,在输出的过程中,只能读取到源字符串结尾的’\0’,所以后面的字节都会被覆盖。
vate1_char数组内部元素摆放:
情境二分析:
问题2:如果源字符串的元素位数比限制数len的数大,结果是如何?
char *strcpy( char *strDestination, const char *strSource ,size_t len);
如果strlen(strSource)->(源字符串位数)比限制数len大时。那么,函数将依据len的大小,只拷贝源字符串中的len个数位到目标字符串中。而在结束时,不以NULL字节作为补齐,因为源字符串数位满足len限制数的调用需求,故结果将以目标字符串中的NULL为结束标志。
但发生源字符串大于len限制数时,那么只会拷贝源字符串中的len位,而原目标字符串中的其他数位也不会被覆盖,因为返回的结果是以原目标字符串中的NULL为结束标志。
vate1_char数组内部元素摆放:
情境三分析:
问题3:如果限制数len的数比目标字符串的元素位数大,结果是如何?
char *strcpy( char *strDestination, const char *strSource ,size_t len);
当限制数len大于strlen(***trDestination)***目标字符串时,不论源字符串的数位大小满足限制数len的条件与否,都会造成最后的内存溢出与非法修改操作。最后strncpy函数会完成它本职的拷贝工作,但是你根本不知道它将溢出的数位拷贝到了哪一块内存中!
现在再来回头看看其中的错误,其实大部分的错误都是可以被避免的,比如我们在使用拷贝函数前,先将目标字符串的大小进行合理的预留,那么bug就将减少%50!
猜想:那么为什么要用NULL来填充呢?
因为,strncpy函数限制数的加入,使得函数的使用变得更灵动化。但有时候,由于错误的限制数设定,导致strncpy函数的调用结果可能并不是一个字符串。原因在于:strncpy(有限制)函数不会像strcpy函数那样,会将源字符串中的NULL也拷入进目标字符串来作为停止标记。
所以,strncpy函数为了避免错误的发生,它会在限制数大于源字符串数的时候触发这个保护机制,即:不够的拷贝位由NULL来补齐,以确保结果是一个能用格式化输出printf->%s来打印出。
(3)strncpy函数的模拟实现
strncpy函数的特性:
具有限制性!
上代码:
char* my_strncpy(char* v_1, const char* v_2, int len)
{
assert(v_1 && v_2);//断言
char* ret = v_1;//将v_1首元素复制给*ret,用于返回值
while (len--)//交换一次限制数就--
{
*v_1++ = *v_2++;//拷贝
}
return ret;
}
int main()
{
char vate_1[25] = "我爱熬夜!也爱学习!";
char vate_2[] = "我爱生活!";
int len = 0;
puts("请输入拷贝数:");
scanf("%d", &len);
char* ret = my_strncpy(vate_1, vate_2, len);
printf("%s\n", ret);
return 0;
}
结果:
将源字符串中的5个位数拷贝至目标字符串中,可能有人会疑惑了:
为什么五个元素你却输入9个限制数,那岂不是拷贝了9次?
其实,在最初,char类型是不允许汉字的参入的,但由于计算机语言的迅速发展,亚太地区汉语、日语、韩语等没有办法在计算机中得到参入,严重影响了功能的实现,故添加了文字。但文字与字母不同,一个汉字等同于两个字母。所以在拷贝中,需要消耗两个拷贝数才能实现一个汉字的拷贝。
故:2+2+2+2+1=9.
二、字符串追加操作
字符串的追加操作,指的是在字符串的末尾插入另一个字符串,对目标字符串进行元素的增加。
(一)、strcat函数·(无限制)字符串追加
strcat函数是一个无限制的字符串追加函数。
即:给定一个目标字符串与源字符串,那么它就会将源字符串元素全部插入目标字符串。
这个函数的顺利进行,必须要依托’\0’,且是两个字符串参数都具有NULL字节结尾。可以这么理解:
目标字符串中的’\0’,是插入元素(源字符串)首元素的地址(入口),而源字符串的’\0’则是出口,即结束标志。
图演:
以上是strcat函数的核心作用,但这这是为了让伙伴们先简要理解以下它是干什么的,好菜还在下面呢!
(1)strcat函数原型解析
char *strcat( char *strDestination, const char *strSource );
strcat函数的两个参数必须是以’\0’为结尾的字符序,其返回的值是一个字符指针。
或许有人好奇:那如果两个参数都不为’\0’结尾的字符序结果会是如何?
那么接下来就让我们实验出真理吧!
(2)strcat函数的使用
老样子,开始前,我们需要怀揣着几个问题思考:
1、如果strcat函数参数部分双方不为字符串,结果是如何?
2、strcat函数可以自己(参数)调用自己(参数)吗?
好戏开始!
情境一:
如果strcat函数参数部分双方不为字符串('\0’结尾),结果是如何?
上代码:
int main()
{
char vate_1[20] = { 'h','a','p','p','y'};//字符数组
char vate_2[] = { 'l','i','f','e'};
char* ret = strcat(vate_1, vate_2);
printf("%s\n", ret);
return 0;
}
结果:
持续闪烁,最后程序挂掉。
如果给这连个非NULL结尾的字符序各自加上NULL,结果会不会有所改变 呢?
上代码:
int main()
{
char vate_1[20] = { 'h','a','p','p','y','\0'};//在字符数组尾部添加'\0'
char vate_2[] = { 'l','i','f','e','\0'};
char* ret = strcat(vate_1, vate_2);
printf("%s\n", ret);
return 0;
}
结果:
最后确实达到了我们的预期,但这是为什么呢?
情境二:
strcat函数可以自己(参数)调用自己(参数)吗?
上代码:
int main()
{
char vate_1[20] = "happy";
char vate_2[] = "life";
char* ret = strcat(vate_1, vate_1);
printf("%s\n", ret);
return 0;
}
结果:
接下来开始解析,小伙伴们,能看到这已经很棒啦!但请继续坚持,我保证最后你将会有所收获!
情境1解析:
问题:如果strcat函数参数部分双方不为字符串('\0’结尾),结果是如何?
如果stact函数的两个参数都不以NULL字节结束,那么,可怜的strcat函数根本就找不到进行的入口,更不要想出口在哪里,所以它一直在寻找目标字符串中的NULL字节,等到最后确实找不到了,那么这个函数也就挂了!
尽管幸运找到那两个入口与出口,那么所返回的值也必定是非法的!
而上述最后所作的修改:是在目标字符序与源字符序中各添加“NULL”字节,有了入口与出口,strcat函数就满血复活啦!
情境2解析:
strcat函数可以自己(参数)调用自己(参数)吗?
这里出现了一个有趣的例子,也是对strcpy函数的挑战。strcat函数能够对目标字符串进行直接的改动,而在情境二中:strcat函数首先找到vate_1的NULL字节,将其修改为源字符串的首元素:‘h’,注意!strcat函数能够对目标元素进行直接的修改,这就意味着作为参数的源字符串vate_1中的NULL字节也被修改为了:‘h’,
之前我们提到:目标字符串中的NULL字节,相当于strcat函数的一个切入点,即:“入口”,而源字符串中的NULL字节则相当于一个结束标志,即:“出口”。而上述情况则相当于函数有了入口,却没了出口!
那会发生什么情况呢?答案一定是内存的越界操作,可怜的strcat函数在苦苦的寻找那个出口,但久久未能找到,最后导致程序崩溃!
依据strcat函数的运行原理,它无法完成对同一字符序的自我追加操作,但strncat函数则对此进行了修善,待完成strcat函数的模拟实现后,我们就见分晓!
(3)strcat函数的模拟实现
char *strcat( char *strDestination, const char *strSource );
我们举了两个strcat函数的使用情境,对此,从中我们可以总结出:
strcat函数在目标字符串结束符“NULL”上进行插入,将源字符串的首元素覆盖掉NULL字节,并逐个向后访问,直到找到源字符串尾部的NULL字节,并将其插入至目标字符串尾部后,函数才会停止。
接下来,我们就以此总结为实现目标,完成对strcat函数的模拟实现:
上代码:
//strcat模拟实现
char* my_strcat(char* value1_char, const char* value2_char)
{
assert(value1_char && value2_char);
char* temp = value1_char;//储存目标字符串的首地址
while (*value1_char)//寻找目标字符串'\0'
{
value1_char++;
}
while (*value1_char++ = *value2_char++)//在'\0'位置进行源字符串的拷贝
{
;
}
return temp;//返回
}
int main()
{
char value1_char[50] = "我热爱生活!也热爱学习!";
char value2_char[] = "愿世间充满欢乐!";
char* temp = my_strcat(value1_char, value2_char);
printf("%s", temp);
return 0;
}
//理解及思路
//1、保证目标字符串及源字符串的结尾都具备有‘\0’。
//2、目标字符串空间须是可修改的,且是足够大的。
//3、第一步需要找到目标字符串的‘\0’处。
//4、第二步,在目标字符串‘\0’处进行进行拷贝,将源字符串拷贝到目标字符串之后,直到'\0'结束。
//5、需要对目标字符串进行存地址,否则贸然返回目标字符串,所得到的将是尾部'\0\的地址,从而导致空白打印。
结果:
在这喧嚣内卷的世界中,我愿拼尽全力,去创那三分自留地,护一家长幼安详,与她安稳相守。也祝你们顺利!
(5)strcat函数的缺陷
要说strcat的缺陷,其实只要有两点:
1、不能作用与同一个参数的调用。
2、不具备灵活调控性。
(二)、strncat函数·(有限制)字符串追加
strncat函数也加入了限制数,因为限制数的加入,使得strcat函数不再像是一匹充满野性的野马,不受控制,反之它将被我们很好的使用。
(1)strncat函数原型解析
char *strncat( char *strDest, const char *strSource, size_t count );
与上方所探索的strncpy很相似,它是控制拷贝数。而strncat函数的限制数,则是用来控制追加数的,知识点可以互通,相信看到这里伙伴们早已经有了思路!
接下来就让我们实验出真理吧!
(2)strncat函数的使用
带着这两个问题进入以下环节:
1、strncat函数可以作用于同一个参数吗?
2、如果限制数比原字符串参数数位:“大了”或者“小了”会如何?
情境一:
strncat函数可以作用于同一个参数吗?
上代码:
//strncat函数可以作用于同一个参数吗?
int main()
{
char vate_1[20] = "joy";
char vate_2[] = "and sadness";
int count = 0;
puts("请输入追加数:");
scanf("%d", &count);//限制数输入:4
char* ret = strncat(vate_1, vate_1, count);
printf("%s\n", ret);
return 0;
}
结果:
情境二:
如果限制数比原字符串参数数位:“大了”或者“小了”会如何?
(限制数大于源字符串位数)上代码:
//限制数大于源字符串位数,会发生什么?
int main()
{
char vate_1[20] = "joy";
char vate_2[] = " and sadness";
int count = 0;
puts("请输入追加数:");
scanf("%d", &count);//限制数输入值:50
char* ret = strncat(vate_1, vate_2, count);
printf("%s\n", ret);
return 0;
}
结果:
(限制数小于源字符串位数)上代码:
//限制数小于源字符串位数,会发生什么?
int main()
{
char vate_1[20] = "joy";
char vate_2[] = " and sadness";
int count = 0;
puts("请输入追加数:");
scanf("%d", &count);//限制数输入值:3
char* ret = strncat(vate_1, vate_2, count);
printf("%s\n", ret);
return 0;
}
结果:
情境一解析:
strncat函数可以作用于同一个参数吗?
这是一个很刁钻的角度,因为如果按照strcat函数的运作来说,这无疑将是一个无法解决的难题。但strncat对此进行了优化。
在两个参数都为同一字符串的情况下,追加无疑会改变参数本身元素,而缺少了一个源字符串末尾NULL字节的出口,无疑又会面临着死循环的问题。
所以,当strncat函数中的限制数为0时,它就会给追加目标数据的末尾加上一个NULL字节。
情境二解析:
如果限制数比原字符串参数数位:“大了”或者“小了”会如何?
面对这种情况,我们可以很轻松的去理解它:
1、当限制数大于源字符串时:其会先将源字符串全部追加至目标字符串中,但源字符串末尾蕴含NULL字节,所以会停止。
2、但限制数小于源字符串时:其会追加限制数要求的几个数位,若限制数为0,那么将为目标字符串末尾追加NULL字节。
(3)strncat函数的模拟实现
上代码:
//strncat函数模拟实现
char* my_strncat(char* v_1, const char* v_2, size_t count)
{
assert(v_1 && v_2);//断言判断是否为空指针
char* ret = v_1;//将目标字符串首地址赋值给字符指针:ret用作返回值
while (*v_1)
{
v_1++;//找到目标字符串参数中的'\0'(找到:入口)
}
while ((count--))//限制数为0时停止
{
if ((*v_1++ = *v_2++) == '\0')
//当v_1为'\0'时返回指针。
//此情况是在两个参数都为NULL字节结束的情况下发生
{
return ret;
}
}
*v_1 = '\0';
//而在末尾补NULL字节是在源字符序结尾不为NULL字节时,或同一参数追加时进行。
return ret;
}
int main()
{
char vate_1[25] = "道阻且长,";
char vate_2[] = "行则将至!";
char* ret = my_strncat(vate_1, vate_2, 100);
printf("%s\n", ret);
return 0;
}
结果:
三、字符串比较操作
字符串比较是字符串操作常用到的一种解法,它用于比较两个字符串之间(无限制)或两个字符串中指定元素(有限制)的大小。
(一)、strcmp函数·(无限制)字符串比较
strcmp函数用于比较两个字符串之间的大小,至于是如何进行比较的、及依据什么去判断的?我们接下来开始探索:
(1)strcmp函数原型解析
int strcmp( const char *string1, const char *string2 );
strcmp函数的两个参数是字符指针,它所返回的值是:int(整形)。在函数内部规定了以下标准:
1、当(目标字符串)string_1 > (源字符串)string_2 时, 返回大于0的数。
2、当(目标字符串)string_1 < (源字符串)string_2 时, 返回小于0的数。
3、当(目标字符串)string_1 ==(源字符串)string_2 时, 返回0。
那么它究竟是怎么判断的呢?请看图演:
而字符比较大小的依据,则取决于ASCII码值,逐个进行大小的比对。
(2)strcmp函数的使用
int strcmp( const char *****string1, const char *****string2 );
接下来我们就来使用strcmp函数,而在此函数中,博主只有一个问题:
strcmp函数能对汉字进行大小的比较吗?
我们实践出真理吧!
上代码:
比较:张三与李三
//strcmp函数能否比对汉字的大小?
//张三与李三
int main()
{
char v_1[] = "张三";
char v_2[] = "李三";
int ret = strcmp(v_1, v_2);
printf("%d\n", ret);
return 0;
}
结果:
比较:李三与张三
//strcmp函数能否比对汉字的大小?
//李三与张三
int main()
{
char v_1[] = "李三";
char v_2[] = "张三";
int ret = strcmp(v_1, v_2);
printf("%d\n", ret);
return 0;
}
结果:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
_50,text_Q1NETiBARHVvbmku,size_20,color_FFFFFF,t_70,g_se,x_16)
而字符比较大小的依据,则取决于ASCII码值,逐个进行大小的比对。
(2)strcmp函数的使用
int strcmp( const char *****string1, const char *****string2 );
接下来我们就来使用strcmp函数,而在此函数中,博主只有一个问题:
strcmp函数能对汉字进行大小的比较吗?
我们实践出真理吧!
上代码:
比较:张三与李三
//strcmp函数能否比对汉字的大小?
//张三与李三
int main()
{
char v_1[] = "张三";
char v_2[] = "李三";
int ret = strcmp(v_1, v_2);
printf("%d\n", ret);
return 0;
}
结果:
比较:李三与张三
//strcmp函数能否比对汉字的大小?
//李三与张三
int main()
{
char v_1[] = "李三";
char v_2[] = "张三";
int ret = strcmp(v_1, v_2);
printf("%d\n", ret);
return 0;
}
结果:
[外链图片转存中…(img-Fm9MYHOE-1715791085449)]
[外链图片转存中…(img-i6sYrADL-1715791085450)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!