【C语言】字符函数和字符串函数


前言

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


一、字符操作函数

字符操作函数主要集中在ctype.h头文件中,分为"分类"和"转换"两类,是处理单个字符的基础。

1.1 字符分类函数

字符分类函数用于判断字符的类型(如是否为小写字母、数字、字母数字等),所有分类函数的返回规则一致:符合条件返回非0整数,不符合返回0。核心函数如下(附专属示例):

函数 判断条件
islower 小写字母(a~z)
isalnum 字母(az/AZ)或数字(0~9)
isgraph 任何图形字符(非控制字符、非空格)
示例1:islower(判断小写字母)

功能:筛选字符串中的所有小写字母并打印

c 复制代码
#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

示例2:isalnum(判断字母或数字)

功能:统计字符串中字母和数字的总个数

c 复制代码
#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

示例3:isgraph(判断图形字符)

功能:筛选字符串中的图形字符(排除空格、控制字符)

c 复制代码
#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!

1.2 字符转换函数

C语言提供两个专用转换函数,替代手动修改ASCII码的方式,更易读、不易错:

  • tolower(int c):大写字母转小写
  • toupper(int c):小写字母转大写

C以下是两种转换场景的完整示例:

示例1:toupper(小写字母转大写)

功能:将字符串中所有小写字母转为大写,其他字符保持不变

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 编程

示例2:tolower(大写字母转小写)

功能:将字符串中所有大写字母转为小写,其他字符保持不变

c 复制代码
#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的使用与模拟实现

strlen是最基础的字符串函数,用于获取字符串长度,核心规则和实现逻辑是理解字符串的关键。

2.1 函数基础

  • 原型:
  • size_t strlen(const char *str);

  • 核心规则:
    1. 字符串以'\0'为结束标志,返回'\0'前的字符个数(不包含'\0')。
    2. 参数必须指向以'\0'结束的字符串 。
    3. 返回值size_t是无符号整数(易错点:减法可能出现"负数变正数")。
使用示例:统计字符串长度
c 复制代码
#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;
}

易错示例:无符号返回值的坑

c 复制代码
#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

2.2 模拟实现strlen

掌握模拟实现能理解底层逻辑,推荐以下3种方式:

方式1:计数器法(最直观)
c 复制代码
#include <assert.h>
int my_strlen(const char *str)
{
    assert(str); // 防止空指针
    int count = 0;
    while(*str) // 直到遇到'\0'停止
    {
        count++;
        str++;
    }
    return count;
}
方式2:递归法(无临时变量)
c 复制代码
#include <assert.h>
int my_strlen(const char *str)
{
    assert(str);
    if(*str == '\0')
        return 0;
    else
        return 1 + my_strlen(str+1); // 递归遍历每个字符
}
方式3:指针-指针法(高效)
c 复制代码
#include <assert.h>
int my_strlen(char *s)
{
    assert(s);
    char *p = s;
    while(*p != '\0')
        p++;
    return p - s; // 同类型指针相减=元素个数
}

三、字符串拷贝:strcpy与strncpy

字符串拷贝是高频操作,strcpy是基础版,strncpy是更安全的限定长度版。

3.1 strcpy(无长度限制拷贝)

  • 原型:
  • char* strcpy(char *destination, const char *source);

  • 核心规则:
    1. 源字符串必须以'\0'结束;
    2. 会将源字符串的'\0'一并拷贝到目标空间;
    3. 目标空间需足够大且可修改(不能是常量字符串)。
使用示例:拷贝字符串
c 复制代码
#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

c 复制代码
#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; // 返回目标地址,支持链式调用
}

3.2 strncpy(限定长度拷贝)

  • 原型:
  • char *strncpy(char *destination, const char *source, size_t num);

  • 核心规则:
    1. 仅拷贝源字符串的前num个字符 。
    2. 若源字符串长度 < num,拷贝完源字符串后,目标空间剩余位置补'\0'num个。
使用示例:限定长度拷贝
c 复制代码
#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

strcat用于将源字符串追加到目标字符串末尾,strncat是限定长度的安全版。

4.1 strcat(无长度限制追加)

  • 原型:
  • char *strcat(char *destination, const char *source);

  • 核心规则:
    1. 源、目标字符串都必须以'\0'结束。
    2. 目标空间需足够大,能容纳追加后的全部内容。
    3. 不支持"字符串自追加"(会覆盖自身'\0',导致死循环)。
使用示例:追加字符串
c 复制代码
#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

c 复制代码
#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;
}

4.2 strncat(限定长度追加)

  • 原型:char *strncat(char *destination, const char *source, size_t num);
  • 核心规则:仅追加源字符串的前num个字符,且会在追加完成后自动补'\0'(即使源字符串长度不足num)。
