C语言笔记(鹏哥)上课板书+课件汇总(KMP算法的动态规划简易处理+字符函数和字符串函数)
一、目录
kmp动态规划简易处理next数组+字符函数与字符串函数
- 一、目录
- 二、引言
- C语⾔标准库中提供了⼀系列库函数
- 三、字符分类函数(字符相关的函数)
- 推荐一个网站
- 四、字符转换函数(字符相关的函数)
- 五、strlen(字符串相关的函数)
- strlen函数的模拟实现
- 方式一:计数器记录字符个数
- 方式二:**指针 - 指针 = 指针之间的元素个数**
- 方式三:递归(一种不创建临时变量就能计算字符串长度的方法)
- 六、strcpy的使用和模拟
- strcpy的模拟实现:
- 七、strcat的使用和模拟
- 易混:0,'0',空格,'\0',NULL----他们不一样哦~
- 模拟实现strcat
- 博主刚刚写代码的时候,犯了一个小错误,发出来给小伙伴们看看,希望有我踩过的坑,你们不要踩~
- 字符串自己给自己追加,能实现吗?
- 八、strcmp函数的使用和模拟
- 这里补充一个知识点:ASCII编码和Unicode编码
- 打开文件内容出现乱码的本质
- 返回值:
- strcmp函数的模拟实现:
- 九、strncpy,strncat,strcmp函数
- 1、strcpy和strncpy
- 2、strcat和strncat(拷贝好相应的字符数量后会在末尾自动加上\0)
- 3、strcmp和strncmp
- 十、strtok函数
- strtok函数的使用方法:
- 十一、strstr函数
- 使用方法
- strstr模拟实现:字符串中找字符串,这里是暴力匹配的过程
- 情景1:一次匹配成功
- 情景2:多次匹配
- 情景3:str1先遇到\0,不可能匹配成功
- 暴力寻找子串程序实现:实际上我们变量选择最多的,情景尽量都能考虑到。
- kmp算法:
- kmp算法的工作原理
- 接下来看看next数组的生成:
- 字符串的一些算法实现(王道考研):
- StrLength
- ClearString
- SubString
- StringCmp:
- Index:
- 十二、strerror函数的使用
- 这样讲未免有些抽象,我一句一句给大家讲讲
- 注解:如何看当前目录是否有此文件
- 到这里也算是懂得strerror工作原理了
- 十三、perror函数
- 使用
二、引言
C语⾔标准库中提供了⼀系列库函数
在编程的过程中,我们经常要处理字符和字符串,为了⽅便操作字符和字符串,C语⾔标准库中提供了⼀系列库函数,接下来我们就学习⼀下这些函数。
三、字符分类函数(字符相关的函数)
首先介绍一下c语言中有一系列专门做字符分类的函数,也就是这些函数属于什么类别的,这里所有的函数使用前都要包含ctype.h
推荐一个网站
函数的使用文档请看c/c++官网
网站打开是这样的,在search栏里面搜索相应的库函数,即可跳转到相应的函数介绍页面
这里我示范islower函数:
int islower(int c):
- 函数的形参是整形,其实传入字符,字符的ASCII码值都是可以的,因为字符的本质就是ASCII码值。
- 函数功能:检查字符c是否是一个小写字母
- 返回值:如果是小写字母就返回一个非零整数,如果不是则返回0。
#include<stdio.h>
#include<ctype.h>
int main()
{int ret = islower('a');printf("%d\n", ret);ret = islower('0');printf("%d\n", ret);ret = islower('A');printf("%d\n", ret);return 0;
}
运行结果是:
再来看一个idigit函数
int isdigit(int c)
- 形参是ASCII码或者字符,也就是整形家族,与上面的lower函数一样
- 函数功能:检查一个字符时不是十进制字符,顺便一提,在计算机里,以0b或者0B作为前缀的是二进制数,以0或者0O为前缀作为八进制数,以0x为前缀作为十六进制数。
- 返回值:如果是,返回非0值,如果不是返回0值
isxdigit函数则是检查一个字符是否是十六进制字符的,其余的解释均与isdigit函数一致
#include<stdio.h>
#include<ctype.h>
int main()
{int ret = isdigit('9');printf("%d\n", ret);ret = isdigit('0');printf("%d\n", ret);ret = isdigit('A');printf("%d\n", ret);ret = isxdigit('A');printf("%d\n", ret);return 0;
}
运行结果是:
当然在我们实际使用的时候,其实是拿他的返回值做判断的
练习:
- 写⼀个代码,将字符串中的⼩写字⺟转⼤写,其他字符不变。
提示:小写字母ASCII码值 - 32 == 大写字母ASCII值。
#include<stdio.h>
#include<ctype.h>
int main()
{char arr[] = "I am a student.";// 0123456789... 注意:空格的可不是\0,他是有自己的ASCII值的,\0是字符串的结束符int i = 0;while (arr[i] != '\0'){if (islower(arr[i])){arr[i] -= 32;}i++;}printf("%s\n",arr);return 0;
}
输出结果为:
四、字符转换函数(字符相关的函数)
c语言中两个库函数:
int tolower ( int c ); //将参数传进去的⼤写字⺟转⼩写
int toupper ( int c ); //将参数传进去的⼩写字⺟转⼤写
测试一下:
#include<stdio.h>
#include<ctype.h>
int main()
{char ch = toupper('a');char ch1 = tolower('A');int ch2 = toupper('a');int ch3 = tolower('A');printf("%c\n", ch);printf("%c\n", ch1);printf("%d\n", ch2);printf("%d\n", ch3);return 0;
}
运行结果是:打印字符或者字符的ASCII码值都是可以的,本质上就是ASCII码
当然通过字符转换函数也可以改变我们刚刚练习的代码:可以看出库函数是c语言封装一系列功能,帮助我们简化代码的操作。
#include<stdio.h>
#include<ctype.h>
int main()
{char arr[] = "I am a student.";int i = 0;while (arr[i] != '\0'){if (islower(arr[i]))//if(arr>'a' && arr<'z'),这样写也是可以的{arr[i] = toupper(arr[i]);//arr[i] -= 32;,另一种写法}i++;}printf("%s",arr);return 0;
}
运行结果仍是:
五、strlen(字符串相关的函数)
这个函数也算是老朋友了,之前将指针6的时候有详细讲过。统计的是字符串’\0’之前的字符个数。
size_t strlen ( const char * str );
参数传递是指针,也就是字符串首字符地址,指针一直往后走,直到找到‘\0’,统计其之前遍历了多少字符,将字符的个数以无符号整形返回。
使用的时候注意几点:
- 1、字符串以 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前⾯出现的字符个数(不包含 ‘\0’ )。
- 2、参数指向的字符串必须要以 ‘\0’ 结束。
- 3、注意函数的返回值为 size_t,是⽆符号的( 易错 )
- 4、strlen的使⽤需要包含头⽂件
- 5、学会strlen函数的模拟实现
#include<stdio.h>
#include<string.h>
int main()
{char arr[] = "abcdef";printf("%zd\n", strlen(arr));//数组名 == 数组首元素地址char arr1[] = { 'a','b','c','d','e','f' };printf("%zd\n", strlen(arr1));//随机值return 0;
}
随机值原因:因为strlen从头开始找,一直沿着内存空间向后走,直到找到’0’才停止,返回之前走的步数(遍历的字符数量),而我们数组中并没有给放置’\0’,所以他一直向后找,直到找到内存中原始就存放的\0,才停下来,而在原始内存中找\0是随机的,因为你并不知道内存初始值都放着什么,这是编译器自己决定的,对用户是透明的。返回值是size_t,size_t是无符号整型(长整型,长长整型)的统一名称,可以是unsigned int,unsigned long,也可以是 unsigned long long。(包含0,正整数)
sizeof的返回值也是size_t,sizeof是一个操作符,计算的是操作数在内存中占用空间的大小,单位是字节,不关注操作数的内容,仅关注其表达式最左侧操作数在内存中的大小
这里有一个易错点:
#include<stdio.h>
#include<string.h>
int main()
{//判断字符串的大小if (strlen("abc") - strlen("abcdef")>0){printf(">");}else{printf("<=");}return 0;
}
运行结果:
原因竟是无符号整形引起的:
无符号整型 - 无符号整型 = 无符号整型
3 - 6 = -3
如果将-3当做无符号整型的话,那么就是-3在内存中的补码被当做是无符号整型了,也就是最后这一行代表的无符号整数,是一个超级大的正数。所以输出一个大于号。
修改方法有两种:输出结果都是<=
//强转int
#include<stdio.h>
#include<string.h>
int main()
{//判断字符串的大小if ((int)strlen("abc") - (int)strlen("abcdef") > 0){printf(">");}else{printf("<=");}return 0;
}
//直接使用比较运算符
#include<stdio.h>
#include<string.h>
int main()
{//判断字符串的大小if (strlen("abc")>(int)strlen("abcdef")){printf(">");}else{printf("<=");}return 0;
}
strlen函数的模拟实现
方式一:计数器记录字符个数
#include<stdio.h>
#include<string.h>
#include<assert.h>
size_t my_strlen(const char* ch)//不想修改ch数组内部的值,只是对其尽进行访问,const修饰谁,谁的值就不能被修改,* ch也就是ch数组中存放的字符串内容,char * const ch代表的就是不可修改ch指针。
{size_t count = 0;assert(ch != NULL);//涉及到指针的解引用操作,所以对指针判空(空指针不可解引用)while (*ch != '\0'){count++;//先判断字符是否为空,再计数加1ch++;//判断完之后到下一个字符}return count;
}
int main()
{char ch[] = "abcdef";size_t ret = my_strlen(ch);printf("%zd\n", ret);return 0;
}
方式二:指针 - 指针 = 指针之间的元素个数
#include<stdio.h>
#include<string.h>
#include<assert.h>
size_t my_strlen(const char* ch)//不想修改ch数组内部的值,只是对其进行访问
{const char* start = ch;//类型一致的赋值,记录字符串的初始位置assert(ch != NULL);//涉及到指针的解引用操作,所以对指针判空(空指针不可解引用)while (*ch != '\0'){ch++;//一直走,直到遇到\0}return ch - start;
}
int main()
{char ch[] = "abcdef";size_t ret = my_strlen(ch);printf("%zd\n", ret);return 0;
}
方式三:递归(一种不创建临时变量就能计算字符串长度的方法)
my_strlen(“abcdef\0”)
1+my_strlen(“bcdef\0”)
1+1+my_strlen(“cdef\0”)
1+1+1+my_strlen(“def\0”)
1+1+1+1+my_strlen(“ef\0”)
1+1+1+1+1+my_strlen(“f\0”)
1+1+1+1+1+1+my_strlen(“\0”)
1+1+1+1+1+1+0;
递归思想:递推:将一个大问题层层拆分为一个个与原问题求解方法类似的子问题,剥下来一层+子问题,再剥下来一层+子问题…直到不能再剥,开始回归。
- 首先相信my_strlen函数能够记录字符串的长度
- 每次把最左边的字符拿出来,就是1长度,再加上剩余字符串长度就是总字符串长度
- 最终到\0的时候返回0值,也就是它是不占长度的
size_t my_strlen(const char* ch)
{if (*ch != '\0'){return 1 + my_strlen(ch + 1);//每一次都返回最左边的长度1,加上后面的长度,也就是去掉最左边字符,指针加1的长度}else{return 0;//识别到\0返回0,不包含这个长度}
}
int main()
{char ch[] = "abcdef";size_t ret = my_strlen(ch);printf("%zd\n", ret);return 0;
}
六、strcpy的使用和模拟
char* strcpy(char * destination, const char * source );
将原指针指向的字符串,拷贝到目的指针指向的空间中,包含‘\0’,返回目标空间的起始地址,为了能够实现链式访问。
注意几点使用:
- 源字符串必须以 ‘\0’ 结束。
- 会将源字符串中的 ‘\0’ 拷⻉到⽬标空间。
- ⽬标空间必须⾜够⼤,以确保能存放源字符串。
- ⽬标空间必须可修改。
- 学会模拟实现。
#include<stdio.h>
#include<string.h>
#include<ctype.h>
int main()
{char arr[] = "abcdef";char arr1[20] = { 0 };strcpy(arr1, arr);printf("%s\n", arr1);return 0;
}
1、那现在我们来看看内存中的样子,按下F11,点击调试,再按照图片中的顺序,随便点击一个监视窗口,在监视窗口中输入arr,arr1,继续按下F11,开始时程序从int main开始进入,接着每按一次F11就走一句代码。
走到这句代码,由于之前的代码中arr1中的内容全是\0,所以看不出arr中的\0有没有被拷贝进去,所以我把arr1中的内容改成了"xxxxxxxxxxx"。
走完这句代码,可以发现\0被拷贝进去了
2、当原字符串中并不是以\0为结尾的时候,就会导致程序不知道拷贝内容的时候,到底在哪里结束。strcpy停不下来。
3、如果目标空间不够大,是放不下原字符串的。
4、目标空间必须是可修改的,如果是常量字符串是不可被修改的,是被放置在只读数据区。
strcpy的模拟实现:
#include<stdio.h>
#include<string.h>
char* my_strcpy(char* dest,const char* src)//不期望源指针所指向的字符串内容被修改,而目的空间必须被修改
{char* parr = dest;//记录初始目标空间位置,以便后续返回空间地址assert(dest && src);//指针判空:空指针不能解引用,有一个为空就是0,为假报错终止。while (*src != '\0'){*dest = *src;src++;dest++;}*dest = *src;return parr;
}
int main()
{char arr1[] = "abcdef";char arr2[20] = { 0 };char* ret = my_strcpy(arr2, arr1);printf("%s\n", arr2);printf("%s\n",ret);printf("%s\n",my_strcpy(arr2, arr1));return 0;
}
过程:
1、源指针和目标指针都指向自己的空间的起始位置
2、将源指针src解引用得到它所指的内存单元中的值,把它放进目标指针dest解引用后的空间,如图每一个向下的箭头。
src和dest都向后走,直到src解引用后遇到\0,也就是src的字符串末尾,停下循环。
3、\0的处理在循环外面,最后src指向的是\0,所以直接解引用将\0放在dest解引用的空间,如图最后一个圈+下箭头。
运行结果:
简易写法:直接将循环判断条件更改,*dest++ = *src++
#include<stdio.h>
#include<string.h>
char* my_strcpy(char* dest, const char* src)//不期望源指针所指向的字符串内容被修改,而目的空间必须被修改
{char* parr = dest;assert(dest && src);//assert(dest != NULL); assert(src != NULL);while (*dest++ = *src++);return parr;
}
int main()
{char arr1[] = "abcdef";char arr2[20] = { 0 };char* ret = my_strcpy(arr2, arr1);printf("%s\n", arr2);printf("%s\n", ret);printf("%s\n", my_strcpy(arr2, arr1));return 0;
}
++运算符后置,代表先把值拷贝(解引用操作),再判断(while),再加加(++)。
这样做就可以将\0和其他值一样被拷贝进去,不需要单独处理,每次拷贝一个值,拷贝完判断是否为\0,不是就将src和dest都加加,往后移。而当src解引用到\0时,赋值给dest空间(将\0及其以前的字符串都拷贝进去了)后,整个表达式就是0(赋值表达式的结果是最左操作数的值),在判断为假,就跳出循环了。
- 注意:因为是循环就得有循环语句,所以while后我加上了一个分号,代表是跳过循环语句或者循环语句为空执行的意思。
七、strcat的使用和模拟
string+concatenate 字符拼接,或者叫成字符串的追加。
1、参数:目标指针,原指针。
2、作用:将原地址指向的字符串内容,连接到目标指针指向的字符串的末尾,是将目标字符串中\0覆盖的拷贝。
3、返回值:返回目标空间的起始地址。
使用注意点:
- 源字符串必须以 ‘\0’ 结束。–保证拷贝的时候知道拷贝到哪结束。
- ⽬标字符串中也得有 \0 ,否则没办法知道追加从哪⾥开始。
- ⽬标空间必须有⾜够的⼤,能容纳下源字符串的内容。
- ⽬标空间必须可修改。–不可以是常量字符串,因为其是只读数据区内容,是不可修改的。
- 字符串⾃⼰给⾃⼰追加,如何?--提前预告,会导致死循环打印这个内容,但是不同编译器处理不同。
#include<stdio.h>
#include<string.h>
int main()
{char arr1[20] = "hello ";char arr2[] ="world.";char* ret = strcat(arr1, arr2);printf("%s\n", arr1);printf("%s\n", ret);return 0;
}
易混:0,‘0’,空格,‘\0’,NULL----他们不一样哦~
1、0代表数字0,非字符没有ASCII码值,字符才有ASCII码值。
2、‘0’代表的是字符0,ASCII码值是48。
3、空格不是’\0’,空格的ASCII值是32。
4、\0 :null 字符,代表没有内容, \0 就是 \ddd 这类转义字符的⼀种,⽤于字符串的结束标志,其ASCII码值是0.
注解:下⾯2种转义字符可以理解为:字符的8进制或者16进制表⽰形式
- \ddd :d d d表⽰1~3个⼋进制的数字。 如: \130 表⽰字符X
- \xdd :d d表⽰2个⼗六进制数字。 如: \x30 表⽰字符0
5、NULL:代表就是空,是0,为了区分指针的0值和变量的0值
int* p = NULL;
int a = 0;
一看到NULL就知道是指针空值了。
字符中有0,打印不会停下来,代表’0’并不是字符串结束符。
发现拷贝提前了,说明’\0’是字符串结束符。
模拟实现strcat
#include<stdio.h>
#include<string.h>
char* my_strcat(char* dest, const char* src)
{char* ret = dest;assert(dest && src);while (*dest )//找到\0,开始位置dest++;//一直往下走while (*dest++ = *src++)//一个一个字符拷贝,直到拷贝到src的末尾\0,结束;return ret;//返回目标空间的起始位置
}
int main()
{char arr1[20] = "hello ";char arr2[] = "world.";char* ret = my_strcat(arr1, arr2);printf("%s\n", arr1);printf("%s\n", ret);return 0;
}
过程是这样的:
1、目标指针和源指针都指向自己空间开始位置
2、dest指针向后遍历直到找到\0(开始拼接的地方),将\0位置解引用出空间,填入src解引用出的字符,如图中的向上箭头,解引用后两指针向后移动,重复这个过程。
3、直到src解引用出\0(结束),拼接结束。
博主刚刚写代码的时候,犯了一个小错误,发出来给小伙伴们看看,希望有我踩过的坑,你们不要踩~
#include<stdio.h>
#include<string.h>
char* my_strcat(char* dest, const char* src)
{char* ret = dest;assert(dest && src);while (*dest++)//找到\0,开始位置;while (*dest++ = *src++)//一个一个字符拷贝,直到拷贝到src的末尾\0,结束;return ret;//返回目标空间的起始位置
}
int main()
{char arr1[20] = "hello ";char arr2[] = "world.";char* ret = my_strcat(arr1, arr2);printf("%s\n", arr1);printf("%s\n", ret);return 0;
}
1、我图省事,直接把第一个循环条件写成了dest++
2、我原本的意思是让指针解引用,判断是否为结束,不是就往后走,直到找到\0停下来
3、调试的时候发现:arr1中的内容,在字符串中多了一个\0,正常应该是hello world\0.现在是hello \0 world,所以打印的时候回提前停止,遇到\0便停了下来,结果就是hello 了。注:窗口还是监视窗口
4、然后追其原因,我发现当dest解引用找到\0之后,dest直接++了,也就是它直接保留了\0,并没有像原来一样把\0给覆盖掉,而是直接就从\0后面开始赋值了
5、那么正常的是,如果解引用之后遇到\0,就应该退出循环,不应该将dest++,也就是不能再让他后移了,这样后来拷贝的时候才能把这个\0给覆盖掉,正常改法就是我写的第一个代码,x循环内部写dest++。
字符串自己给自己追加,能实现吗?
#include<stdio.h>
#include<string.h>
char* my_strcat(char* dest, const char* src)
{char* ret = dest;assert(dest && src);while (*dest)//找到\0,开始位置dest++;while (*dest++ = *src++)//一个一个字符拷贝,直到拷贝到src的末尾\0,结束;return ret;//返回目标空间的起始位置
}
int main()
{char arr1[20] = "abc";char* ret = my_strcat(arr1, arr1);printf("%s\n", arr1);printf("%s\n", ret);return 0;
}
拿我们自己写的strcpy函数测试,发现服务器崩掉了。
我们来找找原因:
1、首先dest找到\0所在位置,也就是开始位置。
2、将src中的内容开始拷贝到接下来的空间中,拷贝完一个,两指针就向后走一步,发先,永远向后走都都走不到有\0的位置让拷贝结束,这种情况就相当于原字符串没有\0,会让拷贝停不下来。
那么我们使用strcpy函数呢?
发现是可行的,其实说明的是编译器内部实现的strcpy算法和我们自己所使用的并不是一样的,它内部是有防止出现拷贝停不下来行为的方法的,说明编译器还是比我们代码能力强的多呀哈哈哈。
but!!!
1、strcpy函数并不保证字符串的自拼接,有可能你换一个编译器结果就不一样了,因为在不同编译器中,他们库函数实现是有可能不一样的,其他编译器不一定有这种机制。
八、strcmp函数的使用和模拟
参数:指向字符串1和字符串2的起始位置的指针。其实传递字符串,传递的就是字符串的首地址。
功能:对比字符串1和字符串2的对应位置字符的ASCII码值的大小,注意比较的不是字符串的长度。
注:当字符串长度不一样的时候,前面的字符串都一样,那就比较\0与下一个字符的大小,比完之后就能判断大小了,不用在比下去了
这里补充一个知识点:ASCII编码和Unicode编码
- 每一个英文字符在存储在计算机中的时候,都会翻译成对应的二进制数,一个英文字符翻译为二进制数包括其高四位,低四位(比特位),这八个比特位构成了一个字节,所以一个英文字母按照ASCII字符集编码规则转为对应的二进制数存储在计算机中,每个字符大小是一个字节。比如空格这个字符,是0010 0000存储在计算机中,转换为十进制数就是32。其余的英文字符同样的道理,这些英文字符集就叫做ASCII字符集,考研中知道这种英文字符集就够了。
- 但是只有8个比特位,显然是不够存储英文字符以外的其他字符的,因为8的bit位仅有2的8次幂个态,也就是256个态,也就是256个不同组合二进制ASCII编码,也就是256个字符。
- 那么出现了另外一种字符集,叫做Unicode字符集,包括韩文,日文,中文,俄罗斯文等等,这些字符的数量远超256个,它给这些字符编码的规则就很多,比如常见的UTF-8,UTF-16等等,编码规则不一样,同一个字符所表示的二进制序列就不同,长度也可能不同,当然你自己也可以给它编码,这就是你独特的编码方案,而当你编写文件和打开文件使用同一个编码方案的时,打开就会显示出相应的内容。
打开文件内容出现乱码的本质
- 请看下面这种情况,相信很多小伙伴都遇到过吧乱码问题,本质上就是编码和解码的方案不同,你编写的时候是采用一套编码方案的,将你写的字符翻译成对应的二进制数存储在计算机里面,在你使用一个软件打开的时候,这个软件默认你使用的是另一套编码规则,将存储在计算机中的这些二进制采用另一套编码规则翻译的时候就会翻译出与原来不同的内容。
- 当然这个有以下场景,你可以利用上,比如你卖给其他公司一套算法,但是你不想让这个公司知道这个函数内部是如何实现的,你只想告诉他使用的方法,那么你就可以将你编写内部实现的说明采用你选择的一套编码方案,但是你并不告诉公司,这样只有你自己能查看内部实现,即使公司获得了这样的内部实现文件,也查看不了。
返回值:
- 如果字符串1>字符串2,返回一个大于0的值。
- 如果字符串1=字符串2,返回0。
- 如果字符串1<字符串2,返回一个小于0的值。
注:vs中是默认大于0的值是1,小于0的值就是-1。在其他编译器中可能就是其他形式,但是都会满足 >0, <0这个条件。
#include<stdio.h>
#include<string.h>
int main()
{char* str1 = "abcdef";//用字符指针存放字符串的首地址char* str2 = "abq";int ret = strcmp(str1, str2);printf("%d\n", ret);return 0;
}
#include<stdio.h>
#include<string.h>
int main()
{char* str1 = "abcdef";//用字符指针存放字符串的首地址char* str2 = "abcdef";int ret = strcmp(str1, str2);printf("%d\n", ret);return 0;
}
字符数组的写法也是可以的,因为字符数组的字符串的首字符的首地址就是数组名
#include<stdio.h>
#include<string.h>
int main()
{char* str1 = "abf";//char str1[] = "abf";char* str2 = "abcdef";//char str2[] = "abcdef";int ret = strcmp(str1, str2);printf("%d\n", ret);return 0;
}
strcmp函数的模拟实现:
1、这种写法完全是基于vs这个编译器实现的。没体现>0,<0这种的返回值。
#include<stdio.h>
#include<string.h>
int my_strcmp(const char* str1,const char* str2)
{ assert(str1 && str2);while (*str1 == *str2)//如果字符串前面都相等,往后走{if (*str1 == '\0')//全都相等直到走到\0,说明两个字符串相同。return 0;str1++;//后走str2++;}if (*str1 > *str2)//不相等字符开始比较return 1;else//不相等,不大于就剩小于了return -1;return 0;
}
int main()
{char* str1 = "abc";//用字符指针存放字符串的首地址char* str2 = "abcdef";int ret = my_strcmp(str1, str2);printf("%d\n", ret);return 0;
}
2、体现总体规则的代码:
#include<stdio.h>
#include<string.h>
int my_strcmp(const char* str1,const char* str2)
{assert(str1 && str2);while (*str1 == *str2){if (*str1 == '\0')return 0;str1++;str2++;}return(*str1 - *str2);//字符相减就是ASCII码值相减。正好比较的就是ASCII码值的大小
}
int main()
{char* str1 = "abcdef";//用字符指针存放字符串的首地址char* str2 = "abq";int ret = my_strcmp(str1, str2);printf("%d\n", ret);return 0;
}
九、strncpy,strncat,strcmp函数
- 上面三个是不受长度限制的字符串函数,就是找\0,确定开始和终止。
- 下面三个是受长度限制的字符串函数,n是number的意思,他会规定拷贝几个字符,连接几个字符,比较几个字符。更为灵活,按照需求可以做很多事情。
- 当然两组函数其实vs编译器认为是使用不安全的,如果将我们代码的第一行紫色的东西注释掉,使用这些函数就会报错,所以其实除了scanf被vs认为不安全,这些会报错,因为比如说你拷贝一个字符串到一个空间中,但是这个空间给的很小,不够放,而这个函数本身是不具备检查空间大小够不够的功能的,所以使用起来会有风险。
1、strcpy和strncpy
两者之间差一个参数:num,代表拷贝的字符数量。
- 拷⻉num个字符从源字符串到⽬标空间。
- 如果源字符串的⻓度⼩于num,则拷⻉完源字符串之后,在⽬标的后边追加0,直到num个。
不足六个字符,后面直接补上\0。
这样的调试,并不能发现到底拷贝完之后到底会不会给你放\0结束符,由于char arr1[20]是一个局部变量,局部变量未初始化的值为0,所以arr1中其他的值就是0。
其实因为这样打印出来也是xxxxxxxx。
所以我们可以这样去调试:发现并没有在结尾给我们放上结束符\0。sum是多少就拷贝多少个字符。
#include<stdio.h>
#include<string.h>
int main()
{char arr1[20] = "abcdefyyyy";//用字符指针存放字符串的首地址char arr2[20] = "xxxxxxxxx";char* ret = strncpy(arr1, arr2,8);printf("%s\n", ret);return 0;
}
2、strcat和strncat(拷贝好相应的字符数量后会在末尾自动加上\0)
两者之间差一个参数:num,代表追加的字符数量。
- 将source指向字符串的前num个字符追加到destination指向的字符串末尾,再追加⼀个 \0 字符
- 如果source 指向的字符串的⻓度⼩于num的时候,只会将字符串中到\0 的内容追加到destination指向的字符串末尾
还是一样的问题,这样调试是看不出来拼接好的时候有没有给你放置\0。
所以我们这样调试:可以看出它将3个x拼接到arr1的尾部了,并且还加上了一个\0结束符。
所以基于这个特性,我们就可以方便的实现字符串的自拷贝了,他会把拷贝好的内容加上\0。
3、strcmp和strncmp
两者之间差一个参数:num,代表比较字符的数量。
- ⽐较str1和str2的前num个字符,如果相等就继续往后⽐较,最多⽐较num个字⺟,如果提前发现不⼀样,就提前结束,⼤的字符所在的字符串⼤于另外⼀个。
- 如果num个字符都相等,就是相等返回0
十、strtok函数
学过计算机网络的同学应该知道,IP地址是采用点分十进制的形式,每一个点都分隔一个十进制数,如果是三段式就是网络号,子网号,主机号。代表你的电脑(主机)在一个网络中的唯一编号。
邮箱:邮箱名@域名.后缀。
- sep参数指向⼀个字符串,定义了⽤作分隔符的字符集合
比如. ,@和. ,写作char* = “@.”;
- 第⼀个参数指定⼀个字符串,它包含了0个或者多个由sep字符串中⼀个或者多个分隔符分割的标记。
意思是每一个分隔符将字符串分为各个小段,每一个小段都叫做一个标记。比如192是一个标记,168也是一个标记,zpengwei也是一个标记等。
- strtok函数找到str中的下⼀个标记,并将其⽤ \0 结尾,返回⼀个指向这个标记的指针。
strtok函数找到标记后,会将其末尾的分隔符更改为\0
strtok函数会改变被操作的字符串,所以被strtok函数切分的字符串⼀般都是临时拷⻉的内容并且可修改。
- strtok函数的第⼀个参数不为 NULL ,函数将找到str中第⼀个标记,strtok函数将保存它在字符串中的位置。
第一次调用的时候,,第一个参数应为你传入的字符串,找到第一个分隔符,改为\0后,跳过\0,记住当前位置。
- strtok函数的第⼀个参数为 NULL ,函数将在同⼀个字符串中被保存的位置开始,查找下⼀个标记。
除第一次调用以外,以后的调用,每一次都传进去空指针就可以了,因为当前函数已经记住了当前指向字符串的位置了,只需要继续向后走,再次找分隔符,改为\0后,跳过\0,记住当前位置,一直重复操作,直到找到\0的时候停止即可。
- 如果字符串中不存在更多的标记,则返回 NULL 指针。
strtok函数的使用方法:
- 返回值:char* 类型会返回每一次分割到的这个标记的起始位置。
#include<stdio.h>
#include<string.h>
int main()
{char str[40] = "zpengwei@yeah.net";char buf[256] = { 0 };//zpengwei@yeah.netstrcpy(buf, str);char* sep = "@.";char* ret = strtok(buf, sep);//zpengwei\0yeah.netprintf("%s\n", ret);//zpengweiret = strtok(NULL, sep);//yeah\0netprintf("%s\n", ret);//yeahret = strtok(NULL, sep);//net\0printf("%s\n", ret);//netret = strtok(NULL, sep);//NULLprintf("%s\n", ret);//(NULL)return 0;
}
然而在实际使用的时候,我们往往不知道或者很不愿意数有多少个分割符,在确定执行几次函数。
所以巧妙地使用for循环可以实现在不知道有多少个分隔符的情况下还能把内容分隔开。
#include<stdio.h>
#include<string.h>
int main()
{char str[40] = "zpengwei@yeah.net";char buf[256] = { 0 };//zpengwei@yeah.netstrcpy(buf, str); //我觉得有好几种写法,都测试过了呢,没问题,如果有其他简洁写法也可以留言哦~char* sep = "@."; //*ret !='\0'或ret != NULL或retfor (char* ret = strtok(buf, sep); *ret; ret = strtok(NULL, sep))//第一次调用后返回第一个标记指针,打印,后续每一次都只是记录当前位置传入NULL,打印下一个标记,直到最终ret指针指向空的空间。{printf("%s\n", ret);//每一次根据返回的每一个标记的起始地址开始打印,打印到其放置的\0之前的内容。}return 0;
}
执行顺序就是:1234,然后一直循环执行234操作。
十一、strstr函数
使用方法
1、找到了:返回c的地址,所以打印的时候会把后续的字符串都打印出来。
#include<stdio.h>
#include<assert.h>
#include<string.h>
int main()
{char arr[] = "abcdef";char* p = "cde";char* ret = strstr(arr, p);printf("%s\n", ret);return 0;
}
2、没找到:返回空指针
3、如果想在字符串中找空串,返回此字符串的首地址,也就是会把这个字符串再次打印出来
#include<stdio.h>
#include<assert.h>
#include<string.h>
int main()
{char arr[] = "abcedf";char* p = "";char* ret = strstr(arr, p);printf("%s\n", ret);return 0;
}
strstr模拟实现:字符串中找字符串,这里是暴力匹配的过程
我们考虑以下三种情景(可以多考虑,算法是多样性的):
情景1:一次匹配成功
1、首先需要三个指针,str1和str2是函数参数传过来的形参,而cur指针是current就是当前指针的意思,需要它记录当前有可能匹配成功的临时初始位置。
2、cur和str2开始匹配,如果匹配不成功,cur就往后走,直到和需要查找的字符串起始字符相同的时候,str1走到当前cur位置,str1开始和str2匹配,假设一次匹配成功,他们一起往后走,发现都相同,直到str2找到\0,结束符的时候,匹配结束,返回cur指针。
情景2:多次匹配
首先应该对cur解引用,看看str1中有没有字符串。
1、此种情形需要新建3个指针,s1,s2,cur,图里的p和arr是主程序中创建这两个空间的时候指向他们的指针,str1和str2是模拟函数my_strstr中形参,接收这p和arr指针的形参。
2、cur向后走,找到第一个与s2首字符一样的位置,说明当前位置是有可能匹配成功的,所以此时s1走到此处,等待与s2的匹配
3、s2和s1指针都向后走,发现有不匹配的,s2依据原来记录的str2的字符初始位置返回,说明此处并不能匹配,而后面的也有可能匹配呀,所以cur指针需要向后走,再次找,直到找到与s2首字符相同的地方,这个地方有可能匹配成功,s1再来到cur的位置等待匹配,重复这样的不走,直到完全匹配
4、匹配结果1、刚好str2走到\0了,说明匹配成功,返回cur指针
2、str1先走到\0,不用管,因为最终程序会停下来,因为在str1找到\0的时候和str2不匹配,str2返回初始位置,cur往后走,重复步骤,直到cur走到\0,停下来。也就是对cur解引用后,是\0。终止循环,返回null代表没找到。
情景3:str1先遇到\0,不可能匹配成功
1、首先cur往后走,走到有可能匹配成功的位置,str1过来到这,与str2开始匹配,发现str1先遇到\0,直接返回null,后续不可能匹配上
暴力寻找子串程序实现:实际上我们变量选择最多的,情景尽量都能考虑到。
大家可以自己调整主函数里面两个字符串的值去调试,两个代码都能做到相应的效果。
//我自己写了一遍,但是时间复杂度高一点,后来我又敲了一遍鹏哥代码,比我快,我没想到str能直接被赋予cur
//下一个是鹏哥的代码
#include<stdio.h>
#include<assert.h>
#include<string.h>
const char* my_strstr(const char* str1, const char* str2)
{//创建指针变量,str1,str2是不可以动的,它记录着初始位置。//s1,s2是可以动的,cur是可以动的。const char* s1 = NULL;const char* s2 = NULL;const char* cur = str1;while (*cur)//判断当前str1中有字符{//将s1和s2指向当前字符串s1 = str1;s2 = str2;while (*cur != *s2){cur++;if (*cur == '\0')//如果一直找不到,直到s1都遍历完str1了,直接返回NULL{return NULL;}}while (*cur == *s2)//找到可能匹配的位置{s1 = cur;//赋予s1当前位置,等待与s2匹配while (*s2 && *s1 && *s1 == *s2)//开始匹配,字符相同就向后走,直到s2先遇到\0说明匹配成功//如果没匹配成功,cur往后走,再次找到可能匹配的位置//如果s1先遇上\0,不用管,因为说明与s2不匹配,cur还会往后走,再次匹配还是s1到\0,cur再往后走循环这个操作,直到cur内容是\0,最外层while判断为假退出{s1++;s2++;}if (*s2 == '\0')//匹配成功{return cur;}cur++;}return NULL;}return NULL;}
int main()
{char arr[] = "abcdefg";char* p = "k";const char* ret = my_strstr(arr, p);printf("%s\n", ret);return 0;
}
//鹏哥代码:
#include<stdio.h>
#include<assert.h>
#include<string.h>
const char* my_strstr(const char* str1, const char* str2)
{assert(str1 && str2);//创建指针变量,str1,str2是不可以动的,它记录着初始位置。//s1,s2是可以动的,cur是可以动的。const char* s1 = NULL;const char* s2 = NULL;const char* cur = str1;if (*str2 == '\0'){return str1;}while (*cur)//判断当前str1中有字符{//将s1和s2指向当前字符串s1 = cur;//由于s1是跟着cur走的,所以直接赋上cur值就好了s2 = str2;//没匹配成功,s2回归原位while (*s2 && *s1 && *s1 == *s2)//找到合适位置,开始匹配,字符相同就向后走,直到s2先遇到\0说明匹配成功//如果没匹配成功,cur往后走,再次找到可能匹配的位置//如果s1先遇上\0,不用管,因为说明与s2不匹配,cur还会往后走,再次匹配还是s1到\0,cur再往后走循环这个操作,直到cur内容是\0,最外层while判断为假退出{s1++;s2++;}if (*s2 == '\0')//匹配成功{return cur;}cur++;//没找到cur就一直往后走,如果没匹配上,cur到了\0,退出外层循环,返回NULL}return NULL;
}
int main()
{char arr[] = "abcdefg";char* p = "k";const char* ret = my_strstr(arr, p);printf("%s\n", ret);return 0;
}
kmp算法:
实际上有一种高效的字符串中查找字符串的方法:叫做kmp算法,鹏哥没有细讲,这里我看了b站视频,尝试给各位讲讲。
在我们刚刚的暴力匹配的过程是一个字符一个字符的匹配,一旦有没匹配成功,就跳回主串中的下一个字符重新匹配。
- 但是它的时间复杂度高,是O(n*m),n代表主串的长度,m代表子串的长度,效率低下
- 假如运气不好,恰好碰到主串和子串都是若干个A最后紧跟着一个B的情况。此时算法会傻傻的将前面多的A都比对完,发现最后一个B字符不匹配,于是跳回下一个字符重新对比。做了不少的无用功。
介绍一下有三位大佬knuth,morris,pratt(保护他们的知识产权~)
他们提出,既然字符串在比对失败的时候已经知道之前都读过哪些字符了,有没有可能避免跳回下一个字符再重新匹配的步骤呢
于是他们发表了线性时间复杂度的KMP算法,再一次字符串的遍历过程中就可以匹配出子串。
kmp算法的工作原理
基本思路:当我们发现某一个字符不匹配的时候,由于已经知道之前遍历过的字符,利用这些信息来避免暴力算法中的回退(backup)步骤。
- 我们不希望递减上面的指针,对应我之前讲过的匹配失败后,str1 = cur的操作
- 希望它永远向前移动:做到方可实现线性时间复杂度。
在这里,由于我们已经知道前面都读到过哪些字符了, 可以将子串移动到如图位置接着进行匹配,避免重复比对,接下来只需要继续测试子串后面的字符就好了。 - 那么我们怎么知道子串应该跳过几个字符再次与主串进行比对呢?
这里就就要用到kmp中定义的next数组了:
先不管next数组怎么生成的,先看一下其功能和用途,kmp算法在匹配失败的时候,会去看最后一个匹配的字符,它对应的next数值--------子串中可以跳过匹配的字符个数
于是移动了子串,直接跳过了前两个字符,也就是前面的两个字符不需要看了,直接从下一个字符接着匹配。 - 显然这样做事没有问题的,因为跳过的两个字符AB确实能与主串中的AB匹配上
所以继续测试后面的字符就好了。
由于不再需要回退主串中的指针。只需要一次主串的遍历就可以完成匹配,效率果真比之前的暴力算法高很多。
接下来看看next数组的生成:
可以使用暴力求解,for循环,但是有一种快速的办法,听评论区说好像是动态规划,本人没学过动态规划,但是up主讲的配合评论区我懂了,也有自信给各位讲懂。
这里给一个前后缀的概念:
也就是比如说我有个一个串是ABABAAB
前缀就是:A,AB,ABA,ABAB,ABABA,ABABAA
后缀就是:BABAAB,ABAAB,BAAB,AAB,AB,B
最长相等前后缀就是:AB(也就是上面两排长度最长的,相等的字母)
当然我们找next数组找的也是这个
1、假设现在我们已经找到当前最长公共前后缀了。
分两种情况讨论:
如果下一个字符依然相同,直接构成一个更长的前后缀,长度等于之前长度加1
但是如果下一个字符不同的话,既然ABA无法与下一个字符构成更长的前后缀,就看看其中存不存在更短的比如A,有可能与下一个字符构共同前后缀,就需要找左右两边ABA子串这个原本的最长前后缀的左边ABA最长前后缀部分,在与当前后面的B这位去判断能否继续构成最长前后缀。看不懂没关系有解释,接着往后看明白了再来品味这句话
- 解释:根据之前的经验,next数组是需要找左右两边最长前后缀,那么就需要找左边的前缀=右边的后缀,那么可以看到目前子串前后的这ABA两部分是相同的,并且右边的后缀=左边的后缀(相同的肯定后缀一样啊),那么直接找左边的ABA的前缀和后缀的最长相等前后缀就好了,即找左边前缀=左边后缀,直接寻找左边ABA的最长前后缀,那么左边ABA的最长前后缀,之前就已经找到过了,就是在求解ABA的最长前后缀的时候,在A那填上1的过程,所以是1
那么找到左右子串ABA的最长前后缀了,是1个,那就是第一个A和倒数第二个A,就可以回到最开始的步骤,检查下一个字符是否相同,相同就构成更长的前后缀,长度加1即可,相当于现在是A一样,往后走B也一样,那么最长前后缀就是AB,长度为2即可。
相信到这里大家也明白了,如果还有问题欢迎评论区留言,up主会给大家解答,那么最后看看整个过程吧:
字符串的一些算法实现(王道考研):
1、首先创建结构体串,我们基于这样的逻辑结构设计串:第一个位置舍弃,由于字符串中的字符位置和线性表中的位序是一样的,都是从1开始的,所以将数组第一个下标为0的位置舍弃。最后一个内存位置存放字符的个数,而一个这样的内存单元空间大小为1B,只能8bit,也就是最大能表示的数是1111 1111也就是256。而第一个空间舍弃,所以最多能存储的字符个数MAXLEN = 255;结构体中包含这样一个静态数组,还有串长度。
接下来模拟实现StrLength,ClearString,SubString,StringCmp,Index的实现,对应的测试方法代码中有写。
StrLength
这个就是数组的遍历,i从0到length-1,其实和1到length一样,也就是从第一个位置到最后一个位置。
int StrLength(SString S)
{int count = 0;for (int i = 0; i < S.length; i++){count++;}return count;
}
ClearString
清空串,也就是将length置为0,但其实他在内存中还是存在的,这是我们下一次操作这块空间的时候直接可以默认里面没有值,再去覆盖相应的值。
bool ClearString(SString &S)
{S.length = 0;return true;
}
SubString
- 参数列表,Sub存放子串的地方,用引用就代表它需要改Sub在主函数中申请的空间内的值,S主串,pos代表从此位置开始切割主串,len代表子串长度
- 首先判断子串是否越界,从i位置起len个字符,如果从5开始的3个字符这个自串就越界了,长度是7,pos+len-1自己带入相应的数字试试就知道了。
- 循环开始,将主串的内容都放到开辟好的子串存放数组,结构体访问用点
- 最后将子串长度给子串数组。
StringCmp:
- 参数列表:比较的两个串
- 遍历这两个串,如果遇到不相同的,直接返回此处字符相减的值,是满足比较函数对返回值的要求的
- 如果遍历下来发现都相同,那就返回字符串长度相减的值。
Index:
- 需要使用我们之前搭建好的函数,在S串中找T串,首先应该知道这两个字符串的长度
- 从S中切割T长度的子串,放进Sub临时存放子串地方,再去比较T与Sub是否相同,使用比较函数
- 如果相同,那么比较函数返回值是0,直接返回当前位置i就可以了,如果不是0意味着S中当前切割的子串并不与T相同,那就找下一个子串与T相比,如果遍历完子串了,还不相同,返回0。
# define MAXLEN 255
typedef struct {char ch[MAXLEN];//静态数组;int length;//串多的长度
}SString;
//SubString(&Sub,S,pos,len)用Sub返回串S的第pos个中字符起长度为len的子串。
int StrLength(SString S)
{int count = 0;for (int i = 0; i < S.length; i++){count++;}return count;
}
bool ClearString(SString &S)
{S.length = 0;//实际上内存中还是具有这些值,但是可以覆盖掉,所以就相当于清空了return true;
}
bool SubString(SString& Sub, SString S, int pos, int len)
{//判断pos是否越界if (pos + len - 1 > S.length)return false;//不越界for (int i = pos;i < pos+len; i++){Sub.ch[i - pos + 1] = S.ch[i];}Sub.length = len;return true;
}
//比较操作
int StringCmp(SString S, SString T)
{//循环比较每一个字符,是否相同for (int i = 0; i < S.length && i < T.length; i++){//如果有不相同的,直接让当前位置字符相减,返回对应值即可。if (S.ch[i] != T.ch[i])return S.ch[i] - T.ch[i];}//如果比较完完全相同,则直接返回长度之差,满足字符串比较返回值的要求return S.length - T.length;
}
//查找子串在主串中的位置
int Index(SString S, SString T)
{//声明一系列变量,i代表位置,主串和子串的长度,暂存子串的空间int i = 0; int n = StrLength(S); int m = StrLength(T);SString Sub;while(i <= n-m)//1 ~n-m+1也是一样的,王道书是1到n-m+1{SubString(Sub, S, i, m);if (StringCmp(Sub, T) != 0){i++;}return i;}
}
int main()
{ //测试全部成功SString S = {"abcdef",6};//测试长度函数int count = StrLength(S);cout << count << endl;SString Sub = {};SString T = { "abc",3 };//测试比较函数int ret = StringCmp(S, T);cout << ret << endl;//测试子串分割函数bool a = SubString(Sub, S, 3, 2);cout << a << endl;//测试索引函数int b = Index(S, T);cout << b << endl;//测试清空函数ClearString(S);cout << S.length << endl;return 0;
}
十二、strerror函数的使用
strerror 函数可以把参数部分错误码对应的错误信息的字符串地址返回来。
char* strerror ( int errnum );
解释:
- 在不同的系统和C语⾔标准库的实现中都规定了⼀些错误码,⼀般是放在 errno.h 这个头⽂件中说明的,C语⾔程序启动的时候就会使⽤⼀个全局的变量errno来记录程序的当前错误码,只不过程序启动的时候errno是0,表示没有错误。
- 当我们在使⽤标准库中的函数的时候发⽣了某种错误,就会将对应的错误码,存放在errno中
- 而一个错误码的数字是整数很难理解是什么意思,所以每⼀个错误码都是有对应的错误信息的。strerror函数就可以将错误码对应的错误信息字符串的地址返回,通过%s,即可打印错误信息,将错误信息可视化。
这样讲未免有些抽象,我一句一句给大家讲讲
1、不同的系统和C语⾔标准库的实现中都规定了⼀些错误码,⼀般是放在 errno.h 这个头⽂件中说明的。
我们打开vs,之后打开everything工具,搜索此文件,找到vs路径下的,一般跟着鹏哥下载的vs路径都是这个,将此文件拖动到vs中。
2、会出现如下界面:里面存放着很多的错误码,每一个出错误码都对应着一个错误信息,比如2对应着no such file or directory。
int main()
{int i = 0;for (i = 0; i < 10; i++){printf("%d:%s\n", i,strerror(i));}return 0;
}
3、在c语言程序启动的时候,会创建并使用一个全局的变量errno来记录程序的当前错误码,此时是0,也就是没有错误的意思。
4、当我们在使⽤标准库中的函数的时候发⽣了某种错误,就会将对应的错误码,存放在errno中
- 比如我以读的形式使用标准库函数fopen打开文件,而这个文件在我当前vs的目录下并没有被我创建,就会产生错误,此时errno全局变量的值就会被修改为2,(这里的2指错误码,因为错误码2的错误信息就是文件操作错误no such file or drictory)
- 那如果只有2这个信息,我并不知道错哪了,还需要将这个错误码解释一下,就使用strerror函数,传入errno参数,错误码值是2,strerror函数会将2翻译成一个错误信息(字符串),并且返回这个字符串的首地址,以char* 的形式返回
- 接下来使用printf(“%s\n”,strerror(errno));,找到字符串地址,使用%s打印,便可将错误信息可视化出来了。
- 代码中以读形式打开文件"abc.txt"
int main()
{FILE* pf = fopen("abc.txt", "r");//文件打开函数,以读形式打开,如果此文件不存在,打开失败返回空指针if (pf == NULL){printf("打开文件失败了,原因是: %s", strerror(errno));return 1;//退出程序}else{printf("打开成功");//.....一系列操作fclose(pf);//关闭文件pf = NULL;//指针置空}return 0;
}
注解:如何看当前目录是否有此文件
- 光标放在当前项目下,有当前目录
- 当前目录没有创建“abc.txt”
到这里也算是懂得strerror工作原理了
再回去看看之前总结的话语,会明白很多。
十三、perror函数
perror函数:顾名思义:printf+error:将错误信息直接打印出来。直接在参数部分放入错误说明字符串,然后它会帮忙打印冒号,空格,接着打印错误信息。
使用
int main()
{FILE* pf = fopen("abc.txt", "r");if (pf == NULL){printf("打开文件失败了,原因是: %s\n", strerror(errno));perror("打开文件失败了,原因是");return 1;}else{printf("打开成功");//.....一系列操作fclose(pf);pf = NULL;}return 0;
}
- 所以未来如果是想获得错误信息字符串,就可使用strerror函数和errno。
- 如果是想打印错误信息,直接使用perror函数更为方便,当然,使用strerror和printf也是可以的。