在C语言编程中,字符和字符串是最基础也最常用的数据类型,从文本解析、数据处理、输入输出等场景几乎都离不开对字符/字符串的操作。为了简化开发,C语言标准库封装了一系列专用处理函数,这些函数分布在ctype.h(字符操作)、string.h(字符串操作)、errno.h(错误处理)等头文件中。本文将系统梳理这些核心函数,从基本使用、注意事项到手动模拟实现,帮你彻底掌握这些高频函数。

字符操作函数主要集中在ctype.h头文件中,分为“分类”和“转换”两类,是处理单个字符的基础。
字符分类函数用于判断字符的类型(如是否为小写字母、数字、字母数字等),所有分类函数的返回规则一致:符合条件返回非0整数,不符合返回0。核心函数如下(附专属示例):
函数 | 判断条件 |
|---|---|
islower | 小写字母(a~z) |
isalnum | 字母(az/AZ)或数字(0~9) |
isgraph | 任何图形字符(非控制字符、非空格) |
功能:筛选字符串中的所有小写字母并打印
#include <stdio.h>
#include <ctype.h>
int main()
{
char str[] = "Hello123! 世界";
int i = 0;
printf("字符串中的小写字母:");
while (str[i] != '\0')
{
if (islower(str[i])) // 仅匹配小写字母a~z
{
putchar(str[i]);
}
i++;
}
printf("\n");
return 0;
}
// 输出:字符串中的小写字母:ello字符串中的小写字母:ello
功能:统计字符串中字母和数字的总个数
#include <stdio.h>
#include <ctype.h>
int main()
{
char str[] = "C语言2025!@# 编程";
int count = 0, i = 0;
while (str[i] != '\0')
{
if (isalnum(str[i])) // 匹配字母(大小写)或数字
{
count++;
}
i++;
}
printf("字母和数字的总个数:%d\n", count);
return 0;
}
// 输出:字母和数字的总个数:5(仅识别ASCII码中的C、2、0、2、5,中文非ASCII不识别)字母和数字的总个数:5
功能:筛选字符串中的图形字符(排除空格、控制字符)
#include <stdio.h>
#include <ctype.h>
int main()
{
char str[] = "Test \t String123! \n"; // 包含空格、制表符\t、换行符\n
int i = 0;
printf("字符串中的图形字符:");
while (str[i] != '\0')
{
if (isgraph(str[i])) // 匹配可显示的图形字符(非空格、非控制字符)
{
putchar(str[i]);
}
i++;
}
printf("\n");
return 0;
}
// 输出:字符串中的图形字符:TestString123!字符串中的图形字符:TestString123!
C语言提供两个专用转换函数,替代手动修改ASCII码的方式,更易读、不易错:
tolower(int c):大写字母转小写toupper(int c):小写字母转大写C以下是两种转换场景的完整示例:
功能:将字符串中所有小写字母转为大写,其他字符保持不变
#include <stdio.h>
#include <ctype.h>
int main()
{
char str[] = "Hello World! 2025 编程";
int i = 0;
printf("转换后的字符串:");
while (str[i] != '\0')
{
// 若为小写字母则转大写,否则直接输出原字符
putchar(islower(str[i]) ? toupper(str[i]) : str[i]);
i++;
}
printf("\n");
return 0;
}
// 输出:转换后的字符串:HELLO WORLD! 2025 编程转换后的字符串:HELLO WORLD! 2025 编程
功能:将字符串中所有大写字母转为小写,其他字符保持不变
#include <stdio.h>
#include <ctype.h>
int main()
{
char str[] = "C LANGUAGE IS FUN! 987";
int i = 0;
printf("转换后的字符串:");
while (str[i] != '\0')
{
// 若为大写字母则转小写,否则直接输出原字符
putchar(isupper(str[i]) ? tolower(str[i]) : str[i]);
i++;
}
printf("\n");
return 0;
}
// 输出:转换后的字符串:c language is fun! 987转换后的字符串:c language is fun! 987
strlen是最基础的字符串函数,用于获取字符串长度,核心规则和实现逻辑是理解字符串的关键。
size_t strlen(const char *str);
'\0'为结束标志,返回'\0'前的字符个数(不包含'\0')。'\0'结束的字符串 。size_t是无符号整数(易错点:减法可能出现“负数变正数”)。#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "Hello C!"; // 包含'\0'的正常字符串
char str2[] = {'H', 'i'}; // 无'\0'的字符串(危险)
char str3[] = ""; // 空字符串(仅含'\0')
printf("str1长度:%zu\n", strlen(str1)); // 输出:7(H e l l o 空格 C)
printf("str2长度:%zu\n", strlen(str2)); // 输出:随机值(直到内存中遇到'\0')
printf("str3长度:%zu\n", strlen(str3)); // 输出:0
return 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掌握模拟实现能理解底层逻辑,推荐以下3种方式:
#include <assert.h>
int my_strlen(const char *str)
{
assert(str); // 防止空指针
int count = 0;
while(*str) // 直到遇到'\0'停止
{
count++;
str++;
}
return count;
}#include <assert.h>
int my_strlen(const char *str)
{
assert(str);
if(*str == '\0')
return 0;
else
return 1 + my_strlen(str+1); // 递归遍历每个字符
}#include <assert.h>
int my_strlen(char *s)
{
assert(s);
char *p = s;
while(*p != '\0')
p++;
return p - s; // 同类型指针相减=元素个数
}字符串拷贝是高频操作,strcpy是基础版,strncpy是更安全的限定长度版。
char* strcpy(char *destination, const char *source);
'\0'结束;'\0'一并拷贝到目标空间;#include <stdio.h>
#include <string.h>
int main()
{
char dest[20]; // 目标空间足够大
const char src[] = "I love C!";
strcpy(dest, src); // 将src拷贝到dest
printf("拷贝结果:%s\n", dest); // 输出:I love C!
return 0;
}模拟实现strcpy:
#include <assert.h>
char *my_strcpy(char *dest, const char* src)
{
assert(dest != NULL && src != NULL); // 校验空指针
char *ret = dest; // 保存目标起始地址(用于返回)
while((*dest++ = *src++)) // 拷贝直到'\0'(赋值表达式为0时停止)
{
;
}
return ret; // 返回目标地址,支持链式调用
}char *strncpy(char *destination, const char *source, size_t num);
num个字符 。num,拷贝完源字符串后,目标空间剩余位置补'\0'至num个。#include <stdio.h>
#include <string.h>
int main()
{
char dest[10];
const char src1[] = "HelloWorld"; // 长度10(含'\0')
const char src2[] = "Hi"; // 长度2(含'\0')
// 拷贝src1的前5个字符到dest
strncpy(dest, src1, 5);
dest[5] = '\0'; // 手动补'\0'(避免源字符串无'\0'时乱码)
printf("dest1:%s\n", dest); // 输出:Hello
// 拷贝src2的前5个字符(src2长度不足,剩余位置补'\0')
strncpy(dest, src2, 5);
printf("dest2:%s\n", dest); // 输出:Hi(后续隐含'\0')
return 0;
}strcat用于将源字符串追加到目标字符串末尾,strncat是限定长度的安全版。
char *strcat(char *destination, const char *source);
'\0'结束。'\0',导致死循环)。#include <stdio.h>
#include <string.h>
int main()
{
char dest[30] = "Hello, "; // 目标字符串需提前初始化(含'\0')
const char src[] = "C programmer!";
strcat(dest, src); // 将src追加到dest末尾
printf("追加结果:%s\n", dest); // 输出:Hello, C programmer!
return 0;
}模拟实现strcat:
#include <assert.h>
char *my_strcat(char *dest, const char* src)
{
assert(dest != NULL && src != NULL);
char *ret = dest;
// 1. 找到目标字符串的'\0'位置
while(*dest)
{
dest++;
}
// 2. 拷贝源字符串到目标末尾(同strcpy逻辑)
while((*dest++ = *src++))
{
;
}
return ret;
}char *strncat(char *destination, const char *source, size_t num);num个字符,且会在追加完成后自动补'\0'(即使源字符串长度不足num)。#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;
}字符串比较不能直接用==(比较的是地址),需用strcmp/strncmp比较字符ASCII码。
int strcmp(const char *str1, const char *str2);
'\0'。#include <stdio.h>
#include <string.h>
int main()
{
const char str1[] = "apple";
const char str2[] = "app";
const char str3[] = "banana";
const char str4[] = "apple";
printf("strcmp(str1, str2):%d\n", strcmp(str1, str2)); // 输出:>0(str1长于str2且前3字符相等)
printf("strcmp(str1, str3):%d\n", strcmp(str1, str3)); // 输出:<0('a'ASCII < 'b')
printf("strcmp(str1, str4):%d\n", strcmp(str1, str4)); // 输出:0(完全相等)
return 0;
}模拟实现strcmp:
#include <assert.h>
int my_strcmp(const char *str1, const char *str2)
{
assert(str1 != NULL && str2 != NULL);
while(*str1 == *str2)
{
if(*str1 == '\0') // 全部字符相等且到末尾
return 0;
str1++;
str2++;
}
return *str1 - *str2; // 返回差值(正负对应大小)
}int strncmp(const char *str1, const char *str2, size_t num);
num个字符,若提前出现不同字符则停止,否则返回0。#include <stdio.h>
#include <string.h>
int main()
{
const char str1[] = "HelloWorld";
const char str2[] = "HelloCoder";
const char str3[] = "HiThere";
// 比较前5个字符("Hello" vs "Hello")
printf("strncmp(str1, str2, 5):%d\n", strncmp(str1, str2, 5)); // 输出:0
// 比较前2个字符("He" vs "Hi")
printf("strncmp(str1, str3, 2):%d\n", strncmp(str1, str3, 2)); // 输出:<0('e' < 'i')
return 0;
}strstr用于查找子串在主串中首次出现的位置,是文本解析的核心函数。
char *strstr(const char *str1, const char *str2);
'\0')。#include <stdio.h>
#include <string.h>
int main()
{
char str[] ="This is a simple string";
char *pch;
pch = strstr(str,"simple"); // 找到"simple"的起始位置
if (pch != NULL)
{
strncpy(pch,"sample",6); // 替换为"sample"
printf("%s\n", str); // 输出:This is a sample string
}
else
{
printf("未找到子串\n");
}
return 0;
}#include <assert.h>
char *my_strstr(const char *str1, const char *str2)
{
assert(str1 && str2);
char *cp = (char *)str1;
char *s1, *s2;
if (!*str2) // 若子串为空,直接返回主串起始地址
return (char *)str1;
while (*cp)
{
s1 = cp;
s2 = (char *)str2;
// 逐字符匹配,直到不相等或子串结束
while (*s1 && *s2 && !(*s1 - *s2))
{
s1++;
s2++;
}
if (!*s2) // 子串匹配完成
return cp;
cp++; // 主串指针后移,继续匹配
}
return NULL; // 未找到子串
}strtok用于按指定分隔符分割字符串,是日志解析、IP处理等场景的常用工具。
char *strtok(char *str, const char *sep);
sep是分隔符集合(如".,-"表示分隔符为.、,、-)。str传需分割的字符串,后续调用传NULL(函数会保存上次分割的位置)。'\0'),建议使用临时拷贝的字符串。#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "192.168.6.111";
char* sep = ".";
char* str = NULL;
// 循环分割:首次传arr,后续传NULL
for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
{
printf("%s\n", str); // 依次输出:192、168、6、111
}
return 0;
}#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "name:zhangsan,age:20;city:beijing";
char* sep = ":,;"; // 分隔符集合:冒号、逗号、分号
char* str = NULL;
for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
{
printf("%s\n", str); // 输出:name、zhangsan、age、20、city、beijing
}
return 0;
}编程中经常需要处理错误(如文件打开失败),strerror和perror用于将错误码转为可读的错误信息。
char *strerror(int errnum);
errno(全局变量,头文件errno.h)保存错误码,strerror将错误码转为对应的描述字符串。#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
FILE *pFile;
pFile = fopen("unexist.ent","r"); // 打开不存在的文件
if (pFile == NULL)
printf("Error: %s\n", strerror(errno)); // 输出:Error: No such file or directory
return 0;
}perror是strerror的简化版,直接打印自定义提示+错误信息:
#include <stdio.h>
#include <errno.h>
int main()
{
FILE *pFile;
pFile = fopen("unexist.ent","r");
if (pFile == NULL)
perror("Error opening file"); // 输出:Error opening file: No such file or directory
return 0;
}#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
// 打印0~5号错误码对应的信息
for (int i = 0; i <= 5; i++)
{
printf("错误码%d:%s\n", i, strerror(i));
}
return 0;
}
// 输出(Windows环境):
// 错误码0:No error
// 错误码1:Operation not permitted
// 错误码2:No such file or directory
// 错误码3:No such process
// 错误码4:Interrupted function call
// 错误码5:Input/output errorC语言字符/字符串函数是编程基础中的基础,这些函数看似简单,但细节(如无符号返回值、原字符串修改、空指针校验)容易出错,建议多写示例代码,结合实际场景(如文本解析、文件处理)练习,才能真正做到熟练运用。 至此,我们已梳理完“字符函数和字符串函数”的全部内容了。最后我们在文末来进行一个投个票,告诉我你对哪部分内容最感兴趣、收获最大,也欢迎在评论区聊聊你的学习感受。