【C语言】字符函数与字符串函数实战:用法原理 + 模拟实现
在编程的过程中,我们经常要处理字符和字符串,为了方便操作字符和字符串,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);elsereturn 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;//拷贝\0return 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);//找到目标空间中的第一个\0while (*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");elseprintf("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");elseprintf("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//如果没有相同部分的地址就返回NULLreturn 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);elseprintf("找不到\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中,如果不够会补\0printf("%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");elseprintf("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);//yeahchar* p3 = strtok(NULL, sep);printf("%s\n", p3);//net//如果还有p4则会返回NULLchar* 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;
}