目录
- 1.字符分类函数
- [2. 字符转换函数](#2. 字符转换函数)
- [3. strlen](#3. strlen)
-
- [3.1 代码演示](#3.1 代码演示)
- 3.2strlen的返回值
- [3.3 strlen的模拟实现](#3.3 strlen的模拟实现)
- [4. strcpy](#4. strcpy)
-
- 4.1代码演示
- [4.2 模拟实现](#4.2 模拟实现)
- [5. strcat](#5. strcat)
-
- [5.1 代码演⽰](#5.1 代码演⽰)
- [5.2 模拟实现](#5.2 模拟实现)
- [6. strcmp](#6. strcmp)
-
- [6.1 代码演⽰:](#6.1 代码演⽰:)
- [6.2 模拟实现:](#6.2 模拟实现:)
- [7. strncpy](#7. strncpy)
-
- [7.1 代码演⽰](#7.1 代码演⽰)
- 7.2模拟实现
- [8. strncat](#8. strncat)
-
- [8.1 代码演⽰](#8.1 代码演⽰)
- [8.2 模拟实现](#8.2 模拟实现)
- [9. strncmp](#9. strncmp)
-
- [9.1 代码演⽰](#9.1 代码演⽰)
- 9.2模拟实现
- [10. strstr](#10. strstr)
-
- [10.1 代码演示](#10.1 代码演示)
- 10.2模拟实现
- [11. strtok 函数的使用](#11. strtok 函数的使用)
- [12. strerror 函数的使用](#12. strerror 函数的使用)
-
- [12.2 perror](#12.2 perror)
1.字符分类函数
C语言中有一类函数是用来专门判断字符是属于什么类型的,这类函数叫做字符分类函数。这些函数的使⽤都需要包含⼀个头⽂件ctype.h。
- 字母数字类
isalmum(c):判断所给的字符是否为字母(含大小写)或数字(0~9,a ~z,A ~ Z)isalpha(c):判断是否是字母(包含大小写)
- 数字类
isdigit(c):判断字符是否是十进制的数字(0~9)isxdigit(c):判断是否是十六进制数字(0-9,a-f,A-F)
- 大小写字母
islower(c):判断是否是小写字母(a~z)isupper(c):判断是否是大写字母(A~Z)
- 空白,控制字符
isspace(c):判断是否是空白字符:空格,制表符,\t,换行\t,回车\r,换页\f,垂直制表符\viscntrl(c):判断是否是控制字符(ascii0~31,127)
- 标点,可打印字符
ispunct(c):判断是否是标点符号(不是字母,数字,空白的可打印字符)isprint(c):判断是否是可打印字符(包括空格,字母,数字,标点)isgraph(c):判断是否是可显示字符(可打印但不包括空格)
特别提醒:控制字符在C语言里就是看不见,不打印,只是用来控制设备行为的字符,比如\0--字符串结束标志,\a--响铃,\n--换行等。ascii里0-31和127都是控制字符。
这些函数如果判断为真,就返回一个真实值,是整型类型,下面举例具体用法:
例子1:
判断一个字符是否是大小字母:
c
#include<stdio.h>
#include<ctype.h>
int main()
{
char c;
//这里敲下的回车键就是换行符,scanf(("%c", &c)读取的时候也会读取换行符
while (scanf("%c", &c)==1)
{
int r = isupper(c);
if (r == 1)
{
printf("这个字符是大写字母\n");
}
else
{
printf("这个字符不是大写字符\n");
}
}
return 0;
}

出现这个情况的原因是我们敲下回车键的时候,换行符\n也会敲进输入缓冲区,scanf("%c", &c)会读取缓冲区的任意字符,所以会多循环一次。
例子2:
写⼀个代码,将字符串中的⼩写字⺟转⼤写,其他字符不变。
c
int main()
{
char a[100];
while (scanf("%[^\n]", a) != EOF)
{
int i = 0;
for (i = 0; a[i] != '\0'; i++)
{
if (islower(a[i]))
{
printf("%c", a[i] - 32);
}
else
{
printf("%c", a[i]);
}
}
getchar();//消耗缓冲区的换行符。
printf("\n");
}
return 0;
}

这个函数中,a[i] - 32是将小写字符转化为大写字符,在ascii里,小写字符-大写字符=32。
2. 字符转换函数
C语言里提供了两个转换函数:
int tolower ( int c ); //将参数传进去的⼤写字⺟转⼩写
int toupper ( int c ); //将参数传进去的⼩写字⺟转⼤写
有了转换函数,我们可以直接使用转换函数来改变字母的大小写
c
int main()
{
char a[100];
while (scanf("%[^\n]", a) != EOF)
{
int i = 0;
for (i = 0; a[i] != '\0'; i++)
{
if (islower(a[i]))
{
printf("%c", toupper(a[i]));
}
else
{
printf("%c", a[i]);
}
}
getchar();//消耗缓冲区的换行符。
printf("\n");
}
return 0;
}
效果和-32是一样的,
3. strlen
原型:
size_t strlen(const char* str)
输入一个字符串,统计这个字符串前\0的字符个数
const char* str:指向要统计的字符串- 返回值:字符串的字符个数
3.1 代码演示
c
int main()
{
const char* str = "abcdef";
printf("%zd\n", strlen(str));//6
return 0;
}
- 字符串默认以
\0结尾,strlen统计的是\0之前字符的个数。 - 参数指向的字符串一定要以
\0结尾,如果是用字符数组来存储字符串则一定要在末尾添上\0。形如const char str[] = {'a','s','d','\0'};. - strlen的使用需要包含头文件
<string.h> - 注意函数的返回值为
size_t,是⽆符号的
3.2strlen的返回值
前面提到了函数的返回值是无符号数的,也就是说返回值没有负数,这里写一个代码来验证一下:
c
#include<string.h>
int main()
{
char* arr1 = "as";
char* arr2 = "asdfgfff";
if (strlen(arr1) - strlen(arr2) > 0)
{
printf("arr1>arr2");
}
else
{
printf("arr1<arr2");
}
return 0;
}

正常来说,strlen(arr1) < strlen(arr2),所以strlen(arr1) - strlen(arr2)<0是没错的。但是要注意,strlen的返回值是size_t类型的,是无符号整数,所以它们两个无符号整数相减不会是负数,那就只能是正数了
3.3 strlen的模拟实现
方法一:
c
size_t my_strlen(const char* str)
{
char* start = str;
while (*str != '\0')
{
str++;
}
return str - start;
}
两个指针相减,得到的值是两个指针之间的元素个数,比如p1+5(个元素)=p2,那么p2-p1=5个元素
⽅式2:
c
int my_strlen(const char* str)
{
assert(str);
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str + 1);
}
这个方法使用了递归的思想,将大问题拆成结构一样但是规模更小的问题,然后一直拆分,直到不能再拆分,回归到基本条件(*str == '\0')
4. strcpy
函数原型:
char* strcpy(char * destination, const char * source );
功能:将源字符串( const char * source 指向的字符串)拷贝到目标字符串(char * destination指向的内存空间)里,会连同把源字符串的\0拷贝进去
char * destination:指向目标字符串的的指针,是存放要复制的字符串,内存空间必须足够(包括\0)- 返回值:目标字符串的起始地址
4.1代码演示
c
int main()
{//将arr2数组的内容复制粘贴到arr1数组中,并且返回arr1的起始地址
char arr1[10] = { 0 };
char arr2[] = "hello";
char*p=strcpy(arr1, arr2);
printf("%s\n", arr1);
printf("%s\n", p);
return 0;
}
- 源字符串在复制的时候不是说会一直复制下去的,遇到
\0就会停止,所以源字符串要的末尾要包括\0 - 复制会连同
\0一起复制到目标字符串里
- 目标空间必须足够大,用来确保存放源字符串的数据
- 目标空间必须可以修改(即不能用const来修饰)
4.2 模拟实现
c
char* my_strcpy(char*destination,const char*source)
{
//断言
assert(source);
assert(destination );
char* ret = destination;
while (*source!='\0')
{
*destination=*source;
destination++;
source++;
}
//连带\0复制
*destination = *source;
return ret;
}
这个函数实现了字符串的拷贝,是通过将字符串的字符一个一个搬运到对应的目标字符串,然后最后再搬运\0来做到复制字符串。
特别注意:如果字符串是以char* a=" ***** "来定义,指针指向的字符串常量通常存储在只读数据段里,只能看,不能被修改,既然不能被修改那么目标空间就不能被访问,拷贝也就无从说起。
5. strcat
原型:
char * strcat ( char * destination, const char * source );
功能:字符串追加,把源字符串追加到目标字符串的末尾
char * destination:指向目标空间,容纳追加的内容拼接在末尾const char * source:指向源字符串,被追加的内容- 返回值:目标空间的起始地址
5.1 代码演⽰
c
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
- 源字符串的末尾必须要有
\0 - 目标字符串的末尾也要有
\0,追加的字符串就是从\0开始拷贝进去。 - ⽬标空间必须有⾜够的⼤,能容纳下源字符串的内容。
- 目标空间要可修改
- 追加完毕之后会自动补上
\0
5.2 模拟实现
c
char* my_strcat(char* destination, const char* source)
{
char* ret = destination;
assert(destination && source);
//找到目标字符串\0的位置
while (*destination!='\0')
{
destination++;
}
while (*destination++ = *source++)
{
;
}
return ret;
}
while (*destination++ = *source++)的执行逻辑是先赋值,再判断赋值的内容是不是\0,也就是*source有没有遇到\0如果是就退出循环。
6. strcmp
函数原型:
int strcmp ( const char * str1, const char * str2 );
功能:用于比较str1和str2指向的字符串。比较过程从两个字符串的首字符开始,依次比较对应字符的ASCII码值。若当前字符相同,则继续比较下一对字符;当遇到不同字符或任一字符串结束时,比较终止。功能:用于比较str1和str2指向的字符串。比较过程从两个字符串的首字符开始,依次比较对应字符的ASCII码值。若当前字符相同,则继续比较下一对字符;当遇到不同字符或任一字符串结束时,比较终止。ASCII码大的字符所对应的字符串就大。
str1:指针,指向要⽐较的第⼀个字符串str2:指针,指向要⽐较的第⼆个字符串
返回值:- 第⼀个字符串⼤于第⼆个字符串,则返回⼤于0的数字
- 第⼀个字符串等于第⼆个字符串,则返回0
- 第⼀个字符串⼩于第⼆个字符串,则返回⼩于0的数字
6.1 代码演⽰:
c
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;
}
注意,不是分别比较两个字符串总共的ascii码值的大小,而是逐个字符比较,直到遇到不同的字符或者\0停止比较。
6.2 模拟实现:
c
int my_strcmp(const char* arr1, const char* arr2)
{
assert(arr1 && arr2);
while (*arr1 == *arr2)
{
//相等的字符中,如果是\0表明两个字符都遍历完了都没有发现不同的字符,
if (*arr1 == '\0')
{
return 0;
}
arr1++;
arr2++;
}
return *arr1 - *arr2;
}
从起始地址开是判断两个字符是否相等,如果相等且不是\0就跳过比较下一个字符,如果不一样返回值return *arr1 - *arr2;,返回值大于零表示第一个字符串对应的字符大于第二个字符串,小于零则相反。如果两个字符串相等且都是\0表明两个字符串遍历完了都没有找到不同的字符,说明两个字符串相同,返回0。
7. strncpy
原型:
char * strncpy ( char * destination, const char * source, size_t num );
与strcpy相比,多了个参数size_t num,控制源字符串能拷贝的字符个数。
7.1 代码演⽰
c
int main()
{
char arr1[20] = "--------------";
char arr2[] = "abcdefghi";
char* str = strncpy(arr1, arr2, 10);
printf("%s\n", arr1);
printf("%s\n", str);
return 0;
}
strncpy在拷贝的时候,不一定会在末尾补上\0,这取决于参数size_t num与源字符串的字符个数- 如果
source<num,复制完了source之后,剩下的位置全部填充\0
- 如果
source>=num,只复制前num个字符,不补\0
所以使用strncpy的时候,可能需要我们自己手动添加\0来保证字符串是我们想要的字符串。与strcpy相比,strncpy可以控制最大复制字符的个数,保证了在复制到目标字符串的时候不会发生目标空间不过而越界的问题,这需要我们多加思考。
7.2模拟实现
c
char* my_strncpy(char* destination, const char* source, size_t num)
{
size_t len_sou = strlen(source);
char* ret = destination;
int i = 0;
for (i = 0; i < num; i++)
{
//判断指针的偏移有没有越界源字符串所占的内存空间
if(i<len_sou)
{
*(destination + i) = *(source + i);
}
//越界的话越界的部分就用\0来填补
else
{
*(destination + i) = '\0';
}
}
return ret;
}
这里重要的是判断指针的偏移量有没有越界源字符串所占的内存空间,如果指针的偏移量越界的话其实就说明了指定的最大拷贝字符数(num)要大于源字符串的字符数(strlen(source)),越界的部分用\0填补。如果没有越界就复制前num个字符,不补\0。
8. strncat
原型:
char * strncat ( char * destination, const char * source, size_t num );
与strcat相比,多了个参数num这个参数用来控制可追加的最大字符个数,返回值也是目标字符串的起始地址。
8.1 代码演⽰
c
int main()
{
char arr1[20] = "hello \0------------";
char arr2[] = "world";
char* str = strncat(arr1, arr2, 2);
printf("%s\n", arr1);
printf("%s\n", str);
return 0;
}
strncat追加完了之后一定会在末尾补上\0- 如果
source<num,只拼接\0之前的字符,末尾会补上\0 - 如果
source>=num,只拼接前num个字符,末尾强制补\0 - 更加安全,更加灵活
8.2 模拟实现
c
char* my_strncat(char* dest, const char* sou, size_t n)
{
char* ret = dest;
size_t len_sou = strlen(sou);
assert(dest && sou);
while (*dest != '\0')
{
dest++;
}
int i = 0;
for (i = 0; i < n; i++)
{
if (i < len_sou)
{
*(dest + i) = *(sou + i);
}
//n>=len_sou的时候,指定的最大追加字符数多余源字符串的字符个数
//将所有源字符串的字符全部追加完了之后,强制在其后面补加一个\0
else
{
*(dest+i) = '\0';
break;//补加之后之间退出循环,越界源字符串的部分就不再补加了
}
}
//i=n并且跳出了循环的时候,说明源字符串的前n个字符已经追加完了,强制在其后面补加\0
if (i = n)
{
*(dest+i) = '\0';
}
return ret;
}

其实从模拟的效果也知道在strncat里,不管追加多少个字符,最后都是要在末尾补上\0的,所以在设计目标内存空间的时候要预留一个字节的空间给\0。
- dest空间大小>=
strlen(dest)+n+1 - 这里
strlen(dest)表示目标空间内存原有的数据,n表示要追加的最多字符个数,+1表示为\0预留的空间
9. strncmp
原型:
int strncmp ( const char * str1, const char * str2, size_t num );
与strcmp相比,strncmp可以控制要比较的字符串的字符个数,更加灵活安全。
返回值规定:
- 第⼀个字符串⼤于第⼆个字符串,则返回⼤于0的数字
- 第⼀个字符串等于第⼆个字符串,则返回0
- 第⼀个字符串⼩于第⼆个字符串,则返回⼩于0的数字
和strcmp的返回值是一样的,只不过限定了比较的字符个数,在限定的范围里来比较字符串的大小。
9.1 代码演⽰
c
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcqw";
int ret1 = strncmp(arr1, arr2, 3);
printf("%d\n", ret1);//0,逐个字符比价前3个字符
int ret2 = strncmp(arr1, arr2, 4);
printf("%d\n", ret2);//-1,逐个字符比较前4个字符
return 0;
}
9.2模拟实现
c
int my_strncmp(const char* arr1, const char* arr2, size_t n)
{
assert(arr1 && arr2);
int i = 0;
for (i = 0; i < n; i++)
{
if (*(arr1 + i) != *(arr2 + i))
{
return *(arr1 + i) - *(arr2 + i);//通过两个字符相减知道其ascci的大小
}
//防止越界访问,如果n>max{strlen(arr1),strlen(arr2)},指针偏移量可能会越界
//这是字符比较停止的必要条件
if (*(arr1 + i) == '\0' && *(arr2 + i) == '\0')
{
return 0;
}
}
return 0;
}
这里要知道字符比较停止的条件
- 两个字符串都已经全部遍历完都没有发现不同的字符(
*(arr1 + i) == '\0' && *(arr2 + i) == '\0')。这种情况一般是n>max{strlen(arr1),strlen(arr2)},即限定的最多比较字符个数大于两个字符串的字符个数 - 发现了不同的两个字符
- 两个字符串在限定的范围里各个对应的字符都相等(比如在限定只能比较3个字符中,两个字符串
"abcf"和"abcfg")
10. strstr
原型:
char * strstr ( const char * str1, const char * str2);
功能:
这个函数是字符串查找函数,用来在主字符串中查找首次出现的子字符串,返回在主字符串中首次出现的子字符串的地址,在主字符串找不到子字符串的话就返回空指针。
str1:指针,指向了被查找的字符串str2:指针,指向了要匹配查找的字符串
返回值:
- 如果str1指向的字符串中存在str2指向的字符串,那么返回首次出现位置的指针
- 如果str1指向的字符串中不存在str2指向的字符串,那么返回NULL
10.1 代码演示
c
int main()
{
char str[50] = "This is a simple string";
char* pch;
pch = strstr(str, "simple");
if (pch != NULL)
{
strncpy(pch, "sample", 6);
printf("%s", str);
}
else
printf(" 查找的字符串不存在 \n");
return 0;
}
这个代码找到"str"中出现的首个"simple"字符串,返回首次出现的地址的指针。用这个指针来修改句子里的simple为sample。
10.2模拟实现
c
char* my_strstr(const char* arr1, const char* arr2)
{
//先解决特殊情况,即字符是空字符
if (!*arr2)
{
return (char*)arr1;
}
//用偏移量来控制指针
int i = 0;
int j = 0;
size_t arr1_len = strlen(arr1);
size_t arr2_len = strlen(arr2);
while (arr1_len-- >= arr2_len)
{
//每遍历一次字符串,重置偏移量
i = 0;
j = 0;
//逐个字符比较,不相等或者子字符串结束就跳出循环
while ( *(arr2+j) && *(arr1 + i) == *(arr2 + j))
{
i++;
j++;
}
//来到子字符串的末尾,就退出函数
if (*(arr2 + j) == '\0')
{
return arr1;
}
//匹配失败,指向主串的指针前进一个字符
else
{
arr1++;
}
}
return NULL;
}
strstr函数的实现有多种,可以暴⼒查找,也有⼀种⾼效⼀些的算法:KMP。
11. strtok 函数的使用
原型:
char *strtok(char *str, const char *delim);
该函数是字符串分割函数,会把字符串按照指定的分割符分割成若干段小的字符串(token)
功能:
- 分割字符串:根据
delim参数指定的分隔符,把字符串切分成若干段小的字符串(token) - 修改原始字符串:
strtok会将原字符串的指定的分隔符用\0替换,因此会修改原字符串。使用前要先备份原始字符串。
参数:
str:首次调用传递待分割的函数,后续再次使用的时候传入NULL表示分割同一个字符串。delim:包含所有可能分隔符的字符串(每个字符均视为独⽴的分隔符)。
返回值:
- 成功时返回指向当前⼦字符串的指针。
- 没有更多⼦字符串时返回
NULL
使用步骤:
- 首次调用的时候传入待分割的字符串(可修改,不能传入字符串常量),返回首次切割完之后得到的的字符串的指针
- 再次调用的时候传入
NULL和相同的分隔符,表示对同一块字符串再次切割,返回再次切割完之后得到的字符串的指针 - 切割结束时返回
NULL表示已经切割完毕
11.1代码演示:
c
int main()
{
char arr[] = "12a34.6s789..22qw*5w5";
printf("原字符串:arr=%s\n", arr);
//首次切割
char* p1 = strtok(arr, ".*");
printf("首次切割:%s\n", p1);
//第二次分割
p1 = strtok(NULL, ".*");
printf("第二次切割:%s\n", p1);
//第三次
p1= strtok(NULL, ".*");
printf("第三次切割:%s\n", p1);
//第四次
p1 = strtok(NULL, ".*");
printf("第四次切割:%s\n", p1);
//切割结束标志
p1 = strtok(NULL, ".*");
printf("切割完毕:%s\n", p1);
//对原字符串的破环性操作
printf("破环了原字符串:arr=%s\n", arr);
return 0;
}
结果:

从结果中我们得知:
- 函数首次调用传入待分割的可修改字符串的指针,返回切割了的字符串的指针,然后下次切割就只需要传入
NULL - 当切割完毕的时候返回空指针
strtok的使用会破坏原字符串,在分割符位置处替换为\0- 两个分隔符连续会视作一个分隔符
- 从中我们发现函数的下一次调用是从上一次函数调用完切到的位置处开始切割,上一次函数切完之后会在分割符的位置处替换成
\0。说明该函数内部有静态缓冲区(static),会记录上一次切到的位置处
这样写代码太冗余了,可以修改代码:
c
int main()
{
char arr[] = "12a34.6s789..22qw*5w5";
for (char* r = strtok(arr, ".*"); r != NULL; r = strtok(NULL, ".*"))
{
printf("%s\n", r);
}
return 0;
}
这段代码如果返回值不是空指针就继续循环,并将参数arr改为NULL,简化了代码
11.2注意事项
- 破坏性操作:
strtok会直接修改原始字符串,将其中的分隔符替换为'\0'。如果需要保留原字符串,应先拷贝⼀份。 - 连续分隔符:多个连续的分隔符会被视为单个分隔符,不会返回空字符串。
- 空指针处理:如果输⼊的
str为NULL且没有前序调⽤,行为未定义。
12. 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
返回值:
- 函数返回通过错误码得到的错误信息字符串的⾸字符的地址。
程序在使用库函数的时候总会发生错误,每一种错误都对应着一个错误码,如下所示:

举例:
c
#include <errno.h>
int main()
{
//指向文件的指针
FILE* pFile = NULL;
//fopen函数以读的形式打开文件,如果文件不存在,则打开失败。
pFile = fopen("unexist.text", "r");
if (pFile == NULL)
{
}
printf("错误信息是:%s\n", strerror(errno));
return 1;//错误返回
return 0;
}


12.2 perror
perror函数就相当于直接打印出显示错误信息,就可以不用printf函数了,也不需要错误码,perror函数打印完参数部分的字符串后,再打印⼀个冒号和⼀个空格,再打印错误。
