目录
一、 strncpy、strncat、strncmp函数的使用
🔥个人主页:艾莉丝努力练剑
🍓专栏传送门:《C语言》
🍉学习方向:C/C++方向
⭐️人生格言:为天地立心,为生民立命,为往圣继绝学,为万世开太平
前言:前面几篇文章介绍了c语言的一些知识,包括循环、数组、函数、VS实用调试技巧、函数递归、操作符、指针等,在这篇文章中,我将介绍C语言内存函数的一些重要知识点!对字符函数和字符串函数感兴趣的友友们可以在评论区一起交流学习!
一、 strncpy、strncat、strncmp函数的使用
strncpy
strncat
strncmp
这些都是长度受限的字符串函数
(一)strncpy
我们在【字符函数和字符串函数(上)】中已经介绍了strcpy,前者是长度不受限的字符串函数,本文介绍的strncpy则是长度受限的字符串函数,我们用C库中对两者的描述来对比一下这两个函数:
前面的参数都一样,就多了一个参数size_t num,多的这个参数就是strncpy比strcpy多的那个“n”,n其实就是“个数”的意思。接下来我们从三板斧开始,详细介绍这个字符串函数——
代码原型在上面对C库的引用处已经给了,这边我们放到代码块里面再观察一下:
char* strncpy(char* destination, const char* source, size_t num);
三板斧:
功能:字符串拷贝;将source指向的字符串拷贝到destination指向的空间中,最多拷贝num个字符。
参数:
destination:指针,指向目的地空间;
source:指针,指向源头数据;
num:从source指向的字符串中最多拷贝的字符个数。
返回值:
strncpy函数返回的目标空间的起始地址。
1、代码演示:
接下来我们举几个例子,方便友友们理解:
比如说我们从数组(1)当中拷贝5个字符到数组(2)中去,可以这么做:
#include<stdio.h>
int main()
{
char arr1[10] = "abcdefghi";
char arr2[20] = { 0 };
strncpy(arr2, arr1, 5);
return 0;
}
打开监视观察一下:
这里我们的数组(1)里面有10个字符,完全够要求的5个,如果数组(1)中只有三个呢?
#include<stdio.h>
int main()
{
char arr1[10] = "abc";
char arr2[20] = { 0 };
strncpy(arr2, arr1, 5);
return 0;
}
监视打开,我们观察一下:
打印了全部的三个字符,后面两个变成了“\0”,那我想问友友们一个问题:
这两个\0是从arr1的“abc”后面拷贝了两个\0过来,还是我们arr2这边自己补上的呢?
是不是很难凭空想出来?没关系,我们验证一下就好了,那怎么验证呢?我们这样:
#include<stdio.h>
int main()
{
char arr1[10] = "abc";
char arr2[20] = "xxxxxxxxxxxx";
strncpy(arr2, arr1, 5);
return 0;
}
打开监视,观察一下拷贝情况:
我们可以看到,abc拷贝完从arr1拿过来到arr2还不够5个,我们这边补了2个\0,最终凑够了5个,什么意思呢?也就是说如果你这个字符串提前结束了(遇到了\0,比如这里的arr1拷贝完c就遇到了\0,像这种我们就就此打住,不再往后拷贝了),如果你不够5个,就主动补够5个。
如果友友们还不明白,我们把代码再改一改,让大家观察得更清楚一点:
#include<stdio.h>
int main()
{
char arr1[10] = "abc\0defgh";
//注意这里已经有转义字符\0了,\0算一个字符,数组arr1只能存放10个字符
char arr2[20] = "xxxxxxxxxxxx";
strncpy(arr2, arr1, 5);
return 0;
}
监视一下:
拷贝完abc,遇到了\0,strncpy认为能拷贝都拷贝完了,补了2个\0(后面两个\0是来凑数的) 。
strncpy总结:
如果源头数据是够的,就拷贝num个字符;如果源头数据不够,拷贝完再给你补够num个(按\0补齐)。
2、比较strcpy函数和strncpy函数
那strcpy和strncpy有什么区别呢?
1、strncpy多了第三个参数;
2、strncpy可以指定,你想拷贝几个,就拷贝几个;
3、 strcpy会把源头数据拷贝到目标空间里去,一直拷贝到\0,连\0也会被拷贝到目标空间去,它没办法灵活地指定要拷贝几个,它只会一口气给你干到\0,没办法指定拷贝几个。
总结一下:
(1)strcpy 函数拷贝到 \0 为止,如果目标空间不够的话,容易出现越界行为;
(2)strncpy函数指定了拷贝的长度,源字符串不一定要有 \0 ,同时在设计参数的时候,就会多一层思考:目标空间的大小是否够用,strncpy 相对 strcpy 函数更加安全。
(二) strncat
和strncpy类似,也只是多了个参数,我们还是来看C库的介绍:
都是把原字符串内容追加到目标空间后面,那么这个参数是什么意思呢?限制的是追加几个吗?其实就是这个意思。
老样子,我们先看一下代码原型,再来三板斧:
代码原型:
char* strncat(char* destination, const char* source, size_t num);
三板斧:
功能:字符串追加,将source指向的字符串的内容追加到destination指向的空间,最多追加num个字符。
参数:
destination:指针,指向目的地空间;
source:指针,指向源头数据;
num:最多追加的字符个数。
返回值: strncat函数返回的目标空间的起始地址。
1、代码演示:
#include<stdio.h>
int main()
{
char arr1[10] = "abc";
char arr2[20] = "xxxxx";
strncat(arr2, arr1, 3);
return 0;
}
监视打开,观察情况:
那这里又有一个问题:我们把arr1拿过来追加到目标空间后面,那c后面的\0有没有拿过来?我们可能不太确定,这里我们就测试一下,看看\0有没有拿过来——
#include<stdio.h>
int main()
{
char arr1[10] = "abc";
char arr2[20] = "xxxxx\0xxxxxxxxxx";
strncat(arr2, arr1, 3);
return 0;
}
监视打开:
观察上图我们可以看到,这里我们设计追加3个,到目标空间之后后面带了个\0,这个\0是abc后面的\0还是说是补给我们的\0呢?不清楚。那我们再改一下:
#include<stdio.h>
int main()
{
char arr1[10] = "abc";
char arr2[20] = "xxxxx\0xxxxxxxxxx";
strncat(arr2, arr1, 2);
return 0;
}
打开监视,观察一下:
这里我们就可以看出来了:追加了2个,只追加了“ab”,后面还是有个\0, 也就是说这个\0不是arr1后面的\0,所以我们就会发现,追加2个就会在追加完2个后补一个\0,追加3个就会在追加完3个后补一个\0,这个\0是strncat送我们的,不是因为abc后面有个\0,而是因为让追加几个就追加几个,追加完之后在后面补一个\0。
我们把num改大一点呢?比arr1中存放的字符数多一点呢?会怎么样?
#include<stdio.h>
int main()
{
char arr1[10] = "abc";
char arr2[20] = "xxxxx\0xxxxxxxxxx";
strncat(arr2, arr1, 6);
return 0;
}
打开监视:
这里并没有用\0补到6个 ,那这里是什么情况?其实这里是num够的情况,abc追加完之后,把\0也拿过来了,就是说如果只有abc\0,那把\0拿过来之后就不会补\0了。但是,如果arr1里面的字符是够num的个数的话,我们拿过来追加到目标空间会在后面补1个\0。
比起strcat,strncat不管怎么说我们是可以控制追加的个数的 。
2、strcat和strncat对比
(1)参数不同, strncat 多了一个参数;(2)strcat 函数在追加的时候要将源字符串的所有内容,包含 \0 都追加过去,但是 strncat 函数指定了追加的长度;(3)strncat 函数中源字符串中不一定要有 \0 了;(4)strncat 更加灵活,也更加安全。
(三)strncmp
这里博主就不赘言了,直接看C库是怎么说的:
这个函数是str1和str2相互比较,那么这个参数是什么意思?难道说是比较str1里面和str2里面的前num个字符吗?是的,就是这个意思。
函数原型:
int strncmp(const char* str1, const char* str2, size_t num);
三板斧——
功能:字符串比较;比较 str1 和 str2 指向的两个字符串的内容,最多比较 num个 字符。参数:str1 :指针,指向一个比较的字符串;str2 :指针,指向另外一个比较的字符串;num :最多比较的字符个数。返回值:标准规定——(1)第一个字符串大于第二个字符串,则返回大于0的数字;(2)第一个字符串等于第二个字符串,则返回0;(3)第一个字符串小于第二个字符串,则返回小于0的数字。
1、代码演示:
#include<stdio.h>
int main()
{
char arr1[10] = "abcqw";
char arr2[20] = "abcdef";
int r = strncmp(arr2, arr1, 3);
if (r > 0)
{
printf(">\n");
}
else if (r < 0)
{
printf("<\n");
}
else
printf("==\n");
return 0;
}
2、strcmp和strncmp比较
(1)参数不同;(2)strncmp可以比较任意长度了;(3)strncmp函数更加灵活,更加安全。
长度受限制和不受限制的这六个函数未来我们根据自己的需要使用即可。
二、strstr的使用和模拟实现
strstr就是string string。
效果:是在一个字符串中找另外一个字符串第一次出现的位置——
如果找到了,则返回地址;如果找不到,则返回NULL。
(一)使用
函数原型:
char* strstr(const char* str1, const char* str2);
三板斧——
功能: strstr 函数,查找 str2 指向的字符串在 str1 指向的字符串中第一次出现的位置;简而言之: 就是在一个字符串中查找子字符串。注意: strstr 的使用得包含<string.h>。参数:str1 :指针,指向被查找的字符串;str2 :指针,指向要查找的字符串。返回值:(1)如果str1指向的字符串中存在str2指向的字符串,那么返回第一次出现位置的指针;(2)如果str1指向的字符串中不存在str2指向的字符串,那么返回NULL。
(二)代码演示
#include<stdio.h>
char* my_strstr(const char* str1, const char* str2)
{
}
int main()
{
char arr1[] = "heheabcdefabcdef";
char arr2[] = "deq";
char* p = my_strstr(arr1, arr2);
if (p != NULL)
{
printf("找到了,%s\n", p);
}
else
{
printf("找不到\n");
}
return 0;
}
两种情况如下图所示:
那么我们就可以进行模拟实现了 。
(三)模拟实现
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
char* strstr(const char* str1, const char* str2)
{
char* cp = (char*)str1;
char* s1, * s2;
if (!*str2)
return((char*)str1);
while (*cp)
{
s1 = cp;
s2 = (char*)str2;
while (*s1 && *s2 && !(*s1 - *s2))
s1++, s2++;
if (!*s2)
return(cp);
cp++;
}
return(NULL);
}
int main()
{
char arr1[] = "abbbcdef";
char arr2[] = "bbc";
char* p = my_strstr(arr1, arr2);
if (p != NULL)
{
printf("找到了,%s\n", p);
}
else
{
printf("找不到\n");
}
return 0;
}
strstr函数的实现有多种,可以暴力查找,也有一种高效一些的算法:KMP,有兴趣的友友们可以去学习一下。
三、strtok函数的使用
代码原型:
char* strtok(char* str, const char* delim);
三板斧——
功能 :(1)分割字符串: 根据 delim 参数中指定的分隔符,将输入字符串 str 拆分成多个子字符串;(2)修改原始字符串: strtok 会直接在原始字符串中插入 '\0' 终止符,替换分隔符的位置,因 此原始字符串会被修改。参数:(1) str :首次调用时传入待分割的字符串;后续调用传入 NULL ,表示继续分割同一个字符串;(2) elim: 包含所有可能分隔符的字符串(每个字符均视为独立的分隔符)。返回值:(1)成功时返回指向当前子字符串的指针;(2)没有更多子字符串时返回 NULL 。
1、首 次调用:传入待分割字符串和分隔符;2、 后续调用:传入 NULL 和相同的分隔符,继续分割;3、 结束条件:当返回 NULL 时,表示分割完成。
(一)代码演示:
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "Jqjlmt@yeah.ret";
char sep[] = "@.";
char buf[20] = { 0 };
strcpy(buf, arr);
//buf
//Jqjlmt\0yeah\0ret
char* p = strtok(buf, sep);
printf("%s\n", p);//Jqjlmt
p = strtok(NULL, sep);//yeah
printf("%s\n", p);
p = strtok(NULL, sep);//net
printf("%s\n", p);
p = strtok(NULL, sep);//返回NULL
printf("%s\n", p);//(null)
return 0;
}
但这个代码还是太low了,我们能不能很好地改进一下呢?
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "Jqjlmt@yeah.ret";
char sep[] = "@.";
char buf[20] = { 0 };
strcpy(buf, arr);
char* p = NULL;
for (p = strtok(buf, sep); p != NULL; p = strtok(NULL, sep))
{
printf("%s\n", p);
}
return 0;
}
这样写无论多长都可以分割出来,只要根据字符长度更改char buf[ ] = { 0 }; 中的数值就好了。
输出结果:
(二)注意事项
1、破坏性操作: strtok 会直接修改原始字符串,将其中的分隔符替换为 ' \0 ' 。如果需要保留原字符串,应先拷贝一份;2、连续分隔符:多个连续的分隔符会被视为单个分隔符,不会返回空字符串;3、空指针处理:如果输入的 str 为 NULL 且没有前序调用,行为未定义。
四、strerror函数的使用
str是字符串的意思,error是错误的意思。
代码原型:
char* strerror(int errnum);
C语言的库函数在使用的时候,如果发生了错误,会将一个错误码记录到errno的变量中。
注:错误码——比如404(网页加载失败的界面)。
三板斧——
功能:
(1)strerror 函数可以通过参数部分的 errnum 表示错误码,得到对应的错误信息,并且返回这个错误信息字符串首字符的地址;
(2)strerror 函数只针对标准库中的函数发生错误后设置的错误码的转换;
(3)strerror 的使用需要包含<string.h>。
( 在不同的系统和C语言标准库的实现中都规定了一些错误码,一般是放在<errno.h>这个头文件中说明的,C语言程序启动的时候就会使用一个全局的变量errno来记录程序的当前错误码,只不过程序启动的时候errno是0,表示没有错误,当我们在使用标准库中的函数的时候发生了某种错误,就会将对应的错误码,存放在errno中,而一个错误码的数字是整数,很难理解是什么意思,所以每一个错误码都是有对应的错误信息的。strerror函数就可以将错误码对应的错误信息字符串的地址返回。)参数:errnum :表示错误码;这个错误码一般传递的是 errno 这个变量的值,在C语言有一个全局的变量叫: errno ,当库函数的调用发生错误的时候,就会将本次错误的错误码存放在 errno 这个变量中,使用这个全局变量需要包含一个头文件<errno.h> 。返回值:函数返回通过错误码得到的错误信息字符串的首字符的地址。
(一) 代码演示
#include<stdio.h>
#include<string.h>
int main()
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d: %s\n", i, strerror(i));
}
return 0;
}
结果输出:
当然,我们也可以自己创建一个文件,去读这个文件(我们在【深入详解文件操作】一文中详细讲述了fopen、fclose这两个打开/关闭文件的函数):
#include<stdio.h>
#include<string.h>
int main()
{
//C语言可以打开文件
//fopen函数
//如果以读的方式打开文件,文件是必须要存在的,如果文件不存在,则打开文件失败
//fopen函数就会将错误码放在errno
//同时函数会返回NULL
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//读文件
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
输出结果:
(二)perror
注:perror相当于printf+strerror 。
代码原型:
void perror(const char* str);
代码演示:
#include<stdio.h>
#include<string.h>
int main()
{
//C语言可以打开文件
//fopen函数
//如果以读的方式打开文件,文件是必须要存在的,如果文件不存在,则打开文件失败
//fopen函数就会将错误码放在errno
//同时函数会返回NULL
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("test");
//test:错误信息
return 1;
}
//读文件
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
输出结果:
以上就是字符串函数剩余的全部内容了,这次博主是一次性放送啦!
结语
往期回顾:
字符函数和字符串函数(一):字符分类函数、字符转换函数、strlen的使用和模拟实现、strcpy的使用和模拟实现、strcat的使用和模拟实现、strcmp的使用和模拟实现
C语言指针深入详解(六):sizeof和strlen的对比,【题解】数组和指针笔试题解析、指针运算笔试题解析
结语:本篇文章就到此结束了,本文为友友们分享了一些字符函数和字符串函数相关的重要知识点,如果友友们有补充的话欢迎在评论区留言,下一期我们将介绍这个C语言内存函数剩下的一些重要知识点,感谢友友们的关注与支持!