字符函数和字符串函数 last part
前提
我们接下来要说的的函数是:strncpy、strncat、strncmp,这三个之间和strcpy、strcat和strcmp有啥不同呢?
差别在于strcpy、strcat、strcmp只有两个参数,而strncpy、strncat、strncmp有三个参数,那么多了哪个参数呢?
这两者之间的区别是一个没有限制字符长度,一个有限制字符的长度
strncpy的使用
有字符限制的字符复制函数,可以指定num个字符复制到目标字符串,若字符串长度小于num,则在拷贝玩字符串后在目标的后面加上'\0',直到num个
函数结构:
char * strncpy ( char * destination, const char * source, size_t num );
返回值:
返回目标字符串的内容
代码:
int main()
{char arr1[] = "abcdef";char arr2[20] = "xxxxxxxxx";strncpy(arr2, arr1, 5);printf("%s\n", arr2);return 0;
}
运行结果:
strncat的使用
有字符限制的字符追加函数,可以指定源字符串追加几个字符到目标字符串中再追加一个'/0',若是源字符串的数量小于num,之会将字符串中的'\0'追加到目标字符串的末尾
函数结构:
char * strncat ( char * destination, const char * source, size_t num );
返回值:
返回目标字符串的内容
代码:
int main()
{char arr1[] = "heil";char arr2[20] = "sieg";strncat(arr2, arr1, 3);printf("%s\n", arr2);return 0;
}
运行结果:
strncmp的使用
有字符限制的字符比较函数,如果str1==str2,则会返回0,若是str1>str2,则会返回1,反之返回-1
函数结构:
int strncmp ( const char * str1, const char * str2, size_t num );
返回值:
str1==str2 | 返回值为0 |
str1>str2 | 返回值为正值 |
str1<str2 | 返回值为负值 |
代码:
int main()
{char arr1[] = "abcdef";char arr2[] = "abs";int s = strncmp(arr2, arr1, 2);printf("%d\n", s);return 0;
}
运行结果:
strstr的使用与模拟实现
strstr函数专门在字符串中找子字符串的函数,不包含'\0',但是遇到'\0'时会停止
函数结构:
const char * strstr ( const char * str1, const char * str2 );
char * strstr (char * str1, const char * str2 );
返回值:
str1==str2 | 返回str2中str1的指针 |
str1!=str2 | 返回NULL |
代码:
int main()
{char arr1[] = "abcdef";char arr2[] = "cd";const char* ret = strstr(arr1, arr2); //一定需要有接收值if (ret == NULL)printf("没找到");elseprintf("%s\n", ret);return 0;
}
运行结果:
接下来进行模拟实现
模拟实现前会有好几种情况
第一种:寻找字符串在前头
第二种:需要寻找的字符串在中间
第三种:cur必须移动才能找到匹配的字符
第四种:找不到匹配的字符
第五种特殊情况:arr2为空语句
现在开始写代码,我们用两个字符串来举例来举例
我们看到cur = str1,cur等于str1
cur不等于’\0‘,进入循环,进入循环后看到s1=cur,cur等于s1;s2 = str2,str2等于s2
在看向下一个while循环,条件为*s1 !='\0' && s2 !='\0' && *s1==s2,先看向字符串首字符s1的字符为a,s2的字符为b,s1和s2!='\0',s1和s2不相等,故不进入循环
再看向if语句,我们看到s2不等于'\0',故不返回cur,cur和s1往后一位
再看向while循环s1和s2!='\0',s1==s2,进入循环,s1和s2往后一位
再进行循环s1和s2!='\0',s1==s2,进入循环s1和s2往后一位
再进入循环s1和s2!='\0',s1和s2不相等,不进入循环,s2不等于'\0',不返回cur,cur往后一位
cur!='\0'重新进入循环,s1重置到cur位置,s2重置到首位字符
s1和s2!='\0'且s1==s2,进入循环s1和s2各往后一位
再进行循环,s1和s2!='\0',s1==s2,再各往后一位
再进入循环,s1和s2!='\0'且s1==s2,s1和s2再往后一位
再进入循环,s1!='\0',s2=='\0'且s1!=s2,不进入循环,s2==‘\0’返回cur,循环到此结束
虽然s2=='\0'了,可是s1还没找到‘\0’,当s1找到'\0'是我们可以再函数中加上return NULL来返回空指针
const char* mien_strstr(const char*str1,const char*str2)
{const char* s1 = NULL;const char* s2 = NULL;const char* cur = str1;while (*cur){s1 = cur;s2 = str2;while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2){s1++;s2++;}if (*s2 == '\0')return cur;cur++;}return NULL;
}
除此之外,还有一种特殊场景,就是子字符串没有字符的(第五种情况),我们可以让它返回str1并加上assert来防止空指针,下面是全部的代码
const char* mien_strstr(const char*str1,const char*str2)
{const char* s1 = NULL;const char* s2 = NULL;const char* cur = str1;assert(str1 && str2);if (str2 == '\0')return str1;while (*cur){s1 = cur;s2 = str2;while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2){s1++;s2++;}if (*s2 == '\0')return cur;cur++;}return NULL;
}
int main()
{char arr1[] = "abbbcdef";char arr2[] = "bbc";const char* ret =mien_strstr(arr1, arr2);if (ret == NULL)printf("没找到");elseprintf("%s\n", ret);return 0;
}
现在来运行看看能不能找到字符串
运行结果:
strtok的使用
strtok(字符切割函数)是用于提取字符串中被分割符号分割的的字符串函数
函数结构:
char * strtok ( char * str, const char * sep );
sep会指向一个字符串,定义用作分隔符号的字符集合
str指定一个字符串,里面包含了0个或多个由sep字符串中一个或多个分割符号分割的标记
strtok会在str中找到下一个分割标记并用'\0'结尾,并返回指向这一个标记的指针
返回值:
如过返回的不是NULL,函数会找str的第一个标记,strtok将他保存在字符串中的位置
如果返回的是NULL,函数会在字符中被保存的位置开始,查找下一个标记
如果不存在其他标记,则返回NULL
代码:
int main()
{char arr[] = "derek@gmail.com";char tmp[40] = {0};strcpy(tmp, arr);const char* sep = "@ .";char * p = strtok(tmp, sep);printf("%s\n", p);p = strtok(NULL, sep);printf("%s\n", p);p = strtok(NULL, sep);printf("%s\n", p);return 0;
}
运行结果:
其实这个代码很冗余,可以使用for循环来完善代码
int main()
{char arr[] = "derek@gmail.com";char tmp[40] = { 0 };strcpy(tmp, arr);const char* sep = "@ .";char* p = NULL;for (p = strtok(tmp, sep); p != NULL;p = strtok(NULL,sep)){printf("%s\n", p);}return 0;
}
运行结果:
还可以再加上其他的字符串,而除了字符串其他的地方都不用改
int main()
{char arr[] = "derek@gmail.com.ch";char tmp[40] = { 0 };strcpy(tmp, arr);const char* sep = "@ .";char* p = NULL;for (p = strtok(tmp, sep); p != NULL;p = strtok(NULL,sep)){printf("%s\n", p);}return 0;
}
运行结果:
第一个代码每多一个字符串就需要增加一行代码
int main()
{char arr[] = "derek@gmail.com.ch";char tmp[40] = {0};strcpy(tmp, arr);const char* sep = "@ .";char * p = strtok(tmp, sep);printf("%s\n", p);p = strtok(NULL, sep);printf("%s\n", p);p = strtok(NULL, sep);printf("%s\n", p);p = strtok(NULL, sep);printf("%s\n", p);return 0;
}
运行结果:
strerror的使用
strerror可以将参数部分的错误码和对应的错误信息传返回
int main()
{for (int a = 0; a < 10; a++){char* p = strerror(a);printf("%d,%s\n", a, p);}return 0;
}
win11+vs2022的运行结果:
在不同的系统中C语言的表准库的实现中都规定一些错误码,一般都是在errno.h这个头文件中解释,C语言启动时就会使用一个全局的变量errno来记录当前的错误码,不过启动的时候为0,表示没有错误,当我们在使用标准库时出现了某种错误就会将对应的错误码存放在errno中,而一个数字是很难理解是啥意思,所以每一个错误码都是有信息的,而strerror就是专门将这些错误信息返回
范例1:
int main()
{FILE* pf = fopen("test.txt","r");if (pf == NULL){printf("%s\n", strerror(errno));return 1;}return 0;
}
运行结果:
也可以了解下perror函数,perror会直接将错误信息打印出来,perror会打印参数中的字符串,再打印一个冒号最后才打印错误信息
范例2:
int main()
{FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("hehe");return 1;}return 0;
}
输出结果: