在编程的过程中,我们经常要处理字符和字符串,为了方便操作字符和字符串,C语言标准库中提供了一系列库函数,接下来我们就学习一下这些函数
一、字符分类函数
C语言中有一系列的函数是专门做字符分类的,也就是一个字符是属于什么类型的字符的。这些函数的使用都需要包含一个头文件是ctype.h
这些函数的使用方法非常类似,我们就讲解一个函数的事情,其他的非常类似:
int islower ( int c );
islower 是能够判断参数部分的 c 是否是小写字母的。通过返回值来说明是否是小写字母,如果是小写字母就返回非0的整数,如果不是小写字母,则返回0。
练习:写⼀个代码,将字符串中的小写字母转大写,其他字符不变。
#include<stdio.h>
#include<ctype.h>
int main()
{
char arr[] = "Hello World";
//因为字符串放在数组中他是有对应下标的,0也算一个
//将字符串中的字符逐个进行处理
//遇到小写,转换成大写,再输出
//如果不是小写,正常输出
int i = 0;
while (arr[i] != '\0')
{
if (islower(arr[i]))
{
arr[i] = arr[i] - 32;
}
printf("%c", arr[i]);
i++;
}
return 0;
}
二、字符转换函数
C语言提供了俩个字符转换函数
int tolower ( int c ); //将参数传进去的⼤写字⺟转⼩写
int toupper ( int c ); //将参数传进去的⼩写字⺟转⼤写
上面的代码,我们将小写转大写,是-32完成的效果,有了转换函数,就可以直接使用toupper函数。
#include<stdio.h>
#include<ctype.h>
int main()
{
char arr[] = "Hello World";
int i = 0;
while (arr[i] != '\0')
{
if (islower(arr[i]))
{
arr[i] = toupper(arr[i]);
}
printf("%c", arr[i]);
i++;
}
return 0;
}
三、strlen函数的使用和模拟实现
函数声明
- 声明:
size_t strlen(const char *str);
功能
- 统计参数
str
指向的字符串的长度。统计的是字符串中'\0'
之前的字符的个数。参数
str
:指针,指向了要统计长度的字符串。返回值
- 返回
str
指向的字符串的长度,返回的长度不会是负数,所以返回类型是size_t
。
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{
const char* str = "abcdef";
printf("%zu\n", strlen(str));
return 0;
}
使用注意事项
- 字符串以
'\0'
作为结束标志,strlen
函数返回的是在字符串中'\0'
前面出现的字符个数(不包含'\0'
)。- 参数指向的字符串必须要以
'\0'
结束。- 注意函数的返回值为
size_t
,是无符号的(易错)。strlen
的使用需要包含头文件<string.h>
。
2、strlen返回值
#include<stdio.h>
#include<string.h>
int main()
{
const char* str1 = "abcdef";
const char* str2 = "bbb";
if (strlen(str2) - strlen(str1) > 0)
{
printf("str2 > str1\n");
}
else
{
printf("str1 > str2\n");
}
return 0;
}
大家乍一看肯定都会觉得打印的肯定是 str1 > str2 因为这是我们学习前面的知识之后所就会默认觉得的内容,实则打印的结果是:str2 > str1,这是因为strlen函数的返回值是size_t类型的(无符号整型),我们用俩个size_t类型的数据相减,其结果也会返回一个size_t类型的数据。为了避免这种问题,应该直接比较两个长度,而不是进行减法运算。
如果我们就是想要返回我们认为的 str1 > str2,我们可以采取强制类型转换,将他转换为int类型。
#include<stdio.h>
#include<string.h>
int main()
{
const char* str1 = "abcdef";
const char* str2 = "bbb";
if ((int)strlen(str2) - (int)strlen(str1) > 0)
{
printf("str2 > str1\n");
}
else
{
printf("str1 > str2\n");
}
return 0;
}
3、模拟实现
方法一:计数器方式
#include<stdio.h>
#include<assert.h>
size_t my_strlen(const char* str)
{
int count = 0;
assert(str);
while (*str)
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "Hello World";
size_t len = my_strlen(arr);
printf("%zu\n", ret);
return 0;
}
方法二:递归
#include<stdio.h>
size_t my_strlen(const char* str)
{
if (*str != '\0')
return 1 + my_strlen(str + 1);
else
return 0;
}
int main()
{
char arr[] = "Hello World";
size_t len = my_strlen(arr);
printf("%zu\n", len);
return 0;
}
方法三:指针 - 指针
#include<stdio.h>
#include<assert.h>
size_t my_strlen(const char* str)
{
assert(str);
char* p = str;
while (*p != '\0')
p++;
return p - str;
}
int main()
{
char arr[] = "Hello World";
size_t len = my_strlen(arr);
printf("%zu\n", len);
return 0;
}
四、strcpy函数的使用和模拟实现
函数声明
- 声明:
char* strcpy(char * destination, const char * source );
功能
- 字符串拷贝,拷贝到源头字符串中的
\0
为止参数
destination
:指针,指向目的地空间source
:指针,指向源头数据返回值
- 返回目标空间的起始地址,返回类型是
char*
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = { 0 };
char arr2[] = "hello world";
//我们可能会想到不用这个函数直接使用循环的方式
//arr1 = arr2 (这是不能使用的)
//因为数组名是地址,地址是一个编号
//地址是一个常量值,不能被修改
//地址是指向空间的,但是地址不是空间
char* ret = strcpy(arr1, arr2);
printf("%s\n", ret);
return 0;
}
使用注意事项
- 源字符串必须以
'\0'
结束。- 会将源字符串中的
'\0'
拷贝到目标空间。- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可修改。
2、模拟实现
test1(基础版)
#include<stdio.h>
char* my_strcpy(char* dest, const char* src)
//dest不能加const,因为他要去被修改
{
char* ret = dest;
//拷贝\0前面的字符
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
}
*dest = *src;//拷贝\0
return ret;
}
int main()
{
char arr1[] = "hello world";
char arr2[20] = "******************";
my_strcpy(arr2, arr1);
printf("%s\n", arr2);
}
test2(升级版)
#include <stdio.h>
#include <assert.h>
//1.参数顺序
//2.函数的功能,停⽌条件
//3.assert
//4.const修饰指针
//5.函数返回值
char* my_strcpy(char* dest, const char* src)
{
char* ret = dest;
assert(dest != NULL);
assert(src != NULL);
while ((*dest++ = *src++))
{
;//先使用再++
}
return ret;//返回目标空间的起始地址
}
int main()
{
char arr1[10] = { 0 };
char arr2[] = "hello";
my_strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
五、strcat函数的使用和模拟实现
函数声明
- 声明:
char * strcat ( char * destination, const char * source );
功能
- 字符串追加,把
source
指向的源字符串中的所有字符都追加到destination
指向的空间中。参数
destination
:指针,指向目的地空间source
:指针,指向源头数据返回值
- 返回目标空间的起始地址,返回类型是
char*
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = "hello";
char arr2[] = " world";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
使用注意事项
- 源字符串必须以
'\0'
结束。- 目标字符串中也得有
'\0'
,否则没办法知道追加从哪里开始。- 目标空间必须足够大,能容纳下源字符串的内容。
- 目标空间必须可修改。
2、模拟实现
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
char* ret = dest;
assert(dest != NULL);
assert(src != NULL);
//找到目标空间中的第一个\0
while (*dest)
{
dest++;
}
//从\0后面的位置开始追加
while ((*dest++ = *src++))
{
;
}
return ret;
}
int main()
{
char arr1[20] = "hello";
char arr2[] = " world";
my_strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
六、strcmp函数的使用和模拟实现
函数声明
- 声明:
int strcmp ( const char * str1, const char * str2 );
功能
- 用来比较
str1
和str2
指向的字符串,从两个字符串的第一个字符开始比较,如果两个字符的 ASCII 码值相等,就比较下一个字符。直到遇到不相等的两个字符,或者字符串结束。参数
str1
:指针,指向要比较的第一个字符串str2
:指针,指向要比较的第二个字符串返回值
- 标准规定:
- 第一个字符串大于第二个字符串,则返回大于 0 的数字
- 第一个字符串等于第二个字符串,则返回 0
- 第一个字符串小于第二个字符串,则返回小于 0 的数字
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abq";
int ret = strcmp(arr1, arr2);
printf("%d\n", ret);
if (ret > 0)
printf("arr1 > arr2\n");
else if (ret == 0)
printf("arr1 == arr2\n");
else
printf("arr1 < arr2\n");
return 0;
}
2、模拟实现
#include<stdio.h>
#include<assert.h>
int my_strcmp(const char* arr1, const char* arr2)
{
int ret = 0;
assert(arr1 != NULL);
assert(arr2 != NULL);
while (*arr1 == *arr2)
{
if (*arr1 == '\0')
return 0;
arr1++;
arr2++;
}
return *arr1 - *arr2;
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abq";
int ret = my_strcmp(arr1, arr2);
if (ret > 0)
printf("arr1 > arr2\n");
else if (ret == 0)
printf("arr1 = arr2\n");
else
printf("arr1 < arr2\n");
return 0;
}
七、strstr函数的使用和模拟实现
函数声明
- 声明:
char * strstr ( const char * str1, const char * str2);
功能
strstr
函数,查找str2
指向的字符串在str1
指向的字符串中第一次出现的位置。- 简而言之:在一个字符串中查找子字符串。
strstr
的使用得包含<string.h>
参数
str1
:指针,指向了被查找的字符串str2
:指针,指向了要查找的字符串返回值
- 如果
str1
指向的字符串中存在str2
指向的字符串,那么返回第一次出现位置的指针- 如果
str1
指向的字符串中不存在str2
指向的字符串,那么返回NULL
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{
char* str1 = "AABCD";
char* str2 = "AB";
char* ret = strstr(str1, str2);
//会找到AB(即相同部分)之后的地址
printf("%s\n", ret);//会输出ABCD
//如果没有相同部分的地址就返回NULL
return 0;
}
2、模拟实现(难点)
#include<stdio.h>
#include<assert.h>
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
char* p = str1;
char* s1 = str1;
char* s2 = str2;
while (*p)
{
if (*str2 == '\0')
return str1;
s1 = p;
s2 = str2;
while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
return p;
p++;
}
return NULL;
}
int main()
{
char arr1[] = "abbcdef";
char arr2[] = "bbc";
char* p = my_strstr(arr1, arr2);
if (p != NULL)
printf("找到了,%s\n", p);
else
printf("找不到\n");
return 0;
}
八、strncpy函数的使用
函数声明
- 声明:
char * strncpy ( char * destination, const char * source, size_t num );
功能
- 字符串拷贝;将
source
指向的字符串拷贝到destination
指向的空间中,最多拷贝num
个字符。参数
destination
:指针,指向目的地空间source
:指针,指向源头数据num
:从source
指向的字符串中最多拷贝的字符个数返回值
strncpy
函数返回的目标空间的起始地址
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[20] = { 0 };
char arr2[] = "abcdefghi";
char* str = strncpy(arr1, arr2, 5);
//将arr2中的前五个字符拷贝到arr1中,如果不够会补\0
printf("%s\n", arr1);
printf("%s\n", str);
return 0;
}
2、模拟实现
#include<stdio.h>
#include<assert.h>
char* my_strncpy(char* dest, const char* src, size_t num)
{
char* ret =* dest;
assert(dest != NULL);
assert(src != NULL);
while (num)
{
*dest = *src;
dest++;
src++;
num--;
}
return ret;
}
int main()
{
char arr1[] = "hello world";
char arr2[20] = "******************";
int k = 0;
scanf("%d", &k);
my_strncpy(arr2, arr1, k);
printf("%s", arr2);
return 0;
}
3、与strcpy对比
- 关于
strcpy
:
- 它的作用是进行字符串拷贝,会一直拷贝直到遇到源字符串的结束符
\0
。- 存在的问题是,如果目标空间不足以容纳源字符串,就很容易出现越界行为,可能会导致程序出现不可预料的错误,比如覆盖其他内存区域的数据等。
- 关于
strncpy
:
- 它指定了拷贝的字符长度,这是它与
strcpy
一个很重要的区别。- 源字符串不一定需要以
\0
结束。- 由于在使用时需要考虑目标空间的大小是否够用,所以相对
strcpy
函数来说更加安全,能在一定程度上避免缓冲区溢出等问题。
九、strnact函数的使用
函数声明
- 声明:
char * strncat ( char * destination, const char * source, size_t num );
功能
- 字符串追加;将
source
指向的字符串的内容,追加到destination
指向的空间,最多追加num
个字符。参数
destination
:指针,指向了目标空间source
:指针,指向了源头数据num
:最多追加的字符的个数返回值
- 返回的是目标空间的起始地址
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "hello";
char arr2[] = " world";
char* str = strncat(arr1, arr2, 5);
//我们追加的时候是从第一个\0开始追加的
//追加完成之后会补\0
//如果追加到字符数超过了原有字符数
//只要追加上原来的即可
printf("%s\n", arr1);
printf("%s\n", str);
return 0;
}
2、模拟实现
#include<stdio.h>
#include<assert.h>
char* my_strncat(char* dest, const char* src, size_t num)
{
char* ret = dest;
assert(dest != NULL);
assert(src != NULL);
while (*dest)
{
dest++;
}
while (num)
{
*dest = *src;
dest++;
src++;
num--;
}
return ret;
}
int main()
{
int k = 0;
scanf("%d", &k);
char arr1[20] = "hello ";
char arr2[] = "world";
my_strncat(arr1, arr2, k);
printf("%s", arr1);
return 0;
}
3、与strcat对比
- 参数不同:
strncat
多了一个参数。- 追加内容处理:
strcat
函数在追加的时候要将源字符串的所有内容,包含\0
都追加过去,但是strncat
函数指定了追加的长度。- 源字符串结束符要求:
strncat
函数中源字符串中不一定要有\0
了。- 灵活性和安全性:
strncat
更加灵活,也更加安全。
十、strncmp函数的使用
函数声明
- 声明:
int strncmp ( const char * str1, const char * str2, size_t num );
功能
- 字符串比较;比较
str1
和str2
指向的两个字符串的内容,最多比较num
字符。参数
str1
:指针,指向一个比较的字符串str2
:指针,指向另外一个比较的字符串num
:最多比较的字符个数返回值
- 标准规定:
- 第一个字符串大于第二个字符串,则返回大于 0 的数字
- 第一个字符串等于第二个字符串,则返回 0
- 第一个字符串小于第二个字符串,则返回小于 0 的数字
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcqw";
int ret1 = strncmp(arr1, arr2, 3);
printf("%d\n", ret1);
int ret2 = strncmp(arr1, arr2, 4);
printf("%d\n", ret2);
return 0;
}
2、模拟实现
#include<stdio.h>
#include<assert.h>
int my_strncmp(const char* str1, const char* str2, size_t num)
{
assert(str1 != NULL);
assert(str2 != NULL);
int ret = 0;
while (num)
{
if (*str1 == *str2)
{
if (*str1 == 0)
{
break;
}
str1++;
str2++;
}
else
{
ret = *(unsigned char*)str1 - *(unsigned char*)str2;
break;
}
num--;
}
return ret;
}
int main()
{
char arr1[] = "abcd";
char arr2[] = "abd";
int k = 0;
scanf("%d", &k);
int ret = my_strncmp(arr1, arr2, k);
if (ret > 0)
printf("arr1 > arr2");
else if (ret == 0)
printf("arr1 = arr2");
else
printf("arr1 < arr2");
return 0;
}
3、与strcmp对比
- 参数不同:
strncmp
比strcmp
多了一个用于指定最多比较字符个数的参数。- 比较长度方面:
strncmp
可以比较任意长度的字符串(通过指定num
参数来控制比较的字符个数)。- 灵活性和安全性:
strncmp
更加灵活,也更加安全,因为它可以通过指定比较长度来避免一些不必要的问题,例如在处理不确定长度的字符串时可以更好地控制比较过程。
十一、strtok函数的使用
函数声明
- 声明:
char *strtok(char *str, const char *delim);
功能
- 分割字符串:根据
delim
参数中指定的分隔符,将输入字符串str
拆分成多个子字符串。- 修改原始字符串:
strtok
会直接在原始字符串中插入'\0'
终止符,替换分隔符的位置,因此原始字符串会被修改。参数
str
:首次调用时传入待分割的字符串;后续调用传入NULL
,表示继续分割同一个字符串。delim
:包含所有可能分隔符的字符串(每个字符均视为独立的分隔符)。返回值
- 成功时返回指向当前子字符串的指针。
- 没有更多子字符串时返回
NULL
。使用步骤
- 首次调用:传入待分割字符串和分隔符。
- 后续调用:传入
NULL
和相同的分隔符,继续分割。- 结束条件:当返回
NULL
时,表示分割完成。
1、代码演示
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "zpengwei@yeah.net";
char sep[] = "@.";
char buf[20] = { 0 };
strcpy(buf, sep);
//我们将arr拷贝一份到buf中,修改buf不去动arr
//第一次调用
//第一次调用会将zpengwei@yeah.net中的@改为\0
//即zpengwei\0yeah.net并且返回z的地址
char* p1 = strtok(buf, sep);
printf("%s\n", p1);//zpengwei
//后续调用都传入空指针
//第二次调用会将zpengwei\0yeah.net中的.改为\0
//即zpengwei\0yeah\0net并且返回y的地址
char* p2 = strtok(NULL, sep);
printf("%s\n", p2);//yeah
char* p3 = strtok(NULL, sep);
printf("%s\n", p3);//net
//如果还有p4则会返回NULL
char* p4 = strtok(NULL, sep);
printf("%s\n", p4);//(null)
return 0;
}
这段代码也可以采取循环的方式去优化
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "zpengwei@yeah.net";
char sep[] = "@.";
char buf[200] = { 0 };
strcpy(buf, arr);
char* p = NULL;
for (p = strtok(buf, sep); p != NULL; p = strtok(NULL, sep))
{
printf("%s\n", p);
}
return 0;
}
2、注意事项
破坏性操作
strtok
会直接修改原始字符串,将其中的分隔符替换为'\0'
。如果需要保留原字符串,应先拷贝一份。连续分隔符
- 多个连续的分隔符会被视为单个分隔符,不会返回空字符串。
空指针处理
- 如果输入的
str
为NULL
且没有前序调用,行为未定义。
十二、strerror函数的使用
函数声明
char* strerror ( int errnum );
功能
strerror
函数可以通过参数部分的errnum
表示错误码,得到对应的错误信息,并且返回这个错误信息字符串首字符的地址。strerror
函数只针对标准库中的函数发生错误后设置的错误码的转换。strerror
的使用需要包含<string.h>
。在不同的系统和 C 语言标准库的实现中都规定了一些错误码,一般是放在
<errno.h>
这个头文件中说明的。C 语言程序启动的时候就会使用一个全局的变量errno
来记录程序的当前错误码,只不过程序启动的时候errno
是 0,表示没有错误。当我们在使用标准库中的函数的时候发生了某种错误,就会将对应的错误码存放在errno
中。而一个错误码的数字是整数,很难理解是什么意思,所以每一个错误码都是有对应的错误信息的。strerror
函数就可以将错误码对应的错误信息字符串的地址返回。参数
errnum
:表示错误码。这个错误码一般传递的是
errno
这个变量的值,在 C 语言中有一个全局的变量叫errno
,当库函数的调用发生错误的时候,就会将本次错误的错误码存放在errno
这个变量中。使用这个全局变量需要包含头文件<errno.h>
。返回值
函数返回通过错误码得到的错误信息字符串的首字符的地址。
1、代码演示
#include <errno.h>
#include <string.h>
#include <stdio.h>
//我们打印⼀下0~10这些错误码对应的信息
int main()
{
int i = 0;
for (i = 0; i <= 10; i++) {
printf("%d: %s\n", i, strerror(i));
}
return 0;
}
#include <stdio.h>#include <string.h>
#include <errno.h>
int main ()
{
FILE * pFile = NULL;
//fopen函数以读的形式打开⽂件,如果⽂件不存在,则打开失败。
pFile = fopen ("unexist.ent", "r");
if (pFile == NULL)
{
printf ("错误信息是:%s\n", strerror(errno));
return 1;//错误返回
}
return 0;
}
2、perror
也可以了解一下 perror 函数, perror 函数相当于一次将上述代码中的第11行完成了,直接将错误 信息打印出来。 perror 函数打印完参数部分的字符串后,再打印一个冒号和一个空格,再打印错误信息。
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main ()
{
FILE * pFile = NULL;
pFile = fopen ("unexist.ent", "r");
if (pFile == NULL)
{
perror("错误信息是");
return 1;
}
return 0;
}