C语言进阶知识--字符和字符串函数
目录:
- 字符分类函数
- 字符转换函数
- strlen的使用和模拟实现
- strcpy的使用和模拟实现
- strcat的使用和模拟实现
- strcmp的使用和模拟实现
- strncpy函数的使用
- strncat函数的使用
- strncmp函数的使用
- strstr的使用和模拟实现
- strtok函数的使用
- strerror函数的使用
正文开始
在编程的过程中,我们经常要处理字符和字符串,为了方便操作字符和字符串,C语言标准库中提供了一系列库函数,接下来我们就学习一下这些函数。
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 | 字母(包括大写和小写字母) |
isalnum | 字母或数字 |
isgraph | 任何图形字符(不包括空白字符) |
isprint | 任何可打印字符,包括图形字符和空白字符 |
这些函数的使用方法非常类似,我们以 islower
为例讲解,其他函数用法可类比:
int islower ( int c );
islower
用于判断参数 c
是否为小写字母。通过返回值判断结果:若为小写字母,返回非0的整数;若不是小写字母,返回0。
练习:将字符串中的小写字母转大写,其他字符不变
#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; // 小写字母ASCII码值比大写大32,减去32转为大写putchar(c);i++;}return 0;
}
2. 字符转换函数
C语言提供了2个常用的字符转换函数:
int tolower ( int c ); // 将参数传进去的大写字母转小写
int toupper ( int c ); // 将参数传进去的小写字母转大写
上面的“小写转大写”练习,也可以用 toupper
函数简化实现,无需手动计算ASCII码差值:
#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 = toupper(c); // 直接调用函数转换为大写putchar(c);i++;}return 0;
}
3. strlen 的使用和模拟实现
函数原型
size_t strlen ( const char * str );
功能说明
- 字符串以
'\0'
作为结束标志,strlen
函数返回的是字符串中'\0'
前面出现的字符个数(不包含'\0'
)。 - 注意事项:
- 参数指向的字符串必须以
'\0'
结束,否则会导致计算结果错误(读取到随机内存内容)。 - 函数的返回值类型为
size_t
(无符号整数),使用时需注意无符号数的运算特性(如无符号数相减不会得到负数)。
- 参数指向的字符串必须以
- 使用需包含头文件
<string.h>
。
示例:strlen 使用注意(无符号返回值问题)
#include <stdio.h>
#include <string.h>
int main()
{const char* str1 = "abcdef"; // 长度为6const char* str2 = "bbb"; // 长度为3// 注意:strlen返回无符号数,3-6的结果是无符号数(4294967293),因此判断为真if(strlen(str2) - strlen(str1) > 0)printf("str2>str1\n");elseprintf("str1>str2\n");return 0;
}
运行结果:str2>str1
(因无符号数运算特性导致的“逻辑错误”,实际应比较 strlen(str1) > strlen(str2)
)。
strlen 的模拟实现
方式1:计数器方式
#include <assert.h> // 用于断言
int my_strlen(const char * str)
{int count = 0;assert(str); // 断言str不为NULL,避免空指针访问while(*str) // 当*str不为'\0'时循环{count++; // 计数器累加str++; // 指针向后移动}return count;
}
方式2:递归方式(不创建临时变量计数器)
#include <assert.h>
int my_strlen(const char * str)
{assert(str);if(*str == '\0')return 0; // 递归终止条件:遇到'\0'返回0elsereturn 1 + my_strlen(str + 1); // 递归调用,累加字符数
}
方式3:指针-指针方式
#include <assert.h>
int my_strlen(const char * str)
{assert(str);const char *p = str; // 保存字符串起始地址while(*p != '\0') // 移动指针到'\0'位置p++;return p - str; // 指针差值 = 字符个数(地址差=元素个数)
}
4. strcpy 的使用和模拟实现
函数原型
char* strcpy(char * destination, const char * source );
功能说明
将 source
指向的字符串(包括终止符 '\0'
)拷贝到 destination
指向的数组中,拷贝到 '\0'
时停止。
- 注意事项:
- 源字符串(
source
)必须以'\0'
结束,否则会导致越界拷贝。 - 会将源字符串的
'\0'
拷贝到目标空间。 - 目标空间(
destination
)必须足够大,能容纳源字符串的全部内容(包括'\0'
),避免内存越界。 - 目标空间必须可修改(不能是常量字符串,如
const char*
指向的内容)。
- 源字符串(
- 使用需包含头文件
<string.h>
。
strcpy 的模拟实现
#include <assert.h>
char* my_strcpy(char *dest, const char* src)
{char *ret = dest; // 保存目标空间起始地址(用于返回)assert(dest != NULL && src != NULL); // 断言指针非空// 循环拷贝:先赋值*dest = *src,再将指针向后移动,直到*src为'\0'(赋值后终止)while((*dest++ = *src++)){; // 空语句,循环体无需额外操作}return ret; // 返回目标字符串起始地址
}
5. strcat 的使用和模拟实现
函数原型
char* strcat(char * destination, const char * source );
功能说明
将 source
指向的字符串追加到 destination
指向的字符串末尾:覆盖 destination
的终止符 '\0'
,并在追加后的新字符串末尾添加 '\0'
。
- 注意事项:
- 源字符串(
source
)必须以'\0'
结束,否则无法确定追加的内容范围。 - 目标字符串(
destination
)必须以'\0'
结束,否则无法确定追加的起始位置。 - 目标空间必须足够大,能容纳原目标字符串 + 源字符串的全部内容(包括
'\0'
)。 - 目标空间必须可修改。
- 禁止字符串自追加(如
strcat(str, str)
):因追加时会覆盖源字符串的'\0'
,导致无限循环越界。
- 源字符串(
- 使用需包含头文件
<string.h>
。
strcat 的模拟实现
#include <assert.h>
char *my_strcat(char *dest, const char* src)
{char *ret = dest; // 保存目标空间起始地址assert(dest != NULL && src != NULL); // 断言指针非空// 1. 移动dest指针到目标字符串的'\0'位置while(*dest)dest++;// 2. 拷贝src到dest(同strcpy逻辑,包括'\0')while((*dest++ = *src++)){;}return ret; // 返回目标字符串起始地址
}
6. strcmp 的使用和模拟实现
函数原型
int strcmp (const char * str1, const char * str2);
功能说明
比较 str1
和 str2
指向的字符串,从第一个字符开始逐字符比较ASCII码值:
-
若对应字符相等,继续比较下一对字符;
-
若对应字符不等,或遇到
'\0'
,停止比较。 -
标准返回值规定:
- 若
str1 > str2
(第一对不等字符中,str1
字符ASCII码更大),返回大于0的整数; - 若
str1 == str2
(所有字符相等,且同时遇到'\0'
),返回0; - 若
str1 < str2
,返回小于0的整数。
- 若
-
使用需包含头文件
<string.h>
。
strcmp 的模拟实现
#include <assert.h>
int my_strcmp (const char * str1, const char * str2)
{assert(str1 != NULL && str2 != NULL); // 断言指针非空// 逐字符比较,直到字符不等或遇到'\0'while(*str1 == *str2){if(*str1 == '\0')return 0; // 同时遇到'\0',字符串相等str1++;str2++;}// 字符不等时,返回ASCII码差值(直接反映大小关系)return *str1 - *str2;
}
7. strncpy 函数的使用
函数原型
char * strncpy ( char * destination, const char * source, size_t num );
功能说明
将 source
指向的字符串的前 num
个字符拷贝到 destination
指向的空间。
- 特殊情况处理:
- 若源字符串长度 大于等于
num
:只拷贝前num
个字符,不追加'\0'
(需手动处理终止符); - 若源字符串长度 小于
num
:拷贝完源字符串(包括'\0'
)后,在目标空间剩余位置填充0
(即'\0'
),直到总拷贝数为num
。
- 若源字符串长度 大于等于
- 使用需包含头文件
<string.h>
。
8. strncat 函数的使用
函数原型
char * strncat ( char * destination, const char * source, size_t num );
功能说明
将 source
指向的字符串的前 num
个字符追加到 destination
指向的字符串末尾,并在最终结果后强制追加一个 '\0'
。
- 特殊情况处理:
- 若源字符串长度 大于等于
num
:只追加前num
个字符,再追加'\0'
; - 若源字符串长度 小于
num
:只追加源字符串的全部内容(包括'\0'
),无需额外填充。
- 若源字符串长度 大于等于
- 使用需包含头文件
<string.h>
。
示例:strncat 使用
#include <stdio.h>
#include <string.h>
int main ()
{char str1[20];char str2[20];strcpy (str1,"To be "); // str1: "To be \0"strcpy (str2,"or not to be"); // str2: "or not to be\0"strncat (str1, str2, 6); // 追加str2的前6个字符:"or not",再追加'\0'printf("%s\n", str1); // 输出:To be or notreturn 0;
}
9. strncmp 函数的使用
函数原型
int strncmp ( const char * str1, const char * str2, size_t num );
功能说明
比较 str1
和 str2
指向的字符串的前 num
个字符,比较逻辑同 strcmp
,但最多比较 num
个字符后停止。
- 返回值规定:
返回值 含义 < 0 str1
的前num
个字符小于str2
= 0 str1
和str2
的前num
个字符完全相等> 0 str1
的前num
个字符大于str2
- 使用需包含头文件
<string.h>
。
10. strstr 的使用和模拟实现
函数原型
char * strstr ( const char * str1, const char * str2);
功能说明
在 str1
指向的字符串中,查找 str2
指向的字符串(称为“子串”)的第一次出现位置:
- 若找到,返回
str1
中指向子串起始位置的指针; - 若未找到(或
str2
为空字符串),返回NULL
。 - 匹配过程不包含
'\0'
,但遇到'\0'
时停止。 - 使用需包含头文件
<string.h>
。
示例:strstr 使用
#include <stdio.h>
#include <string.h>
int main ()
{char str[] ="This is a simple string";char * pch;pch = strstr (str,"simple"); // 在str中查找"simple",返回其起始指针if(pch != NULL)strncpy (pch,"sample",6); // 将"simple"替换为"sample"printf("%s\n", str); // 输出:This is a sample stringreturn 0;
}
strstr 的模拟实现
#include <assert.h>
char * strstr (const char * str1, const char * str2)
{const char *cp = str1; // 用于遍历str1的指针const char *s1, *s2; // s1遍历str1子串,s2遍历str2if (!*str2) // 若str2为空字符串,直接返回str1return (char *)str1;// 遍历str1,直到cp指向'\0'while (*cp){s1 = cp; // 记录当前str1的起始位置s2 = str2; // 重置str2的起始位置// 逐字符比较s1和s2,直到字符不等或遇到'\0'while (*s1 && *s2 && (*s1 == *s2)){s1++;s2++;}if (!*s2) // 若s2遍历到'\0',说明找到子串return (char *)cp;cp++; // 未找到,cp向后移动一位}return NULL; // 遍历完str1仍未找到,返回NULL
}
11. strtok 函数的使用
函数原型
char * strtok ( char * str, const char * sep);
功能说明
将 str
指向的字符串按 sep
定义的“分隔符集合”拆分(分割为多个“标记”):
sep
:指向一个字符串,包含所有用作分隔符的字符(如".-"
表示分隔符为.
和-
);strtok
会将找到的标记末尾替换为'\0'
,并返回指向该标记的指针;- 关键特性:
- 若
str
不为NULL
:函数找到str
中第一个标记,保存其在字符串中的位置,供下次调用使用; - 若
str
为NULL
:函数从上次保存的位置继续查找下一个标记; - 若无更多标记,返回
NULL
; - 函数会修改原字符串(替换
'\0'
),因此建议传入原字符串的临时拷贝(避免破坏原数据)。
- 若
- 使用需包含头文件
<string.h>
。
示例:strtok 使用(拆分IP地址)
#include <stdio.h>
#include <string.h>
int main()
{char arr[] = "192.168.6.111"; // 原字符串(可修改)char* sep = "."; // 分隔符为 '.'char* str = NULL;// 循环拆分:首次传arr,后续传NULLfor (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep)){printf("%s\n", str); // 依次输出每个标记:192、168、6、111}return 0;
}
12. strerror 函数的使用
函数原型
char* strerror ( int errnum );
功能说明
将错误码 errnum
转换为对应的“错误信息字符串”,并返回该字符串的地址。
- 相关背景:
- C语言中,错误码定义在头文件
<errno.h>
中,程序启动时全局变量errno
初始化为0(表示无错误); - 当标准库函数执行出错时,会将对应的错误码写入
errno
; strerror
的作用是“翻译”错误码(如errno=2
对应 “No such file or directory”),方便开发者理解错误原因。
- C语言中,错误码定义在头文件
- 使用需包含头文件
<string.h>
和<errno.h>
。
示例1:打印0~10对应的错误信息
#include <errno.h>
#include <string.h>
#include <stdio.h>
int main()
{int i = 0;for (i = 0; i <= 10; i++) {printf("%s\n", strerror(i)); // 打印每个错误码对应的信息}return 0;
}
Windows11+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
示例2:结合 errno 处理文件操作错误
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main ()
{FILE * pFile;// 尝试打开不存在的文件(只读模式)pFile = fopen ("unexist.ent","r");if (pFile == NULL) // 打开失败时,fopen返回NULL,errno被设置为对应错误码{// 打印错误信息:strerror(errno) 翻译当前errno对应的错误printf ("Error opening file unexist.ent: %s\n", strerror(errno));}return 0;
}
输出:
Error opening file unexist.ent: No such file or directory
补充:perror 函数
perror
是简化版的“错误打印函数”,功能等同于 printf("xxx: %s\n", strerror(errno))
,直接打印错误信息:
#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
文章参考资料:鹏哥C语言