在C语言编程中,字符和字符串的处理是非常核心的操作。为了简化这些操作,C语言标准库提供了一系列实用的字符函数和字符串函数。本文将全面讲解这些函数的使用方法、注意事项,并通过模拟实现帮助大家深入理解其底层逻辑。
一、字符分类函数
字符分类函数用于判断字符的类型(如是否为数字、字母、空白字符等),这些函数都声明在 <ctype.h>
头文件中。
常用字符分类函数列表
函数名 | 功能描述 |
---|---|
iscntrl |
判断是否为控制字符(如换行符、制表符) |
isspace |
判断是否为空白字符(空格、换页\f 、换行\n 、回车\r 、制表符\t 、垂直制表符\v ) |
isdigit |
判断是否为十进制数字(0~9) |
isxdigit |
判断是否为十六进制数字(09、af、A~F) |
islower |
判断是否为小写字母(a~z) |
isupper |
判断是否为大写字母(A~Z) |
isalpha |
判断是否为字母(az或AZ) |
isalnum |
判断是否为字母或数字 |
ispunct |
判断是否为标点符号(非字母/数字的可打印图形字符) |
isgraph |
判断是否为图形字符(可打印且非空白) |
isprint |
判断是否为可打印字符(含图形字符和空白) |
函数特点
所有字符分类函数的参数为 int c
(字符的ASCII码),返回值为:
- 非0整数:表示满足条件(真)
- 0:表示不满足条件(假)
示例:使用islower
判断小写字母
下面代码将字符串中的小写字母转为大写,其他字符不变:
#include <stdio.h>
#include <ctype.h> // 包含字符分类函数声明
int main()
{
int i = 0;
char str[] = "Test String.\n";
char c;
while (str[i])
{ // 遍历字符串直到'\0'
c = str[i];
if (islower(c)) // 判断是否为小写字母
c -= 32; // 小写转大写(ASCII码差值)
putchar(c);
i++;
}
return 0;
}
输出结果:TEST STRING.
二、字符转换函数
C语言提供了两个常用的字符转换函数,同样声明在 <ctype.h>
中:
函数名 | 功能描述 |
---|---|
tolower(int c) |
将大写字母转为小写(非大写字母返回原字符) |
toupper(int c) |
将小写字母转为大写(非小写字母返回原字符) |
示例:使用toupper
转换小写字母
#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;
}
输出结果:TEST STRING.
三、字符串长度函数strlen
strlen
用于计算字符串的长度(不含结束符\0
),声明在 <string.h>
中。
函数原型
size_t strlen(const char *str); // size_t是无符号整数类型
关键特性
- 字符串必须以
\0
结尾,否则结果不确定; - 返回值为
size_t
(无符号),注意比较时的坑(见示例); - 计算的是
\0
之前的字符个数。
使用示例
#include <stdio.h>
#include <string.h>
int main()
{
const char* str1 = "abcdef"; // 长度6
const char* str2 = "bbb"; // 长度3
// 易错点:无符号数相减
if (strlen(str2) - strlen(str1) > 0) // 3-6 = -3,无符号下为大正数
printf("str2>str1\n"); // 实际会执行这里
else
printf("str1>str2\n");
return 0;
}
输出结果:str2>str1
(因无符号数特性导致的逻辑错误)
模拟实现strlen
方式1:计数器法
#include <stdio.h>
#include <assert.h> // 用于断言
int my_strlen(const char* str)
{
assert(str != NULL); // 确保指针非空
int count = 0;
while (*str)
{ // 当*str为'\0'时终止
count++;
str++; // 指针后移
}
return count;
}
方式2:递归法(无临时变量)
int my_strlen(const char* str)
{
assert(str);
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str + 1); // 递归累加
}
方式3:指针-指针法
int my_strlen(const char* str)
{
assert(str);
const char* start = str; // 记录起始地址
while (*start != '\0')
start++; // 移动到'\0'位置
return start - str; // 地址差值即长度
}
四、字符串拷贝函数strcpy
strcpy
用于将源字符串拷贝到目标空间,声明在 <string.h>
中。
函数原型
char* strcpy(char* destination, const char* source);
关键特性
- 源字符串必须以
\0
结尾,且会将\0
一同拷贝; - 目标空间必须足够大(能容纳源字符串);
- 目标空间必须可修改(不能是常量字符串);
- 返回目标空间的起始地址。
模拟实现strcpy
#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
assert(dest != NULL && src != NULL); // 检查指针有效性
char* ret = dest; // 保存目标起始地址
// 循环拷贝,直到拷贝'\0'后终止
while ((*dest++ = *src++))
{
; // 空语句,逻辑在表达式中完成
}
return ret; // 返回目标地址
}
五、字符串追加函数strcat
strcat
用于将源字符串追加到目标字符串末尾,声明在 <string.h>
中。
函数原型
char* strcat(char* destination, const char* source);
关键特性
- 源字符串必须以
\0
结尾; - 目标字符串必须以
\0
结尾(否则无法确定追加起始位置); - 目标空间必须足够大(容纳原目标+源字符串);
- 目标空间必须可修改;
- 字符串不能自己追加自己(会覆盖
\0
导致死循环)。
模拟实现strcat
#include <stdio.h>
#include <assert.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
// 1. 移动目标指针到'\0'位置
while (*dest)
{
dest++;
}
// 2. 拷贝源字符串到目标末尾(含'\0')
while ((*dest++ = *src++))
{
;
}
return ret;
}
六、字符串比较函数strcmp
strcmp
用于比较两个字符串的大小,声明在 <string.h>
中。
函数原型
int strcmp(const char* str1, const char* str2);
比较规则
- 从第一个字符开始逐个比较ASCII码值;
- 若字符不同,返回差值(
str1字符 - str2字符
); - 若所有字符相同且都到
\0
,返回0; - 标准规定:
- 返回>0:
str1 > str2
- 返回=0:
str1 == str2
- 返回<0:
str1 < str2
- 返回>0:
模拟实现strcmp
#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
// 比较字符,直到不同或遇到'\0'
while (*str1 == *str2)
{
if (*str1 == '\0') // 都到'\0',相等
return 0;
str1++;
str2++;
}
// 返回差值
return *str1 - *str2;
}
七、受限字符串拷贝函数strncpy
strncpy
是strcpy
的安全版本,可指定拷贝的字符数,声明在 <string.h>
中。
函数原型
char* strncpy(char* destination, const char* source, size_t num);
关键特性
- 拷贝
num
个字符从源字符串到目标空间; - 若源字符串长度 <
num
:拷贝完源字符串后,目标空间剩余部分补\0
至num
个; - 若源字符串长度 ≥
num
:只拷贝前num
个字符(不自动补\0
)。
模拟实现strncpy
#include <stdio.h>
#include <assert.h>
char* my_strncpy(char* dest, const char* src, size_t num)
{
assert(dest && src);
char* ret = dest;
// 拷贝前num个字符或直到源字符串结束
while (num > 0 && *src != '\0')
{
*dest++ = *src++;
num--;
}
// 若还有剩余num,补'\0'
while (num > 0)
{
*dest++ = '\0';
num--;
}
return ret;
}
八、受限字符串追加函数strncat
strncat
是strcat
的安全版本,可指定追加的字符数,声明在 <string.h>
中。
函数原型
char* strncat(char* destination, const char* source, size_t num);
关键特性
- 追加源字符串的前
num
个字符到目标字符串末尾; - 无论是否追加满
num
个字符,最终都会在目标字符串末尾补一个\0
; - 若源字符串长度 <
num
:只追加源字符串到\0
的部分,再补\0
。
使用示例
#include <stdio.h>
#include <string.h>
int main() {
char str1[20];
char str2[20];
strcpy(str1, "To be ");
strcpy(str2, "or not to be");
strncat(str1, str2, 6); // 追加str2的前6个字符"or not"
printf("%s\n", str1); // 输出:To be or not
return 0;
}
模拟实现strncat
#include <stdio.h>
#include <assert.h>
char* my_strncat(char* dest, const char* src, size_t num)
{
assert(dest && src);
char* ret = dest;
// 1. 移动到目标字符串的'\0'位置
while (*dest)
{
dest++;
}
// 2. 追加前num个字符(或源字符串结束)
while (num > 0 && *src != '\0')
{
*dest++ = *src++;
num--;
}
// 3. 必须补'\0'
*dest = '\0';
return ret;
}
九、受限字符串比较函数strncmp
strncmp
用于比较两个字符串的前num
个字符,声明在 <string.h>
中。
函数原型
int strncmp(const char* str1, const char* str2, size_t num);
关键特性
- 比较前
num
个字符,或直到遇到\0
; - 若前
num
个字符都相同,返回0; - 若中途字符不同,返回差值(
str1字符 - str2字符
)。
模拟实现strncmp
#include <stdio.h>
#include <assert.h>
int my_strncmp(const char* str1, const char* str2, size_t num)
{
assert(str1 && str2);
// 比较前num个字符
while (num > 0)
{
if (*str1 != *str2)
{
return *str1 - *str2; // 字符不同,返回差值
}
if (*str1 == '\0')
{
break; // 已到字符串末尾
}
str1++;
str2++;
num--;
}
return 0; // 前num个字符相同
}
十、字符串查找函数strstr
strstr
用于查找子字符串在母字符串中第一次出现的位置,声明在 <string.h>
中。
函数原型
char* strstr(const char* str1, const char* str2); // str1:母串,str2:子串
关键特性
- 若找到子串,返回子串在母串中第一次出现的起始地址;
- 若未找到,返回
NULL
; - 若子串为空(
*str2 == '\0'
),返回母串起始地址。
使用示例
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "This is a simple string";
char* pch = strstr(str, "simple"); // 查找"simple"
if (pch)
{
strncpy(pch, "sample", 6); // 替换为"sample"
printf("%s\n", str); // 输出:This is a sample string
}
return 0;
}
模拟实现strstr
#include <stdio.h>
#include <assert.h>
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
// 子串为空,返回母串
if (*str2 == '\0')
{
return (char*)str1;
}
const char* cp = str1; // 母串起始指针
while (*cp)
{
const char* s1 = cp; // 母串当前比较位置
const char* s2 = str2; // 子串起始位置
// 匹配子串
while (*s1 && *s2 && *s1 == *s2)
{
s1++;
s2++;
}
// 若子串已匹配完,返回当前cp位置
if (*s2 == '\0')
{
return (char*)cp;
}
cp++; // 母串指针后移继续查找
}
return NULL; // 未找到
}
十一、字符串分割函数strtok
strtok
用于按指定分隔符分割字符串,声明在 <string.h>
中。
函数原型
char* strtok(char* str, const char* sep); // sep:分隔符集合
关键特性
sep
是分隔符集合(如".,"
表示.
和,
都是分隔符);- 第一次调用时,
str
为待分割字符串,函数返回第一个标记的地址; - 后续调用需将
str
设为NULL
,函数从上次保存的位置继续查找下一个标记; - 函数会修改原字符串(将分隔符替换为
\0
),建议使用临时拷贝; - 无更多标记时返回
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); // 依次输出每个IP段
}
return 0;
}
输出结果:
192
168
6
111
十二、错误信息函数strerror
strerror
用于获取错误码对应的描述信息,声明在 <string.h>
中。
函数原型
char* strerror(int errnum); // errnum:错误码
关键特性
- C语言用全局变量
errno
(声明在<errno.h>
)记录错误码; - 程序启动时
errno
为0(无错误); - 库函数出错时会设置
errno
,strerror
将其转为可读字符串。
使用示例
#include <stdio.h>
#include <string.h>
#include <errno.h> // 包含errno定义
int main() {
// 打印0~10错误码对应的信息
for (int i = 0; i <= 10; i++) {
printf("%d: %s\n", i, strerror(i));
}
// 文件操作错误示例
FILE* pFile = fopen("unexist.txt", "r"); // 打开不存在的文件
if (pFile == NULL) {
printf("Error: %s\n", strerror(errno)); // 输出错误信息
}
return 0;
}
扩展:perror
函数
perror
可直接打印错误信息,等价于printf("xxx: %s\n", strerror(errno))
:
#include <stdio.h>
#include <errno.h>
int main() {
FILE* pFile = fopen("unexist.txt", "r");
if (pFile == NULL) {
perror("Open file failed"); // 自动拼接错误信息
}
return 0;
}
输出结果:Open file failed: No such file or directory
总结
本文详细讲解了C语言中常用的字符函数和字符串函数,包括功能、使用注意事项及模拟实现。掌握这些函数的核心逻辑不仅能提高编程效率,还能帮助大家规避潜在的bug(如无符号数比较、字符串越界等)。实际开发中,建议优先使用标准库函数,但理解其底层实现能让你在遇到问题时更快定位原因。
希望本文对你的C语言学习有所帮助!如果有疑问或补充,欢迎在评论区交流~