前言
C语言中存在不少对字符串处理的函数,像strcpy、strlen等等。这一个章节就让我们来简单地了解和学习它们把
目录
1. 字符分类函数
C语⾔中有⼀系列的函数是专⻔用于字符分类的,也就是分析⼀个字符是属于什么类型的字符并将其分类。而想使用这些函数则需要包含⼀个头⽂件 —— < ctype.h >。
函数 | 如果函数的参数符合下列条件就返回真 |
iscntrl | 任何控制字符 |
isspace | 空白字符:空格 ' ' ,换页 ‘ \f ' ,换行’ \n ' , 回车 ' \r ',制表符 ’ \t '或者垂直制表符' \v ' |
isdigit | 十进制数字 0~9 |
isxdigit | 十六进制数字,包括所有十进制数字,小写字母 a~f,大写字母 A~F |
islower | 小写字母a~z |
isupper | 大写字母A~Z |
isalpha | 字母a~z或A~Z |
isalnum | 字母或数字,a~z,A~Z,0~9 |
ispunct | 标点符号,任何不属于数字或者字母地图形字符(可打印) |
isgraph | 任何图形字符 |
isprint | 任何可打印字符,包括图形字符和空白字符 |
上述这些函数的使用方法都是非常相似地,这里我们以islower为例来讲解即可。
int character1 = 'c';
char character2 = 'c';
int character3 = 'b';
char character4 = 'b';
int ret1 = islower(character1);
int ret2 = islower(character2);
int ret3 = islower(character3);
int ret4 = islower(character4);
printf("ret1 = %d\n", ret1);
printf("ret2 = %d\n", ret2);
printf("ret3 = %d\n", ret3);
printf("ret4 = %d\n", ret4);
运行与调试结果:
我们可以看到给islower函数传的参数不论是char类型还是int类型都是可以的,而且返回值的非零整数都是一样的,且是随机的,不论传的参数内容是什么。
那我们学会的这些函数有什么用呢?可以怎么用呢?举个简单的例子,我们可以从他们返回值这里做些文章。举个列子:
写⼀个代码,将字符串中的⼩写字⺟转⼤写,其他字符不变。
#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 (islower(c))
c -= 32;
printf("%c", c);
i++;
}
return 0;
}
运行结果:
2. 字符转换函数
C语⾔提供了2个字符转换函数:
int tolower ( int c ); //将参数传进去的⼤写字⺟转⼩写
int toupper ( int c ); //将参数传进去的⼩写字⺟转⼤写
在第一part中我们通过使其字母的ASCII值减去32,从而达到将⼩写转⼤写的效果,有了转换函数,就可以直接使用 tolower 函数。一步到位。
int main()
{
int i = 0;
char str[] = "Test String.\n";
char c;
while (str[i])
{
c = str[i];
if (islower(c))
c = toupper(c);
printf("%c", c);
i++;
}
return 0;
}
3.长度不受限制的字符串函数
3.1strlen的使用和模拟实现
3.1.1 strlen函数的定义及其理解
在C++的官网中我们可以查找到strlen函数的定义以及各参数的意义和如何使用。(下图就是官网的截图和翻译)
其实在前面指针的章节中我们就已经讲过 strlen函数 是用来计算字符串的长度
1 size_t strlen ( const char * str ); //strlen的标准形式
让我们结合上图,再总结下strlen函数的几个特点及注意事项:
- 字符串以 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前⾯出现的字符个数(不包 含 '\0' )。
- 参数指向的字符串必须要以 '\0' 结束。
- 注意函数的返回值为size_t,是无符号的( 易错 )
- strlen的使用需要包含头文件
3.1.2 strlen函数的模拟
其实如果看过我前面介绍指针的同学们其实不难发现strlen函数是跟我们指针密切相关的,甚至以为们目前的知识储备量我们完完全全可以自己自定义一个函数来模拟实现strlen函数。接下来我将通过两种方式来模拟实现strlen函数。
3.1.2.1 方式一 (计数器方式)
//计数器方式
int my_strlen(const char * str)//const在*左边是为了限制str的内容不可更改
{
int count = 0;
assert(str);//assert()是断言,只要条件不符合括号内的参数,程序就会报错。这个我们后面还会详
//细介绍。使用assert前需要包含<assert.h>的头文件
while(*str)
{
count++;
str++;
}
return count;
}
运行结果示范:
思路:
- 我们知道字符串是以’ \0 ‘结尾,
- 在自定义函数中我们设置一个变量count来计数,
- 我们设置一个while循环,参数是字符串str的地址,在循环里面,每当str的地址加一,count就紧接着加一,
- 待str的地址指向的数据是' \0 '后,程序跳出while循环,并返回count的值给主函数的ret
3.1.2.2 方法二 (使用回调函数)
//使用回调函数,不用创建临时变量来计数
int my_strlen(const char* str)
{
/*assert(str);*/
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str + 1);
}
运行示范:
思路:
(这个写法涉及到回调函数,回头有机会我会给大家专门出一章来详细介绍回调函数,这里我先给大伙讲一个大概 )
- 我们知道字符串是以’ \0 ‘结尾,
- 回调函数简单来讲就是在自己的函数内部调用自己,因此得名回调。如果我们的str指向的数据变成' \0 ',则返回0,回到主函数;否则就是返回1 + my_strlen(str + 1)
- 注意!注意!因为此时我们的返回值中含有函数自己本身,所以这时候并不会回到主函数,而是再次进入到我们自定义的函数当中去,唯一不同的是这次进入函数时的参数变成str + 1,而且返回的1+并不会消失,而其实暂时存放着。
- 只要str还没遇到' \0 ',程序就会一直进行下去,以此类推。
- 直到str遇到' \0 '才停了下来。此时,前面留下的1+便全部想结合,比如我们举例的字符串“Lebron”有六个字符,则证明前面会留下6个1,6个一相加,便得到最后的结果6,这时才会将最后的值返回给主函数中的ret并回到主函数中。
3.1.2.3 方式三(指针-指针的方式)
//指针-指针的⽅式
int my_strlen(char *s)
{
assert(str);
char *p = s;
while(*p != ‘\0’ )
p++;
return p-s;
}
运行示范:
思路:
- 设置一个字符指针变量p,并将传参的s的值赋给p
- 设置一个while循环,只要p指向的数据不为0,则p的大小加一
- 待遇到0后跳出while循环,然后用p - s得到p和s之间的差值即为p与s地址之间差距了几个字节,即是字符串的长度。
- 返回差值,并回到主函数中。
3.2 strcpy 的使用和模拟实现
3.2.1 strcpy函数的定义及其理解
strcpy,是string copy的缩写,顾名思义就是字符串拷贝的意思,我们可以使用strcpy函数将字符串拷贝到我们指定的地方去。下面是strcpy函数在C++官网中的定义。
下面是strcpy的标准形式:
char* strcpy(char * destination, const char * source );
(其中destination是目的地,即待粘贴地点;source是源头,即拷贝对象)
总结下strcpy函数的几个特点及注意事项:
- 源字符串必须以 '\0' 结束。
- 会将源字符串中的 '\0' 拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可修改。
3.2.2 strcpy函数的模拟
3.2.2.1 代码
与strlen函数一样,strcpy函数我们也是可以自己来模拟实现的。
char *my_strcpy(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while((*dest++ = *src++))
{
;
}
return ret;
}
运行示范:
思路:
- 我们用字符指针变量和字符数组分别定义两个字符串,str2为我们的目的地。特别注意str2一定要用char类型数组来定义,不能用字符指针变量,因为字符指针变量定义的字符串是常量字符串,是不可被修改的!
- 我们将str1和str2传参(dest是目的地,src是拷贝对象)给自定义函数,在函数内部我们设置一个while循环,
- 将src解引用,将其值赋给*dest,然后dest和src都加1,即向后进一个字节,再将后一个字节的地址上的值继续赋给*dest。以此类推,直到src遇到\0,
- while(*dest++ == *src++ ){ ; }是 while(*dest = *src ){dest++; src++; } 的缩写,
- 在进入while循环前,我们要先设置一个ret变量,来记录目的地的地址开头。切记!切记!不能直接返回dest,因为你的dest的地址经过上面while循环后已经向后挪动了。
3.2.2.2 额外补充
这里额外多提一嘴。
如果我们将代码中的while(*dest++ == *src++ ){ ; }改成while(*src++){*dest = *src ;dest++; src++; } 你会发现如果你的目的地(str1)的空间如果比源头(str2)大的话,拷贝后str2剩余没被拷贝修改的字符还会跟着被打印出来。
如下图所示
这是因为如果是 while(*src!= '\0'){*dest = *src ;dest++; src++; }的话src来到\0便跳出while循环了,此时的dest是"Lebronxxxx ";而如果是while(*dest++ == *src++ ){ ; }的话,src会连同\0一块赋值给dest,此时的dest是"Lebron\0xxx ".而我们printf输出字符串时的格式是遇到\0就会停下,所以\0后面的xxx就被隐藏起来不打印了。
这个问题其实很好验证:
但是!但是!
while(*src!= '\0')
{
*dest = *src ;
dest++;
src++;
}
这种写法其实是错误的,它不符合strcpy的定义,因为你会发现它并没有把\0拷贝进去,即最后并不是以\0结尾,所以严格来说拷贝后的数据并不能算是字符串。
3.3 strcat 的使用和模拟实现
3.3.1 strcat函数的定义及其理解
讲完了字符串的计数和拷贝,可能会有同学好奇,那没有字符串拼接呢?这不,还真有,就是我们下面要讲的strcat函数。
总结下strcat函数的几个特点及注意事项:
- 源字符串必须以 '\0' 结束。
- 目标字符串中也得有 \0 ,否则没办法知道追加从哪里开始。
- 目标空间必须有足够的⼤,能容纳下源字符串的内容。
- 目标空间必须可修改。
3.3.2 strcat函数的模拟
3.3.2.1 代码
char* my_strcmp(char* dest, const char* src)
{
char* ret = dest;
assert(src != NULL);
assert(dest != NULL);
while (*dest)
{
dest++;
}
while ((*dest++ = *src++))
{
;
}
return ret;
}
int main()
{
char* str1 = "Jamns";
char str2[20] = "Leborn ";
char* str3 = my_strcmp(str2, str1);
printf("%s\n",str3);
return 0;
}
运行结果:
思路:
- 其实我们不难发现strcat的思路跟strcpy其实是差不多的,strcat其实就是多了一步while(*dest){ desrt++; } ,这是为了找出\0,str1找到衔接处。
- 要注意的就是给目的地(str2)开辟足空间便是。
3.4 strcmp 的使用和模拟实现
3.4.1strcmp函数的定义及其理解
有的同学可能会好奇,数字有大小之分,那字符串有没有大小之分呢?如果有的话又是如何比较的呢?接下来登场的strcmp函数会给你答案。
总结下strcmp函数的几个特点及注意事项:
- 标准规定:
- 第⼀个字符串大于第二个字符串,则返回大于0的数字,
- 第⼀个字符串等于第二个字符串,则返回0,
- 第⼀个字符串小于第二个字符串,则返回小于0的数字,
- 那么如何判断两个字符串? 比较两个字符串中对应位置上字符ASCII码值的大小。采用的是整体赋值法,两个字符串的字符依次比较。只要有一个字符的ASCII码值大,那这个字符串整体就大。
3.4.2 strcmp函数的模拟
3.4.2.1 代码
int my_strcmp(const char* dest, const char* src)
{
int ret = 0;
assert(dest != NULL);
assert(src != NULL);
while (*dest == *src)
{
if (*dest == '\0')
return 0;
dest++;
src++;
}
ret = *dest - *src;
}
int main()
{
char* str1 = "Jamns";
char* str2 = "Leborn";
int num1 = my_strcmp(str1, str2);
printf("num1 = %d\n", num1);
char* str3 = "Jamns";
char* str4 = "Jamns";
int num2 = my_strcmp(str3, str4);
printf("num2 = %d\n", num2);
char* str5 = "Jamns";
char* str6 = "HJamns";
int num3 = my_strcmp(str5, str6);
printf("num3 = %d\n", num3);
return 0;
}
运行结果:
思路:
- 与前面的strlen函数的写法相似,但是多了一步if(*dest == ‘\0’),这是用来检测如果两个字符串均为空,则直接退出程序,
- 关于如何比较大小,便是看最后的返回值,用*dest - *src得到的差值的正负和零代表着两个数的大小和相同与否。
3.4.2.2 额外补充
在vs2022编译器中,由库函数提供strcmp函数的返回值分别是1、0、-1
- 当str1 > str2 时,返回值时1,
- 当str1 = str2 时,返回值时0,
- 当str1 < str2 时,返回值时-1。
4.长度受限制的字符串长度
4.1 strncpy 函数的使用
4.1.1strncpy函数的定义及其理解
其实strncpy与前面的strcpy函数一样都是字符串的拷贝,到那时strncpy多了一个参数n,这个n是你打算拷贝多少个字符。
总结下strncpy函数的几个特点及注意事项:
- 拷贝num个字符从源字符串到目标空间,
- strbcpy不会主动在字符后面追加\0,
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加\0,直到num个。
4.1.2 strncpy函数的模拟
代码:
//strncpy
char* my_strcpy(char* dest, const char* src, int num)
{
int count = 1;
char* ret = dest;
assert(dest != NULL);
assert(src != NULL);
/*while(*dest = *src)
{
if (count <= num)
count++;
else
return ret;
}*/
while(count <= num)
{
*dest = *src;
dest++;
src++;
count++;
}
if (count <= num)
{
*dest = '\0';
count++;
}
return ret;
}
int main()
{
char* str1 = "leborn";
char str2[] = "xxxxxxxxxxx";
char* str3 = my_strcpy(str2, str1, 6);
printf("str1 = %s\n", str1);
printf("str3 = %s\n", str3);
char* str4 = "leborn";
char str5[] = "xxxxxxxxxxx";
char* str6 = my_strcpy(str5, str4, 3);
printf("str4 = %s\n", str4);
printf("str6 = %s\n", str6);
char* str7 = "leborn";
char str8[] = "xxxxxxxxxxx";
char* str9 = my_strcpy(str8, str7, 8);
printf("str7 = %s\n", str7);
printf("str9 = %s\n", str9);
return 0;
}
运行结果
思路:
- 其实只需str稍加修改即可,因为我们多了一个新的传参int num来决定要拷贝多少个字符串,对应的我们设置一个变量count来计数,
- 在运来strcpy函数的while循环里面添加一个if和else,如果字符串还没结束或者拷贝的个数还不到num个,则count加一;如果已经拷贝了num个字符了,则直接返回ret退出,回到主函数中
-
有个特别注意的点,如果想继续使用原来的两个字符串来实现strncpy但n不同,一定要另外设置一组一样的字符串,不可继续使用,因为此时str2已经改变,可能会导致结果不符合预期。
4.2 strncat 函数的使用
4.2.1strncat函数的定义及其理解
总结下strncpy函数的几个特点及注意事项:
- 将source指向字符串的前num个字符追加到destination指向的字符串末尾,再追加⼀个 \0 字 符
- 如果source 指向的字符串的长度小于num的时候,只会将字符串中到 \0 的内容追加到destination指向的字符串末尾
4.3 strncmp函数的使用
4.3.1strncmp函数的定义及其理解
strncmp函数是用于⽐较str1和str2的前num个字符,如果相等就继续往后⽐较,最多⽐较num个字⺟,如果提前发现不⼀ 样,就提前结束,⼤的字符所在的字符串⼤于另外⼀个。如果num个字符都相等,就是相等返回0.
下面是strncmp的标准形式:
int strncmp ( const char * str1, const char * str2, size_t num )
5.其他一些特殊的字符串函数
5.1 strstr 的使用和模拟实现
5.1.1strstr函数的定义及其理解
strstr光从字面意思上比较难看出他的作用,其实它就是一个实现在一个字符串中查找另一个字符串,并且返回的是另一个字符串的地址的函数
strstr函数的标准形式
char * strstr ( const char * str1, const char * str2)
总结下strstr函数的几个特点及注意事项:
- 函数返回字符串str2在字符串str1中第一次出现的位置
- 字符串的比较匹配不包含 \0 字符,以 \0 作为结束标志
5.1.2 strstr函数的模拟实现
5.1.2.1 代码一:
//模拟字符串查找函数
char* sim_strstr(const char* arr1, const char* arr2)
{
char* sign1 = (char*)arr1;
char* sign2 = (char*)arr2;
while (*arr1 != '\0')
{
while (*++arr1 == *++arr2);//这里要使用前置++,因为如果使用后置++的话,arr2在比到最后的'\0'时还会再向后加1导致数组越界。
if (*arr2 == '\0')
{
return sign1;
}
sign1++;
arr1 = sign1;
arr2 = sign2;
}
return NULL;
}
int main()
{
char arr1[] = "abbcef";
char arr2[] = "bbc";
char* ret = sim_strstr(arr1, arr2);
if (ret)
printf("%s", ret);
else
printf("找不到");
return 0;
}
运行结果:
5.1.2.2 代码二 :
#include <stdio.h>
#include <assert.h>
//char* strstr(char* str1, const char* str2);
//查找子串,返回子串的起始地址,没找到返回空
char* mystrstr(char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1)
{
char* tmp = str2;
char* tar = str1;
while (*tar)
{
if (*tmp == 0)
{ //找到了
return str1;
}
if (*tar != *tmp)
{
break;
}
tar++;
tmp++;
}
++str1;
}
return null;
}
int main()
{
char str[] = "this is a simple string";
char* pch;
pch = mystrstr(str, "simple");
if (pch == null)
{
printf("无该子串\n");
}
else
printf("%s\n", pch);
return 0;
}
运行结果:
5.2 strtok 函数的使用
5.2.1strtok函数的定义及其理解
下面是strtok函数的标准形式:
char * strerror ( int errnum )
总结下strtok函数的几个特点及注意事项:
- sep参数指向⼀个字符串,定义了用作分隔符的字符集合
- 第⼀个参数指定⼀个字符串,它包含了0个或者多个由sep字符串中⼀个或者多个分隔符分割的标 记。
- strtok函数找到str中的下⼀个标记,并将其用 \0 结尾,返回⼀个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使⽤strtok函数切分的字符串⼀般都是临时拷贝的内容 并且可修改。)
- strtok函数的第⼀个参数不为 NULL ,函数将找到str中第⼀个标记,strtok函数将保存它在字符串 中的位置。
- strtok函数的第⼀个参数为 NULL ,函数将在同⼀个字符串中被保存的位置开始,查找下⼀个标记。
- 如果字符串中不存在更多的标记,则返回 NULL 指针
具体例子的引用:
int main()
{
char arr[] = "192.168.6.111";
char* sep = ".";
char* str = NULL;
for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
{
printf("%s\n", str);
}
return 0;
}
运行结果:
5.3 strerror 函数的使用
5.3.1 streror函数的定义及其理解
总结下strtok函数的几个特点及注意事项:
- strerror函数可以把参数部分错误码对应的错误信息的字符串地址返回来。
- 在不同的系统和C语⾔标准库的实现中都规定了⼀些错误码,⼀般是放在 errno.h 这个头⽂件中说明的,
- C语⾔程序启动的时候就会使⽤⼀个全面的变量errno来记录程序的当前错误码,只不过程序启动的时候errno是0,表示没有错误,
- 当我们在使⽤标准库中的函数的时候发⽣了某种错误,就会讲对应的错误码,存放在errno中,而⼀个错误码的数字是整数很难理解是什么意思,
- 所以每⼀个错误码都是有对应的错误信息的。strerror函数就可以将错误对应的错误信息字符串的地址返回。
5.3.2 查看错误码对应的错误信息
举例:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main ()
{
FILE * pFile;
pFile = fopen ("unexist.ent","r");//fopen以读(“r”)的形式打开文件的时候,
//如果文件不存在,就打开失败
if (pFile == NULL)
printf ("Error opening file unexist.ent: %s\n", strerror(errno));
return 0;
}
输出:
Error opening file unexist.ent: No such file or directory
也可以了解⼀下perror函数,perror函数相当于⼀次将上述代码中的第9⾏完成了,直接将错误信息打 印出来。perror函数打印完参数部分的字符串后,再打印⼀个冒号和⼀个空格,再打印错误信息。
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main ()
{
FILE * pFile;
pFile = fopen ("unexist.ent","r");
if (pFile == NULL)
perror("Error opening file unexist.ent");
return 0;
}
输出:
Error opening file unexist.ent: No such file or directory
perror有能力直接打印错误信息,打印的时候,先打印传给perror的字符串,然后打印冒号,再打印空格,最后打印错误码对应的错误信息。(perror = printf + strerror)
6. 一点额外的补充
可能有的同学在学习C语言的过程中被很多各种各样的零搞晕了头脑,所以在这里我将给大家理清楚几个不同形式的0之间的关系
7.总结
本章介绍了一些有关于跟字符和字符串有关的函数,知识点也比较多,里面这么多函数其实也并不是说就一定要全部熟记于心,我们知道大概有这么个函数,工作或者学习时需要用到再来查找资料也是可以的。而熟悉这些函数是如何实现的朋友,即使在后续忘了还有这些函数存在,我们也可以仿照这些函数的思路自己写一个。
最后非常非常感谢能看到这里的同学,你的点赞让我不停更新!
完