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

发布于:2025-08-03 ⋅ 阅读:(10) ⋅ 点赞:(0)

在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";  // 长度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);

关键特性

  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 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);

关键特性

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


网站公告

今日签到

点亮在社区的每一天
去签到