3.长度受限制的字符串函数——strncpy,strncat,strncmp
为什么会出现这些函数呢?
前面三个函数压根不关心到底拷贝,追加,比较了几个字符。它们只关心是否找到了\0,一旦找到了\0就会停止。这样的话如果目标空间不够大,会造成越界。这些特点就会让人们决定它是不安全的,并且我们之前发现如果自己给自己追加会出现死循环的现象,因为这些缺点,下面介绍较安全的函数。
strncpy函数:
strncpy
函数原型:
函数作用:
长度受限的字符串拷贝
注意事项:
拷贝num个字符从源字符串到目标空间。
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
模拟实现:
char* my_strncpy(char* dest, const char* src, int num) { assert(dest && src); char* ret = dest; while (num) { if (*src == '\0')//此时说明src指针已经指向了待拷贝字符串的结束标志'\0'处,src指针就不用再++了 { *dest = '\0'; dest++; } else { *dest = *src; dest++; src++; } num--; } return ret; }
strncat函数
strncat
函数原型:
注意事项:
从源字符串的第一个字符开始往后数num个字符追加到目标空间的后面,外加一个终止字符。
如果源字符串的长度小于 num,则仅复制终止字符之前的内容。
模拟实现:
char* my_strncat(char* dest, const char* src, int sz) { assert(dest && src); char* ret = dest; //找目标空间的\0 while (*dest != '\0') { dest++; } //追加 while (sz) { *dest++ = *src++; sz--; } *dest = '\0'; return ret; }
strncmp函数:
strncmp
函数原型:
模拟实现:
int my_strncmp(const char* str1, const char* str2, int sz) { assert(str1 && str2); while (sz) { if (*str1 < *str2) { return -1; } else if (*str1 > *str2) { return 1; } else if(*str1 == '\0'||*str2 =='\0')//当有一个为'\0',说明比较就可以结束了 { if (*str1 == '\0' && *str2 == '\0')//如果二者都是'\0',说明两个字符串相等 { return 0; } else if(*str1 =='\0')//如果str1为'\0',说明str1小,str2大 { return -1; } else//如果src为'\0',说明str1大,str2小 { return 1; } } sz--; str1++; str2++; } }
4.字符串查找
a.strstr——判断是否为子字符串
strstr
函数原型:
函数作用:
判断是否为子字符串
注意事项:
在str1指向的字符串中查找str2指向的字符串
返回一个指向str1中第一次出现的str2的指针
如果 str2 不是 str1 的一部分,则返回一个空指针NULL
匹配过程不包括终止空字符,但它到此为止
BF算法(暴力枚举)模拟函数实现:
char* my_strstr(const char* str1, const char* str2) { assert(str1 && str2); if (*str2 == '\0') { return (char*)str1; } const char* s1 = NULL; const char* s2 = NULL; const char* cp = str1; while (*cp) { s1 = cp; s2 = str2; while (*s1 !='\0' && *s2!='\0' && *s1 == *s2) { s1++; s2++; } if (*s2 == '\0') { return (char*)cp; } cp++; } return NULL; }
KMP算法模拟实现:
void Getnext(char* next, char* str2) { next[0] = -1; next[1] = 0; int k = 0; int i = 2; while (i <= strlen(str2)) { if (str2[k] == str2[i-1]) next[i] = k + 1; else if (str2[i] != str2[0]) next[k] = 0; else if (str2[i] == str2[0]) next[k] = 1; k++; i++; } } char* KMP(const char* str1, const char* str2) { assert(str1 && str2); int* next = (int*)malloc(sizeof(int) * strlen(str2)); assert(next); Getnext(next, str2); int i = 0; int j = 0; while (i < strlen(str1) && j < strlen(str2)) { if (j==-1||str1[i] == str2[j]) { i++; j++; } else { j = next[j]; } } free(next); if (i == strlen(str2)) return &str1[i - j]; return NULL; }
关于KMP算法可以通过这两篇博客来了解:
b.strtok——一个奇怪的函数
strtok
函数原型:
作用:
通过分隔符分割字符串
注意事项:
1.sep参数是个字符串,定义了用作分隔符的字符集合第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
2.strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
3. strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
4.strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
5.如果字符串中不存在更多的标记,则返回 NULL 指针。
这个函数很奇怪,让我举个栗子:
用来分割字符串。一个例子,例如我的邮箱是xxxxx@163.com。这个邮箱起始由三部分组成,一个是xxxxxx,一个是163,一个是com。我现在想把这三部分分开。
当然,我们可以用for循环简写:
该函数模拟较复杂,我们就先不模拟了。
c.strerror——错误信息查找
strerror
函数原型:
作用:
把错误码转换成错误信息
注意事项:
C语言的库函数在运行的时候,如果发生错误,就会把错误码存在一个变量中,这个变量是:errno
返回的指针指向静态分配的字符串(错误信息字符串)
一些栗子:
用法:
int main() { //打开文件 FILE* pf = fopen("test.c", "r"); if (pf == NULL) { printf("%s\n", strerror(errno));//需要包含头文件#include<errno.h> return 1; } //读文件 //关闭文件 fclose(pf); return 0; } //打开失败时屏幕显示: No such file or directory
关于这里的errno,C语言的库函数在运行的时候,如果发生错误,就会将错误码存在一个变量中,这个变量是:errno,错误码是一些数字:1 2 3 4 5,我们需要讲错误码翻译成错误消息。
perror函数:
perror
实际上就是printf和strerror的结合!
上面是字符串相关的函数,下面是一些字符分类的函数:
字符转换函数:
tolower:将大写字母转换为小写字母
int tolower ( int c );
toupper:将小写字母转换成大写字母
int toupper ( int c );
这些函数我就不一一讲解了,家人们有兴趣的话可以去官网了解一下哦!