使用示例:限定长度追加
c 复制代码
#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

字符串比较不能直接用==(比较的是地址),需用strcmp/strncmp比较字符ASCII码。

5.1 strcmp(全长度比较)

  • 原型:
  • int strcmp(const char *str1, const char *str2);

  • 标准返回规则:
    • str1 > str2:返回大于0的数。
    • str1 == str2:返回0。
    • str1 < str2:返回小于0的数。
  • 比较逻辑:逐字符对比ASCII码,直到出现不同字符或遇到'\0'
使用示例:比较两个字符串
c 复制代码
#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

c 复制代码
#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; // 返回差值(正负对应大小)
}

5.2 strncmp(限定长度比较)

  • 原型:
  • int strncmp(const char *str1, const char *str2, size_t num);

  • 核心规则:仅比较前num个字符,若提前出现不同字符则停止,否则返回0。
使用示例:限定长度比较字符串
c 复制代码
#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的使用与模拟实现

strstr用于查找子串在主串中首次出现的位置,是文本解析的核心函数。

6.1 函数基础

  • 原型:
  • char *strstr(const char *str1, const char *str2);

  • 规则:返回str2在str1中首次出现的指针;若str2不是str1的子串,返回NULL(匹配不包含'\0')。
使用示例:查找子串位置
c 复制代码
#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;
}

6.2 模拟实现strstr

c 复制代码
#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的使用

strtok用于按指定分隔符分割字符串,是日志解析、IP处理等场景的常用工具。

7.1 函数核心规则

  • 原型:
  • char *strtok(char *str, const char *sep);

  • 关键特性:
    1. sep是分隔符集合(如".,-"表示分隔符为.,-)。
    2. 首次调用时str传需分割的字符串,后续调用传NULL(函数会保存上次分割的位置)。
    3. 会修改原字符串(将分隔符替换为'\0'),建议使用临时拷贝的字符串。
使用示例(分割IP地址)
c 复制代码
#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;
}
扩展示例(分割多分隔符字符串)
c 复制代码
#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

编程中经常需要处理错误(如文件打开失败),strerror和perror用于将错误码转为可读的错误信息。

8.1 strerror(错误码转字符串)

  • 原型:
  • char *strerror(int errnum);

  • 核心:C语言用errno(全局变量,头文件errno.h)保存错误码,strerror将错误码转为对应的描述字符串。
使用示例:打印文件打开失败的错误信息
c 复制代码
#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;
}

8.2 perror(简化版错误打印)

perror是strerror的简化版,直接打印自定义提示+错误信息:

使用示例:简化错误打印
c 复制代码
#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;
}
扩展示例:打印指定错误码信息
c 复制代码
#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 error

C语言字符/字符串函数是编程基础中的基础,这些函数看似简单,但细节(如无符号返回值、原字符串修改、空指针校验)容易出错,建议多写示例代码,结合实际场景(如文本解析、文件处理)练习,才能真正做到熟练运用。

至此,我们已梳理完"字符函数和字符串函数"的全部内容了。最后我们在文末来进行一个投个票,告诉我你对哪部分内容最感兴趣、收获最大,也欢迎在评论区聊聊你的学习感受。

以上就是本期博客的全部内容了,感谢各位的阅读以及关注。如有内容存在疏漏或不足之处,恳请各位技术大佬不吝赐教、多多指正。

相关推荐
翔云 OCR API1 小时前
人脸识别API开发者对接代码示例
开发语言·人工智能·python·计算机视觉·ocr
小白程序员成长日记1 小时前
2025.11.24 力扣每日一题
算法·leetcode·职场和发展
有一个好名字1 小时前
LeetCode跳跃游戏:思路与题解全解析
算法·leetcode·游戏
V***u4531 小时前
MS SQL Server partition by 函数实战二 编排考场人员
java·服务器·开发语言
这是程序猿2 小时前
基于java的ssm框架旅游在线平台
java·开发语言·spring boot·spring·旅游·旅游在线平台
芳草萋萋鹦鹉洲哦2 小时前
【elemen/js】阻塞UI线程导致的开关卡顿如何优化
开发语言·javascript·ui
爱学习的小邓同学2 小时前
C++ --- 多态
开发语言·c++
颜*鸣&空2 小时前
QT实现串口通信+VSPD+串口调试工具
开发语言·qt
AndrewHZ2 小时前
【图像处理基石】如何在图像中提取出基本形状,比如圆形,椭圆,方形等等?
图像处理·python·算法·计算机视觉·cv·形状提取