文章目录
- 前言
- 1. 字符分类函数
- 2. 字符转换函数
- 3. strlen
- 3. 1 strlen 的使用
- 3. 2 strlen 的模拟实现
- 4. strcpy
- 4. 1 strcpy 的使用
- 4. 2 strcpy 的模拟实现
- 5. strcat
- 5. 1 strcat 的使用
- 5. 2 strcat 的模拟实现
- 6. strcmp
- 6. 1 strcmp 的使用
- 6. 2 strcmp 的模拟实现
- 7. strncpy 函数的使用
- 8. strncat 函数的使用
- 9. strncmp 函数的使用
- 10. strstr
- 10. 1 strstr 的使用
- 10. 2 strstr 的模拟实现
- 11. strtok 函数的使用
- 12. strerror函数的使用
前言
在编程的过程中,我们经常要处理字符和字符串,为了方便操作字符和字符串,C语言标准库中提供了一系列库函数,接下来我们就了解一下这些函数。
1. 字符分类函数
C语言中有一系列的函数是专门做字符分类的,也就是一个字符是属于什么类型的字符的。
这些函数的使用都需要包含一个头文件是 ctype.h
cplusplus上的 ctype.h 。
下面是常用的字符分类函数:
可以看出来,这些函数的用法及其相似,因此我们借助其中一个进行讲解。
int islower (int c);
islower
是能够判断参数部分的c是否是小写字母的函数。
通过返回值来说明是否是小写字母,如果是小写字母就返回非0的整数,
如果不是小写字母,则返回0。
来做一个测试:
写一个代码,将字符串中的小写字母转大写,其他字符不变。
#include<stdio.h>
#include<ctype.h>
#include<string.h>
void Upper(char* c)
{
(*c) -= 32;//大写字母和小写字母的差是32
}
int main()
{
char str[] = "Hello World";
for (int i = 0; i < strlen(str); i++)//strlen 函数返回的是 str 的长度,后面会介绍
{
if (islower(str[i]))
Upper(&str[i]);
}
printf("%s", str);
}
输出结果为:
2. 字符转换函数
C语言提供两个字符转换函数:
int tolower ( int c ); //将参数传进去的⼤写字母转⼩写
int toupper ( int c ); //将参数传进去的⼩写字母转⼤写
这两个函数同样也在 ctype.h
中。
上面的代码,我们将小写转大写,是 -32
完成的效果,有了转换函数,就可以直接使用 tolowe
r 函数。
修改后的代码:
int main()
{
char str[] = "Hello World";
for (int i = 0; i < strlen(str); i++)
{
if (islower(str[i]))
str[i] = toupper(str[i]);
}
printf("%s", str);
}
注:以下函数均包括于string.h中。
3. strlen
3. 1 strlen 的使用
size_t strlen ( const char * str );
注意:
- 字符串以
'\0'
作为结束标志,strlen
函数返回的是在字符串中'\0'
前面出现的字符个数
(不包含'\0'
)。 - 参数指向的字符串必须要以
'\0'
结束。 - 注意函数的返回值为
size_t
,是无符号的! - strlen 的使用需要包含头文件(
string.h
)
关于它的使用,也就是将一个字符串的首元素地址传递过去,然后就可以接收它的返回值了。
示例:
#include<stdio.h>
#include<string.h>
int main()
{
char* a = "bbb";
char b[] = "abcd";
printf("%zd %zd", strlen(a), strlen(b));//strlen 的返回值是 size_t 类型的,打印的时候用%zd
return 0;
}
运行结果:
3. 2 strlen 的模拟实现
模拟实现 strlen
的核心思路其实就是:找到字符串的 ‘\0’ ,然后找到其与字符串首元素中间的字符个数。
方法一:计数器
#include<stdio.h>
#include<string.h>
size_t my_strlen(char* str)
{
//计数器
size_t count = 0;
char* cur = str;
while (*cur != '\0')//这里也可以直接写成 while(*cur),因为'\0'的码值为 0
cur++,count++;
return count;
}
int main()
{
char* a = "abcdefg";
printf("%zd\n", strlen(a));//对照
printf("%zd\n", my_strlen(a));
return 0;
}
方法二:指针-指针
#include<stdio.h>
#include<string.h>
size_t my_strlen(char* str)
{
//指针-指针
char* cur = str;
while (*cur)
cur++;
return cur - str;//指针-指针得到的是指针之间的元素个数
}
int main()
{
char* a = "abcdefg";
printf("%zd\n", strlen(a));
printf("%zd\n", my_strlen(a));
return 0;
}
方法三:不创建临时变量
#include<stdio.h>
#include<string.h>
size_t my_strlen(char* str)
{
//不创建临时变量
if (!*str)//递归的终止条件
return 0;
else
return 1 + my_strlen(str + 1);
}
int main()
{
char* a = "abcdefg";
printf("%zd\n", strlen(a));
printf("%zd\n", my_strlen(a));
return 0;
}
不创建临时变量模拟实现 strlen
使用到了函数递归,如果你对上面的代码有疑惑,可以看函数递归与迭代这篇博客,这里不再过多赘述。
4. strcpy
4. 1 strcpy 的使用
char* strcpy(char * destination, const char * source );
它的作用就是将 source
中的字符串拷贝到 destination
中。
- 源字符串必须以
'\0'
结束。 - 会将源字符串中的
'\0'
拷贝到目标空间。 - 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可修改。
使用范例:
#include<stdio.h>
#include<string.h>
int main()
{
char str1[] = "abcd";
char str2[] = "efghxxxxx";
printf("str1 = %s\nstr2 = %s\n\n", str1, str2);
strcpy(str2, str1);
printf("str1 = %s\nstr2 = %s\n\n", str1, str2);
return 0;
}
结果:
4. 2 strcpy 的模拟实现
要想实现这个函数,我们要先搞清楚 strcpy
是怎么工作的,在拷贝结束之后,str2 里究竟放的是什么, 我们可以通过调试上面的代码来得到答案:
可以看到,在执行 strcpy
函数后, str2
里实际存放的不只是 str1
的数据,只是在拷贝的时候将 str1
的 '\0'
拷贝过来了,所以 str2
的内容变得和 str1
一样了。
这样,我们就可以开始尝试模拟实现 strcpy
函数了。
#include<stdio.h>
char* my_strcpy(char* destination, const char* source)
{
//注意返回值,返回的是复制结束后 destination 的首元素地址
char* ret = destination;
while (*destination++ = *source++);
*destination = '\0';//可不要忘了'\0'!
return ret;
}
int main()
{
char str1[] = "abcd";
char str2[] = "efghxxxxx";
printf("str1 = %s\nstr2 = %s\n\n", str1, str2);
my_strcpy(str2, str1);
printf("str1 = %s\nstr2 = %s\n\n", str1, str2);
return 0;
}
5. strcat
5. 1 strcat 的使用
char *strcat(char *dest, const char*src)
strcat
用于连接两个字符串,将 src
中的字符串连接到 dest
字符串的后面,连接时会删除掉 src
后面的'\0'
。
- 源字符串必须以
'\0'
结束。 - 目标字符串中也得有
'\0'
,否则没办法知道追加从哪里开始。 - 目标空间必须可修改。
- 目标空间必须足够大,能容纳下源字符串的内容。
使用范例:
#include<stdio.h>
#include<string.h>
int main()
{
char str1[50] = "abcd";//注意目标字符串要足够大
char str2[] = "efghxxxxx";
strcat(str1, str2);
printf("%s", str1);
return 0;
}
运行结果:
5. 2 strcat 的模拟实现
在这里我们不妨多思考一下:在这个函数的模拟实现中,我们必然会涉及到指针的解引用,那么为了防止空指针的解引用,我们可以使用 assert
进行断言,这样在排查问题时也更方便。
#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);//断言 dest 和 src 这两个指针都不为空
//注意返回值,也是返回目标字符串的首元素地址
char* ret = dest;
while (*dest)//找 dest 字符串的'\0'
dest++;
while (*dest++ = *src++);
*dest = '\0';//这两步和 strcpy 是一样的
}
int main()
{
char str1[50] = "abcd";//注意目标字符串要足够大
char str2[] = "efghxxxxx";
my_strcat(str1, str2);
printf("%s", str1);
return 0;
}
不仅是 strcat
函数,实际上 strlen
和 strcpy
的模拟实现中都可以加入 assert
断言来增强代码的健壮性。
6. strcmp
6. 1 strcmp 的使用
int strcmp (const char * str1, const char * str2)
这个函数用来比较两个字符串是否相同,关于它的返回值,我们不妨来看看cplusplus上对返回值的描述。
也就是说:
第一个字符串大于第二个字符串,则返回大于 0 的数字
第一个字符串等于第二个字符串,则返回 0
第一个字符串小于第二个字符串,则返回小于 0 的数字
strcpm
是如何比较两个字符串?
比较两个字符串中对应位置上字符ASCII码值的大小。
使用范例:
#include<stdio.h>
#include<string.h>
int main()
{
char str1[] = "abcd";
char str2[] = "abcd";
char str3[] = "abce";
printf("%d %d", strcmp(str1, str2), strcmp(str1, str3));
return 0;
}
输出结果:
6. 2 strcmp 的模拟实现
#include<stdio.h>
int my_strcmp(const char* str1, const char* str2)
{
while (*str1 || *str2)//当 str1 和 str2 有一个不为 '\0' 时进行循环
{
if (*str1 != *str2)
return *str1 - *str2;//返回的就是两个字符串中第一个不相同字符的码值的差
str1++, str2++;
}
return 0;
}
int main()
{
char str1[] = "abcd";
char str2[] = "abcd";
char str3[] = "abce";
printf("%d %d", my_strcmp(str1, str2), my_strcmp(str1, str3));
return 0;
}
7. strncpy 函数的使用
char * strncpy ( char * destination, const char * source, size_t num );
用法和 strcpy
基本一致,只是多了一个参数来控制复制多少元素过去。
- 拷贝
num
个字符从源字符串到目标空间。 - 如果源字符串的长度小于
num
,则拷贝完源字符串之后,在目标的后边追加\0
,直到num
个。 - 拷贝完成后,如果没有到源字符串的末尾,不会追加
'\0'
。
测试:
#include<stdio.h>
#include<string.h>
int main()
{
char str1[50] = "abcdxxxxxxxxxxxxxxxxxxxxxxxx";
char str2[] = "efghxxxxx";
strncpy(str1, str2, 13);
printf("%s", str1);
return 0;
}
我们通过调试观察 str1 的内容变化。
8. strncat 函数的使用
char * strncat ( char * destination, const char * source, size_t num );
- 将
source
指向字符串的前num
个字符追加到destination
指向的字符串末尾,再追加一个'\0'
字符。 - 如果
source
指向的字符串的长度小于num
的时候,只会将字符串中到'\0'
的内容追加到destination
指向的字符串末尾
测试:
#include <stdio.h>
#include <string.h>
int main()
{
char str1[20];
char str2[20];
strcpy(str1, "To be ");//将"To be"放进 str1 中
strcpy(str2, "or not to be");
strncat(str1, str2, 6);
printf("%s\n", str1);
return 0;
}
结果:
9. strncmp 函数的使用
int strncmp ( const char * str1, const char * str2, size_t num );
比较str1
和str2
的前num
个字符,如果相等就继续往后比较,最多比较num
个字母,如果提前发现不一样,就提前结束。如果num
个字符都相等,就是相等返回0
.
返回值与 strcmp
一致。
10. strstr
10. 1 strstr 的使用
char * strstr ( const char * str1, const char * str2);
- 函数返回指向字符串
str2
在字符串str1
中第一次出现的位置的指针。 - 字符串的比较匹配不包含
'\0'
字符,以'\0'
作为结束标志)。
示例:
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "This is a simple string";
char* pch;
pch = strstr(str, "simple");//找到 simple 第一次在 str 中出现的位置,pch指向 s.
strncpy(pch, "sample", 6);//使用 strcpy 会有 '\0'
printf("%s\n", str);
return 0;
}
结果:
10. 2 strstr 的模拟实现
大致思路:先在 str1
中遍历找和 str2
首元素相同的元素,找到了就将此时的 str1
复制出来,开始同时遍历两个字符串,判断两个字符串是否相同,如果相同,返回 str1
,不相同就继续遍历。
#include <stdio.h>
char* my_strstr(const char* str1, const char* str2)
{
while (*str1)
{
if (*str1 == *str2)
{
char* tmp1 = str1;
char* tmp2 = str2;
while (*tmp1 && *tmp2)
{
if (*tmp1 != *tmp2)
break;
tmp1++, tmp2++;
}
if (!*tmp2)//如果循环结束的时候 tmp2 是'\0',说明上面的循环走到底了,也就是找到了
return str1;
}
str1++;
}
return NULL;
}
当然,上面那里在找到与 str2
首元素相同的元素之后,也可以使用 strcmp
函数进行比较。
11. strtok 函数的使用
strtok
函数用于分割字符串。
char * strtok ( char * str, const char * sep);
sep
参数指向一个字符串,定义了用作分隔符的字符集合(标记)。- 第一个参数指定一个字符串,它包含了0个或者多个由
sep
字符串中一个或者多个分隔符分割的标记。 strtok
函数找到str
中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针。strtok
函数会改变被操作的字符串,所以被strtok
函数切分的字符串一般都是临时拷贝的内容并且可修改。strtok
函数的第一个参数不为NULL
,函数将找到str
中第一个标记,strtok
函数将保存它在字符串中的位置。strtok
函数的第一个参数为NULL
,函数将在上一次传递的字符串中被保存的位置开始,查找下一个标记。(也就是说,strtok
函数可以连续调用,连续调用时第一个参数传NULL
)- 如果字符串中不存在标记,则返回
NULL
。
示例:
#include <stdio.h>
#include <string.h>
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;
}
结果:
12. strerror函数的使用
char* strerror ( int errnum );
strerror
函数可以把参数部分错误码对应的错误信息的字符串地址返回来。
在不同的系统和C语言标准库的实现中都规定了一些错误码,一般是放在 errno.h
这个头文件中说明的,C语言程序启动的时候就会使用一个全局的变量errno
来记录程序的当前错误码,只不过程序启动的时候errno
是 0 ,表示没有错误,当我们在使用标准库中的函数的时候发生了某种错误,就会将对应的错误码存放在errno
中,而一个错误码的数字是整数很难理解是什么意思,所以每一个错误码都是有对应的错误信息的。strerror
函数就可以将错误对应的错误信息字符串的地址返回。
举例:
#include <errno.h>
#include <string.h>
#include <stdio.h>
//我们打印一下0-10这些错误码对应的信息
int main()
{
int i = 0;
for (i = 0; i <= 10; i++) {
printf("%s\n", strerror(i));
}
return 0;
}
在我使用的环境 win11+VS2022 下输出结果为:
No error
Operation not permitted
No such file or directory
No such process
Interrupted function call
Input/output error
No such device or address
Arg list too long
Exec format error
Bad file descriptor
No child processes
使用举例:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
FILE* pFile;
pFile = fopen("unexist.ent", "r");//尝试打开名为 unexist.ent 的文件,但是并没有找到,也就是打开失败了,那么 pfile 就是 NULL
if (pFile == NULL)
printf("Error opening file unexist.ent: %s\n", strerror(errno));
return 0;
}
输出:
也可以了解一下 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;
}
输出结果同上。
如果喜欢的话不妨顺手点个赞,收藏,评论,关注!
我会持续更新更多优质文章!!