家人们欢迎来到小姜的世界,<<点此>>传送门 这里有详细的关于C/C++/Linux等的解析课程,家人们赶紧冲鸭!!!
客官,码字不易,来个三连支持一下吧!!!关注我不迷路!!!
字符串函数和字符分类函数
- 前言
- 一、strlen
- (一)介绍
- (二)模拟实现
- 二、strcpy
- (一)介绍
- (二)模拟实现
- 三、strcat
- (一)介绍
- (二)模拟实现
- (三)有趣的知识
- 四、strcmp
- (一)介绍
- (二)模拟实现
- 五、strncpy
- (一)介绍
- (二)模拟实现
- 六、strncat
- (一)介绍
- (二)模拟实现
- 七、strncmp
- (一)介绍
- (二)模拟实现
- 八、strstr
- (一)介绍
- (二)模拟实现
- 九、strtok
- (一)介绍
- (二)模拟实现
- 十、strerror
- (一)介绍
- (二)使用
- (三)perror
- 十一字符分类函数(头文件:<ctype.h>)
- 十二、字符转换
- 总结
前言
本文的重点是以下几个字符函数和字符串函数:
C语言中对字符和字符串的处理很是频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组中。字符串常量适用于那些对它不做修改的字符串函数.
一、strlen
在之前的博客中细细分析过,大家可以先去看一看实现的形式,下面的各个形式也同样是概况与拓展。
传送门<<点此>>
(一)介绍
1.字符串已经 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )。
2.参数指向的字符串必须要以 ‘\0’ 结束。
3.注意函数的返回值为size_t,是无符号的
4.学会strlen函数的模拟实现
建议大家使用size_t表示无符号整型:
如下代码事例:
#include<stdio.h>
int main() {
if (strlen("abc") - strlen("abcdef")) {
printf(">\n");
}
else {
printf("<=\n");
}
return 0;
}
大家可以想一想上面这串代码输出的应该是什么?3-6=-3,应该输出的是<=对吧,可是我们编译看一下结果:
这怎么输出的是>!?原来是strlen的返回值是size_t,在计算机内部存储的是二进制的补码,而-3转化称为补码以后存储计算机中,可是计算机在取出的时候拿到的是无符号整形,不看符号位的,所以是一个很大很大的正数,所以是>。
(二)模拟实现
我们之前也了解过模拟实现,那我们直接给代码,其中有三种模拟形式,第一种就是常规的解法,是用指针从开始往后一直指到’\0’,第二种就是利用递归思想,第三种就是指针减指针的方法。
#include<assert.h>
#include<stdio.h>
//size_t == typedef unsigned int size_t
//法3
//指针-指针
size_t my_strlen(const char* str) {
assert(str);
const char* start = str;//定义首指针
while (*str) {
str++;
}
return str - start;//计算长度
}
//法2
//递归思想
size_t my_strlen(const char* str) {
assert(str);
if (*str != '\0')
return 1 + my_strlen(str + 1);
else
return 0;
}
//法1
//常规思路,往后加1找大小
size_t my_strlen(const char* str) {
assert(str);
int count = 0;
while (*str) {
str++;
count++;
}
return count;
}
int main() {
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
二、strcpy
在之前的博客中细细分析过,大家可以先去看一看实现的形式,下面的各个形式也同样是概况与拓展。
<<点此>>传送门
(一)介绍
1.源字符串必须以 ‘\0’ 结束。 ---- 将strSource中一直到’\0’之前的字符以及’\0’拷贝到strDestination,所以一定要有’\0’。
2.会将源字符串中的 ‘\0’ 拷贝到目标空间。 — 从strSource中的所有字符包括’\0’都拷贝上去。
3.目标空间必须足够大,以确保能存放源字符串。 — 被拷贝的空间一定要是足够大的空间能够存放拷贝的字符串。
4.目标空间必须可变。— 如果给的是一个常量字符串那肯定用不了strcpy了,只有数组是可变的才能用strcpy。
(二)模拟实现
直接上代码:
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* src) {
assert(dest && src);
char* ret = dest;
while (*dest++ = *src++) {
;
}
return dest;
}
int main() {
char arr1[20] = "*************";
char arr2[] = "abcdef";
my_strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
三、strcat
(一)介绍
大家肯定可以发现的是,strcpy和strcat两个库函数是一模一样的,一个是拷贝过去,另一个是追加字符串。
先来了解一下这个函数的构成:
那根据这个函数的构成我们进行写一下追加吧!
既然要追加是怎么追加的呢?是从目标指针的’\0’开始追加的,那也就是说两个字符串数组都是需要’\0’的,被追加的字符串能有个尾巴被追加,追加过去的那个字符串能找到尾巴追加过去并在把’\0’拷贝过去以后能结束,停止拷贝,如下图:
所以总结一下:
1.源字符串必须以 ‘\0’ 结束。
2.目标空间必须有足够的大,能容纳下源字符串的内容。
3.目标空间必须可修改。
(二)模拟实现
我们在了解了这些概念了以后,我们能够进行模拟实现,它第一步是需要将目标指针移动到它的’\0’处,再进行追加,是不是很简单呢?那我们直接给出代码:
#include<stdio.h>
#include<assert.h>
#include<string.h>
char* my_strcat(char* dest, char* src) {
assert(dest && src);
char* ret = dest;
//1.找到目标空间的'\0'
while (*dest != '\0') {
dest++;
}
//2.追加
while (*dest++ = *src++) {
;
}
return ret;
}
int main() {
char arr[20] = "hello ";
char arr1[] = "world!";
//char* p = "world!";
my_strcat(arr, arr1);
printf("%s\n", arr);
return 0;
}
(三)有趣的知识
strcat库函数能够进行追加字符串,那它自己追加自己呢?答案是死循环,如下图解释:
当我们将dest指针往后移动找’\0’的时候,发现找到了,挺开心的,src和dest两个指针一起往后运动,当src指针运动到f的尾部,想这下子我可以美美完成工作睡大觉了,可是发现,这咋没’\0’呢!?震惊了,跟自己说,不灰心,往后找找肯定有的,但是找来找去发现没有’\0’,dest指针也纳闷了,这娃子咋让我一直走,不带停歇的呢?系统崩溃了,这时候系统和这两个指针说,你们虽然在完成任务,可是你看,dest你把人src的\0给覆盖掉了,把墙给拆了,src找不到南墙回不了头呀!!!这时候我们就知道了,dest在往后找完后进行覆盖的时候,首当其冲把’\0’给覆盖了这怪不得会死循环。
四、strcmp
在之前的博客中细细分析过,大家可以先去看一看实现的形式,下面的各个形式也同样是概况与拓展。
传送门<<点此>>
(一)介绍
标准规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
简单实现:
ps:在VS环境下,>返回1,<返回-1,=返回0。
比较形式:
(二)模拟实现
指针相等则两个指针往后移动,但不要忘记的是当在函数内部指针是等于’\0’的时候,是这两个字符串相等,而跳出循环发现指针解引用哪个ASCII码值大就是那个大。
//VS环境下
#include<stdio.h>
#include<assert.h>
int my_strcmp(const char* str1, const char* str2) {
assert(str1 && str2);
while (*str1 == *str2) {
if (*str1 == '\0' && *str2 == '\0') {//两个字符串相等的时候
return 0;
}
str1++;
str2++;
}
if (*str1 > *str2)
return 1;
else
return -1;
}
int main() {
char arr1[] = "abcdef";
char arr2[] = "abq";
int ret = my_strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}
改进:
#include<stdio.h>
#include<assert.h>
int my_strcmp(const char* str1, const char* str2) {
assert(str1 && str2);
while (*str1 == *str2) {
if (*str1 == '\0' && *str2 == '\0') {//两个字符串相等的时候
return 0;
}
str1++;
str2++;
}
/*if (*str1 > *str2)
return 1;
else
return -1;*/
return *str1 - *str2;
}
int main() {
char arr1[] = "abcdef";
char arr2[] = "abq";
int ret = my_strcmp(arr1, arr2);
printf("%d\n", ret);
return 0;
}
五、strncpy
(一)介绍
我们在进行完strcpy的讲解相信大家对这个函数有了比较简单的了解,但那些函数长度不受限制,你可以随意拷贝字符串是非常不安全的,那我们即将介绍strncpy这个库函数,是长度受到限制的库函数。
1.拷贝num个字符从源字符串到目标空间。
2.如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
以下是简单介绍:
有了以上的概念,我们使用这个函数试一试吧!
这拷贝的是hel过去,但后面还有那么多xxx没有被替代,这是很好理解的,我只需要改变这几个字符,其他字符不改变呀!
那可能大家想了,那我调皮一下,我给的数比字符串长度要长呢?输出的是什么?那万变不离调试,我们试一试调试吧!
这已经有\0了,那就后面的都没了,到\0之前即可。
(二)模拟实现
只要多一个num–即可。
#include<stdio.h>
#include<assert.h>
char* my_strncpy(char* dest, const char* src, size_t num) {
assert(dest && src);
char* ret = dest;
while (num--) {
*dest++ = *src++;
}
return ret;
}
int main() {
char arr1[20] = "xxxxxxxx";
char* ret = my_strncpy(arr1, "hello world!", 3);
printf("%s\n", arr1);
return 0;
}
六、strncat
(一)介绍
在介绍完strcat以后,大家肯定对追加字符串有了较为基础的了解,那我们再来讲解一下strncat吧!
有了上面的概念,我们来简单使用一下它吧!
那前面我们讲了strncpy不会增加个\0,那strncat呢?它是会增加一个\0的,我们进入调试看看:
(二)模拟实现
不仅仅要加个num–循环,更要知道的是\0后面的值都舍弃掉。
#include<stdio.h>
#include<assert.h>
char* my_strncat(char* dest, char* src, size_t num) {
assert(dest && src);
char* ret = dest;
//1.找到目标空间的'\0'
while (*dest != '\0') {
dest++;
}
//2.追加
while (num--) {
*dest++ = *src++;
}
*dest = 0;//\0后面的值都不要,'\0'的ASCII码值为0
return ret;
}
int main() {
char arr[20] = "hello\0xxxxxxx";
char* ret = my_strncat(arr, "world!", 4);
printf("%s\n", arr);
return 0;
}
七、strncmp
(一)介绍
在介绍完strcmp后,这个strncmp仅仅比strcmp多了个数,那我们先了解再实现吧!
有了上面的概念,我们来简单使用一下它吧!
(二)模拟实现
依旧是用num–循环。
#include<stdio.h>
#include<assert.h>
int my_strncmp(char* str1, char* str2, int num) {
assert(str1 && str2);
while (num--) {
if (*str1 == *str2) {
str1++;
str2++;
}
else {
if (*str1 > *str2) {
return 1;
}
else {
return -1;
}
}
}
return 0;
}
int main() {
char arr1[] = "abcdef";
char arr2[] = "abqwq";
int ret = my_strncmp(arr1, arr2, 3);
printf("%d\n", ret);
return 0;
}
八、strstr
(一)介绍
strstr库函数简单来讲就是找在第一个字符串找有没有第二个字符串的地址,有则返回第一个出现的字符串的位置,无则返回空指针。
那有了上面的概念,我们进行简单实现吧!
(二)模拟实现
我们发现是有两种情况的,第一种情况是第一次往后找字符串就找到了然后与第二个字符串匹配,第二种情况是找第一次不匹配,找第二次才匹配。
那我们先来分看一下第一种情况的实现吧!
再看一下第二种情况吧!
思路是用两个指针代替原本的指针运动,后续元素不匹配,那就重新跳回去继续找即可。
解题代码如下:
#include<stdio.h>
#include<assert.h>
char* my_strstr(const char* str1, const char* str2) {
assert(str1 && str2);
//空字符串找不到,但输出是第一个字符串
if (*str2 == '\0') {
return str1;
}
//找替代的指针让它们去走
const char* sp1 = str1;
const char* sp = str2;
//cp去储存每次str1与str2两个一样的地址
const char* cp = str1;
while (*cp) {
//碰到一样元素后续不一样sp1先回来,再往后走一位
//找不到sp永远是处在str2(首地址)的位置
sp1 = cp;
sp = str2;
while (*sp1!='\0' && *sp!='\0' && *sp1 == *sp) {//判断都不是走到末尾
sp1++;
sp++;
}
if (*sp == '\0') {//第二个字符串走到头
return cp;
}
cp++;
}
return NULL;
}
int main() {
char arr1[] = "abbbbcedf";
char arr2[] = "bbc";
char* ret = my_strstr(arr1, arr2);
if (ret == NULL) {
printf("找不到\n");
}
else {
printf("找到了,是%s\n", ret);
}
return 0;
}
九、strtok
(一)介绍
这是个很怪的库函数,简单来说就是分隔符的集合,下面是strtok的简单介绍,我也会用代码的形式一步步解析定义:
我们要了解的是这个strtok是会永久替换替换符的,那我们就需要再加一个数组去当个影子参与替换。
当我们找到第一个字符串以后也是需要往后找其他的字符串,那这个有讲究了,是需要NULL空指针当做第一个元素的,因为strtok会记下分割符转换成为’\0’的位置。
代码:
#include<stdio.h>
#include<string.h>
int main() {
char arr[] = "hello/world,nihao/wendang";//分隔符是,/
char* p = ",/";
char strDest[30] = { 0 };
strcpy(strDest, arr);
char* ret = strtok(strDest, p);
printf("%s\n", strDest);
printf("%s\n", arr);
ret = strtok(NULL, p);
printf("%s\n", ret);
ret = strtok(NULL, p);
printf("%s\n", ret);
ret = strtok(NULL, p);
printf("%s\n", ret);
return 0;
}
这写的有点费时间,如果有几百个串是不是很浪费时间,那就需要进行循环了:
#include<stdio.h>
#include<string.h>
int main() {
char arr[] = "hello/world,nihao/wendang";//分隔符是,/
char* p = ",/";
char strDest[30] = { 0 };
strcpy(strDest, arr);
char* ret = NULL;
for(ret = strtok(strDest, p); ret != NULL; ret = strtok(NULL, p)) {
printf("%s\n", ret);
}
return 0;
}
(二)模拟实现
#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strtok(char* strToken, const char* strDelimit){
assert(strDelimit && strDelimit);
static char* s1 = NULL;//静态变量,之后再进来的时候可以接着原本的值使用
static char* s2 = NULL;
static int len1 = 0;
static int count = 0;
int len2 = 0;
int i = 0;
if (strToken) { //第一个字符不等于'\0'
s1 = strToken;
len1 = strlen(strToken);
len2 = strlen(strDelimit);
while(*strToken != '\0'){//解引用以后拿到的字符不等于'\0'
for (i = 0; i < len2; i++) {
if (i == 0){
count++;
}
if (*strToken == *(strDelimit + i)){
*strToken = '\0'; //找到了那个分隔符,就将这个分隔符内容赋为0
s2 = strToken;
return s1;//返回头指针
}
}
strToken++;
}
}
else{
s1 = s2 + 1; //s1去移动,s2不用运动
len2 = strlen(strDelimit);
strToken = s1;
while(*strToken != '\0'){
for (i = 0; i < len2; i++){
if (i == 0){
count++;
}
if (*strToken == *(strDelimit + i)){
*strToken = '\0'; //找到了那个分隔符,就将这个分隔符内容赋为0
s2 = strToken;
return s1;
}
}
strToken++;
}
}
if (count > len1){//要是
return NULL;
}
return s1;
}
int main(){
char arr1[] = "hello/world,nihao/wendang";
char arr2[] = "/,";
char* str = NULL;
for (str = my_strtok(arr1, arr2); str != NULL; str = my_strtok(NULL, arr2)){//进入循环,不断的返回
printf("%s\n", str);
}
return 0;
}
十、strerror
(一)介绍
C语言的库函数在运行的时候,如果发生错误,就会将错误码存放在一个变量中,这个变量是errno,错误码是一些数字,所以我们需要将错误码翻译成错误信息,返回错误码所对应的错误信息。
举个例子:
如图,这个函数是用于返回errnum所对应的错误信息的,而这些错误信息是C语言中库函数报错的时候的错误码。
(二)使用
在我们使用这个库函数的时候,我们需要先认识了解一下fopen库函数:
这个有点提前了,那也需要在这里说明一下,如下代码:
#include<stdio.h>
#include<errno.h>
int main() {
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL) {
printf("%s\n",strerror(errno));//errno是用来保存错误信息,strerror是用来进行译码
return 1;
}
//读文件
//关闭文件
fclose(pf);//释放
pf = NULL;
return 0;
}
我们仅仅看这个strerror库函数即可,现在告诉你,这个文件是没有的,所以当我打开一个不存在的文档,错误码就会被保存在errno变量中,然后用strerror函数进行解析错误码,最后用printf函数打印错误信息,所以说以后运用的时候就可以这样用了。
(三)perror
有个perror库函数,更加强大,在报错领域中自行打印,我们先来看一下这个库函数的介绍:
如下代码,我们将printf换成perror库函数,因为perror是直接打印错误信息,在打印错误信息前,会先打印自定义的信息,也就是说,perror相当于printf+strerror:
#include<stdio.h>
int main() {
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL) {
//printf("%s\n",strerror(errno));
perror("fopen");
return 1;
}
//读文件
//关闭文件
fclose(pf);//释放
pf = NULL;
return 0;
}
对于以上代码,perror(“error”)是perror函数的写法,所以perror会输出:传参信息+冒号+错误信息。
十一字符分类函数(头文件:<ctype.h>)
我们举个大小写的例子吧,先来看一下islower字符分类函数的简单介绍:
那我们试一试吧!
十二、字符转换
int tolower ( int c );大写字母转小写
int toupper ( int c );小写字母转大写
如下代码:
#include<stdio.h>
#include <ctype.h>
int main(){
int i = 0;
char str[] = "Test String.\n";
char c;
while (str[i]){
c = str[i];
if (isupper(c))
c = tolower(c);
putchar(c);
i++;
}
return 0;
}
总结
这十二个是字符串函数和字符分类函数的汇总,但真正的汇总可远不止这些,当然,库里面还有很多其他的奇特的库函数和有用的库函数在这里都没有表示出来,这里只是展现了我们常用的几个库函数,所以我们在以后的学习中多多运用互联网去搜寻这些库函数并进行学习,当我们有一天变的很强大,都掌握了的时候,那就是真正学会了一门语言。
客官,码字不易,来个三连支持一下吧!!!关注我不迷路!!!