当前位置: 首页 > news >正文

C语言字符函数和字符串函数全解析:从使用到模拟实现

在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是无符号整数类型

关键特性

  1. 字符串必须以\0结尾,否则结果不确定;
  2. 返回值为size_t(无符号),注意比较时的坑(见示例);
  3. 计算的是\0之前的字符个数。

使用示例

#include <stdio.h>
#include <string.h>int main() 
{const char* str1 = "abcdef";  // 长度6const char* str2 = "bbb";     // 长度3// 易错点:无符号数相减if (strlen(str2) - strlen(str1) > 0)  // 3-6 = -3,无符号下为大正数printf("str2>str1\n");  // 实际会执行这里elseprintf("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;elsereturn 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);

关键特性

  1. 源字符串必须以\0结尾,且会将\0一同拷贝;
  2. 目标空间必须足够大(能容纳源字符串);
  3. 目标空间必须可修改(不能是常量字符串);
  4. 返回目标空间的起始地址。

模拟实现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);

关键特性

  1. 源字符串必须以\0结尾;
  2. 目标字符串必须以\0结尾(否则无法确定追加起始位置);
  3. 目标空间必须足够大(容纳原目标+源字符串);
  4. 目标空间必须可修改;
  5. 字符串不能自己追加自己(会覆盖\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);

比较规则

  1. 从第一个字符开始逐个比较ASCII码值;
  2. 若字符不同,返回差值(str1字符 - str2字符);
  3. 若所有字符相同且都到\0,返回0;
  4. 标准规定:
    • 返回>0:str1 > str2
    • 返回=0:str1 == str2
    • 返回<0:str1 < str2

模拟实现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

strncpystrcpy的安全版本,可指定拷贝的字符数,声明在 <string.h> 中。

函数原型

char* strncpy(char* destination, const char* source, size_t num);

关键特性

  1. 拷贝num个字符从源字符串到目标空间;
  2. 若源字符串长度 < num:拷贝完源字符串后,目标空间剩余部分补\0num个;
  3. 若源字符串长度 ≥ 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

strncatstrcat的安全版本,可指定追加的字符数,声明在 <string.h> 中。

函数原型

char* strncat(char* destination, const char* source, size_t num);

关键特性

  1. 追加源字符串的前num个字符到目标字符串末尾;
  2. 无论是否追加满num个字符,最终都会在目标字符串末尾补一个\0
  3. 若源字符串长度 < 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 notreturn 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);

关键特性

  1. 比较前num个字符,或直到遇到\0
  2. 若前num个字符都相同,返回0;
  3. 若中途字符不同,返回差值(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:子串

关键特性

  1. 若找到子串,返回子串在母串中第一次出现的起始地址;
  2. 若未找到,返回NULL
  3. 若子串为空(*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:分隔符集合

关键特性

  1. sep是分隔符集合(如".,"表示.,都是分隔符);
  2. 第一次调用时,str为待分割字符串,函数返回第一个标记的地址;
  3. 后续调用需将str设为NULL,函数从上次保存的位置继续查找下一个标记;
  4. 函数会修改原字符串(将分隔符替换为\0),建议使用临时拷贝;
  5. 无更多标记时返回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:错误码

关键特性

  1. C语言用全局变量errno(声明在 <errno.h>)记录错误码;
  2. 程序启动时errno为0(无错误);
  3. 库函数出错时会设置errnostrerror将其转为可读字符串。

使用示例

#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语言学习有所帮助!如果有疑问或补充,欢迎在评论区交流~

http://www.lryc.cn/news/607873.html

相关文章:

  • 力扣面试150题--回文数
  • 2411. 按位或最大的最小子数组长度
  • 历史数据分析——中青旅
  • OneCode 3.0智能分页拦截器深度解析:从拦截机制到性能优化
  • 仿muduo库实现高并发服务器
  • flink写paimon表的过程解析
  • 华为核心交换机S7700的内存OID
  • Unity_数据持久化_XML基础
  • MATLAB科研数据可视化技术
  • LPVIMO-SAM:基于多传感器紧耦合的高精度鲁棒SLAM系统
  • Noob靶机
  • VueX进阶Pinia
  • VScode对Ubuntu用root账号进行SSH远程连接开发
  • 技巧|SwanLab记录混淆矩阵攻略
  • 解决忘记修改配置密码而无法连接nacos的问题
  • DockerFile文件执行docker bulid自动构建镜像
  • Android 15 限制APK包手动安装但不限制自升级的实现方案
  • 20250802让飞凌OK3576-C开发板在飞凌的Android14下【rk3576_u选项】适配NXP的WIFIBT模块88W8987A的蓝牙
  • 【Android】通知
  • React ahooks——副作用类hooks之useDebounceFn
  • linux eval命令的使用方法介绍
  • 【vue】创建响应式数据ref和reactive的区别
  • 防火墙配置实验2(DHCP,用户认证,安全策略)
  • C语言---函数的递归与迭代
  • 【DL学习笔记】DL入门指南
  • 《深潜React列表渲染:调和算法与虚拟DOM Diff的优化深解》
  • 2024年网络安全案例
  • rag学习-以项目为基础快速启动掌握rag
  • 建筑施工场景安全帽识别误报率↓79%:陌讯动态融合算法实战解析
  • WordPress AI写作插件开发实战:从GPT集成到企业级部署