文章目录
- 前言
- 一、字符分类函数
- 小练习
- 二、字符转换函数
- 三、strlen的使用和模拟实现
- 四、strcpy的使用和模拟实现
- 五、strcat的使用和模拟实现
- 六、strcmp的使用和模拟实现
- 七、strncpy函数的使用
- 八、strncat函数的使用
- 九、strncmp函数的使用
- 十、strstr函数的使用和模拟实现
- 十一、strtok的使用
- 十二、strerror的使用
- 总结
前言
开启新阶段的学习了!大家加油!
一、字符分类函数
C语言中有一系列的函数是专门做字符分类的,也就是一个字符是属于什么类型的字符的
它们的使用都需要包含一个头文件是ctype.h
我们就以一个函数为例,注意这个函数:int islower (int c);
作用很明显,判断c是否是小写字母的,通过返回值来说明是否是小写字母,如果是小写字母就返回非0的整数,如果不是小写字母,则返回0
小练习
请编写一个程序,将字符串中的小写字母转大写,其他字符保持不变
应该说思路还是挺明显的,遍历一遍字符串,加个是否是小写的判断条件(islower),减个32,就可以了( ‘a’ 与 ‘A’ 差32)
二、字符转换函数
前面那个代码,我们想着通过-32来实现大写到小写的转换,其实我们还可以考虑上面两个函数,值得注意的是,虽然说是转换,但是如果本来就是大写的话,toupper也可以接收,只是没啥效果罢了~
你也去试试吧!
三、strlen的使用和模拟实现
函数原型:size_t strlen(const char* str);
使用注意事项:
1.字符串以’\0’作为结束标志,strlen函数返回的是字符串中’\0’前面出现的字符个数(不包含’\0’)
2.参数指向的字符串必须要以’\0’结束
3.注意函数的返回值为size_t,是无符号的
4.strlen的使用需要包含头文件
对于strlen,我们也来尝试实现以下我们自己的my_strlen,因为我们本质上不希望改动所接收的字符串,所以加个const修饰
接下来我们思考如何求出字符串的长度,逻辑其实也很简单,字符串以’\0’结尾,那就从头遍历,遍历一个字符,计数器加一,代码如下:
至于说完善一下,因为循环条件有解引用,所以我们要预防一下空指针,于是加个断言
assert(str != NULL);
同样的,我们回顾前面指针 - 指针得到的是元素个数这个特点,我们可以接收完指针后,设置一个临时变量start保存起始位置,再让str后移到’\0’,接着再str - start,这种思路也是极棒的,代码如下:
那如果不能使用临时变量呢?那就考虑递归,这也是前面讲过的!
请看下面代码(突然发现还有这个代码块…好吧,是我之前没了解全面,这样应该就方便复制了)
#include <stdio.h>
#include <string.h>size_t my_strlen(char* str)
{if (str == '\0')return 0;return 1 + my_strlen(str + 1);
}int main()
{char* str = "abcdef";printf("%zd\n", strlen(str));return 0;
}
四、strcpy的使用和模拟实现
函数原型:char* strcpy(char* destination, const char* source);
使用注意事项:
1.源字符串必须以’\0’结束
2.会将源字符串的’\0’拷贝到目标空间
3.目标空间必须足够大,以确保能存放源字符串
4.目标空间必须可修改
5.同样也得包含头文件
我们来确认一下第2点,看看到底会不会把字符0复制过来
这很简单,我们给arr1塞点东西进去
char arr1[20] = “xxxxxxxxx”;
char arr2[] = “hello”;
打开监视,并且观察strcpy前后arr1的变化,会发现显然把字符0也给复制过去了:
我们再来看下第二点,因为它会将源字符串的起始位置到字符0全部复制给arr1,可能会导致越界:
至于说目标空间必须足够大,理由自不必说,只是我们需要考虑字符0也算,另外目标字符串不能是常量字符串,因为它不可修改
接下来我们来研究一下strcpy的模拟实现,显然my_strcpy不需要返回类型,我们来思考一下复制的逻辑,显然 arr1 和 arr2 一个字符一个字符的循环复制,可是循环条件是什么呢?
我们思考,src指针一直到 ‘\0’ 才停下来,所以我们条件是当 src != ‘\0’ 的时候循环,可是’\0’也是需要复制的,于是我们在循环后面再赋值一次 *dest = *src; 即可
可是我们到底是不太满意的,为了这最后一次,还要再单独写一个语句,这很不好,可以想一下有没有其他的好方法,我们可以把 *dest++ = *src; 放到while里面
while (*dest++ = *src++) 相当巧妙,刚好到’\0’的时候就复制,然后停下
代码如下:
插一条小知识
NULL本质上也是0,一般用于指针的初始化
\0是\ddd形式的转义字符,本质也是0,一般字符串的末尾会有\0,是字符串的结束标志
0是数字0
null NUL -> \0
'0’是字符0,本质是48
五、strcat的使用和模拟实现
函数原型:char* strcat(char* destination, const char* source);
原理是找到目标字符串的\0,然后开始拷贝
注意事项有:
1.源字符串必须以’\0’结束
2.目标字符串中也得有\0,否则没办法知道追加从哪里开始
3.目标空间必须足够的大,能容纳下源字符串的内容
4.目标空间可修改
5.字符串自己给自己追加,如何?
我们来思考一下模拟实现my_strcat
首先找到目标空间的\0,这还是比较简单的,*dest自加循环到\0即可
然后开始拷贝,然后开始拷贝,这时候又可以 while(*dest++ = *src) 了,代码如下:
如果 my_strcat(arr1, arr2); 是不安全的,因为会死循环,永远找不到\0
六、strcmp的使用和模拟实现
函数原型:int strcmp(const char* str1, const char* str2);
作用是比较对应位置上的字符大小,小的字符所在的字符串,小于另外一个字符串,而不是字符串的长度!
我们来模拟实现一下 my_strcmp ,首先我们不希望 str1 和 str2 被修改,因此要加个 const ,接着我们来思考一下比较逻辑,首先获得两个字符串的起始位置,一直循环判断到两个指针所指向的字符不一样,所以有 while(*s1 == *s2),期中,我们考虑到如果两个字符串都是走到字符0,这个时候可以直接return 0;如若遇到不同,跳出去之后比较大小,大于就返回1,小于就返回0,或者因为对返回数值的大小没要求,对符号有要求,我们还可以直接返回 *s1 - *s2,代码如下:
七、strncpy函数的使用
函数原型:char* strcpy(char* destination, const char* source, size_t num);
使用注意事项:
拷贝 num 个字符从源字符串到目标空间
如果源字符串的长度小于 num ,则拷贝完源字符串后,在目标的后面追加0,直到 num 个
请注意,复制过来的时候,不会必带\0
当源字符串的长度小于 num 的时候,后面会追加0:
八、strncat函数的使用
函数原型:char* strncat(char* destination, const char* source, size_t num);
使用注意事项:
将 source 指向字符串的前 num 个字符追加到 destination 指向的字符串末尾,再追加一个 \0 字符
如果 source 指向的字符串的长度小于 num 的时候,只会将字符串中到 \0 的内容追加到 destination 指向的字符串末尾
九、strncmp函数的使用
函数原型:int strncmp(const char* str1, const char* str2, size_t num);
使用注意事项:
1.比较 str1 和 str2 的前 num 个字符,如果相等就继续往后比较,最多比较 num 个字母,如果提前发现不一样,就提前结束,大的字符所在的字符串大于另外一个,如果 num 个字符都相等,就是相等返回0
十、strstr函数的使用和模拟实现
函数原型:char* strstr(const char* str1, const char* str2);
使用注意事项:
1.函数返回字符串 str2 在字符串 str1 中第一次出现的位置
输出结果如下:
首先,我们先创建了一个字符数组,接着通过 strstr 函数找到了str中 ‘‘simple’’ 中 ‘s’ 字符的地址,接着再通过 strncpy 复制过去一个 sample ,接着再输出验证
所以说,strstr 函数的作用是返回字符串在另外一个字符中第一次出现的位置
那么,我们来模拟实现一下 strstr 函数
我们来思考一下实现逻辑:
1.我们会发现,在str1 和 str2移动的过程中,先打算让str1跑完字符串,一旦遇到 str1 == str2,那么开始向后移动,可是找到还好,万一没找到,都回不去,而且,就算找到了,也不知道起始位置在哪里,所以,我们可能还需要两个指针cur1,cur2
2.于是我们设置两个指针cur1,cur2,继续刚才的思路,先来一个循环 while (*str1 != ‘\0’) ,并且循环里面str1保持更新
3.每次循环开始的时候,都让 cur1 = str1,cur2 = str2,接着判断 *str1 与 *str2是否相等,如果相等的话,开始判断 str2 是否是 str1 的一部分,所以再来个循环,这个循环的条件是什么呢?显然,当 *cur1 与 *cur2 相等的时候,继续循环,一直到不相等或者 *cur2 == ‘\0’ 的时候退出,可是万一 cur1 也走到结尾,那不就要越界访问了吗,于是我们再加个条件 *cur1 != ‘\0’ ,所以这个循环是 while ( *cur1 != ‘\0’ && *cur2 != ‘\0’ && *cur1 == *cur2)
4.等到判断循环结束,我们就来验证,怎么验证,显然只要当上述循环结束的时候,我们验证一下 *cur2 == ‘\0’ 就可以了,若为真,则直接返还 str1
5.若不为真,则我们让 str1 加 1,判断下一个起始字符是否满足题意,至于 cur1 和 cur2,前面说了每次循环开始都会更新的
综上,代码如下:
char* my_strstr(const char* str1, const char* str2)
{if (*str2 == '\0') return (char*)str1; // 如果 str2 是空字符串while (*str1 != '\0'){ const char* cur1 = str1;const char* cur2 = str2;if (*str1 == *str2) {while (*cur1 && *cur2 && *cur1 == *cur2){cur1++;cur2++;}if (*cur2 == '\0') return (char*)str1;}str1++;}return NULL;
}
十一、strtok的使用
函数原型:char* strtok(char* str, const char* sep);
使用注意事项:
1.sep参数指向一个字符串,定义了用做分隔符的字符整合
2.第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记
3.strtok函数找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针,请注意,strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改
4.strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置
5.(吊诡)strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记
6.如果字符串中不存在更多的标记,则返回NULL指针
因为第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记
当然也不能一直这么搞,再接下去就是NULL了
这个函数比较特殊,原理大家可自行查找,你可以大胆猜测这个函数内部会有静态变量,因为这样多次进入才确保有上次保留的值
十二、strerror的使用
函数类型:char* strerror(int errnum);
strerror函数可以把参数部分错误代码对应的错误信息的字符串地址返回来。
在不同的系统和C语言标准库的实现中都规定了一些错误码,一般是放在errno.h这个头文件中,C语言程序启动的时候就会使用一个全局变量errno来记录程序的当前错误码,只不过程序启动的时候errno是0,表示没有错误,当我们在使用标准库中的函数发生了某种错误,就会将对应的错误码,存放在errno中,而一个错误码的数字是整型很难理解是什么意思,所以每一个错误码都是有对应的错误信息的。strerror函数就可以将错误码对应的字符串地址返回。
举个例子:
#include <errno.h>
#include <string.h>
#include <stdio.h> // 我们打印一下~10这些错误码对应的信息
int main()
{ int i = 0; for (i = 0; i <= 10; i++) { printf("%s\n", strerror(i)); } return 0;
}
运行结果如下:
实用的一个场景如下(C语言是可以进行文件操作的,这个我们后面会有详细讲解,在下面这个场景下,我们的打开文件函数fopen是失败的,因此errno会有一个错误码):
也可以了解一下 perrorr 函数,perrorr函数相当于一次将上述代码中 if 语句给直接完成了,直接将错误信息打印出来。perrorr函数打印参数部分的字符串后,再打印一个冒号和一个空格,再打印错误信息。
它的输出如下:
总结
这篇篇幅很长,很多都是相当枯燥的干活,希望你能啃下来~