C 语言实例 – 删除字符串中的特殊字符(千字长文)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

截止目前, 星球 内专栏累计输出 90w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 3100+ 小伙伴加入学习 ,欢迎点击围观

前言

在编程实践中,字符串处理是一个高频需求场景。例如,用户输入的文本可能包含特殊字符(如标点符号、控制字符或非法符号),需要在后续处理前将其清除。本文将以 C 语言实例 – 删除字符串中的特殊字符 为主题,通过循序渐进的方式讲解这一问题的解决思路。内容涵盖字符串基础、算法设计、代码实现以及优化方法,帮助读者逐步掌握这一技能。


字符串与特殊字符:基础知识

1. 字符串在 C 语言中的表示

在 C 语言中,字符串本质上是以空字符(\0)结尾的字符数组。例如,字符串 "Hello" 在内存中存储为 {'H', 'e', 'l', 'l', 'o', '\0'}。字符串操作的核心在于对字符逐个遍历和处理。

比喻:可以将字符串想象成一列火车车厢,每个车厢代表一个字符,而火车的末尾有一个特殊的“终点站”标志(即 \0)。遍历字符串的过程,就像沿着轨道检查每个车厢,直到找到终点站为止。

2. 特殊字符的定义

特殊字符通常指不符合业务逻辑需求的字符。例如:

  • 控制字符:如换行符(\n)、回车符(\r)等不可打印字符。
  • 标点符号:如逗号(,)、句号(.)等用户希望过滤的符号。
  • 自定义非法字符:如用户指定的特殊符号(如 @#)等。

案例:假设需要从用户输入的文本中删除所有非字母字符(如数字、空格、标点符号),那么这些字符即为需要处理的“特殊字符”。


方法一:逐字符遍历与重构字符串

1. 基本思路

通过遍历原始字符串的每个字符,判断是否为特殊字符。若不是,则将其拷贝到新字符串中;若是,则跳过。最终生成的字符串即为过滤后的结果。

2. 代码实现

#include <stdio.h>  
#include <string.h>  

// 函数功能:删除字符串中的特殊字符  
// 参数:src 原始字符串,dst 目标字符串,special_chars 需要删除的字符集合  
void remove_special_chars(const char *src, char *dst, const char *special_chars) {  
    int i, j;  
    for (i = 0, j = 0; src[i] != '\0'; i++) {  
        // 检查当前字符是否在特殊字符集合中  
        if (strchr(special_chars, src[i]) == NULL) {  
            dst[j++] = src[i];  
        }  
    }  
    dst[j] = '\0'; // 确保新字符串以空字符结尾  
}  

int main() {  
    const char *input = "Hello!@# World. This is a test!";  
    char output[100];  
    const char *special_chars = "!@#."; // 需要删除的特殊字符  

    remove_special_chars(input, output, special_chars);  
    printf("过滤后的字符串: %s\n", output);  

    return 0;  
}  

3. 代码解析

  • 函数设计remove_special_chars 接受三个参数:原始字符串 src、目标字符串 dst、以及需要删除的特殊字符集合 special_chars
  • 遍历逻辑:通过 for 循环逐个检查 src 中的字符。
  • 判断逻辑:使用 strchr 函数检查当前字符是否存在于 special_chars 中。若不存在,则拷贝到 dst
  • 空字符结尾:循环结束后,手动添加 \0 以确保目标字符串的合法性。

4. 局限性

此方法需要预先定义特殊字符集合,并且要求目标字符串 dst 有足够的空间。若输入字符串过长,可能导致缓冲区溢出。


方法二:双指针优化算法

1. 算法思想

通过“快慢指针”技术优化内存使用,直接在原字符串上进行修改,避免额外空间的开销。

2. 代码实现

#include <stdio.h>  
#include <string.h>  

// 函数功能:原地删除字符串中的特殊字符  
// 参数:str 需要修改的字符串,special_chars 需要删除的字符集合  
void remove_special_chars_inplace(char *str, const char *special_chars) {  
    int slow = 0;  
    for (int fast = 0; str[fast] != '\0'; fast++) {  
        if (strchr(special_chars, str[fast]) == NULL) {  
            str[slow++] = str[fast];  
        }  
    }  
    str[slow] = '\0'; // 截断多余部分  
}  

int main() {  
    char input[] = "Hello!@# World. This is a test!";  
    const char *special_chars = "!@#.";  

    remove_special_chars_inplace(input, special_chars);  
    printf("原地修改后的字符串: %s\n", input);  

    return 0;  
}  

3. 代码解析

  • 双指针策略fast 指针负责遍历原字符串,slow 指针记录有效字符的位置。
  • 内存优化:直接修改原字符串,无需额外空间,但要求原字符串可写(如字符数组而非字符串字面量)。
  • 截断操作:循环结束后,用 \0 截断 slow 之后的内容,确保字符串合法。

4. 使用场景

此方法适合内存有限的场景,但需注意原字符串的可写性。若输入字符串为常量(如 const char *),则需改用方法一。


方法三:动态内存分配(高级技巧)

1. 场景需求

当无法预知目标字符串的长度时,动态内存分配(malloc/realloc)能更灵活地处理大容量数据。

2. 代码实现

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  

// 函数功能:动态生成过滤后的字符串  
// 返回值:指向新字符串的指针(需用户释放)  
char *remove_special_chars_dynamic(const char *src, const char *special_chars) {  
    int len = strlen(src);  
    char *result = (char *)malloc((len + 1) * sizeof(char)); // 初始分配空间  
    if (result == NULL) {  
        perror("内存分配失败");  
        return NULL;  
    }  

    int count = 0;  
    for (int i = 0; src[i] != '\0'; i++) {  
        if (strchr(special_chars, src[i]) == NULL) {  
            result[count++] = src[i];  
        }  
    }  
    result[count] = '\0';  

    // 释放多余空间(可选)  
    result = (char *)realloc(result, (count + 1) * sizeof(char));  
    return result;  
}  

int main() {  
    const char *input = "Hello!@# World. This is a test!";  
    const char *special_chars = "!@#.";  

    char *output = remove_special_chars_dynamic(input, special_chars);  
    if (output != NULL) {  
        printf("动态生成的字符串: %s\n", output);  
        free(output); // 释放内存  
    }  

    return 0;  
}  

3. 关键点解析

  • 内存管理:使用 malloc 分配初始空间,realloc 根据实际需求调整内存大小,避免浪费。
  • 错误处理:检查 malloc 的返回值,确保内存分配成功。
  • 用户责任:返回的指针需要用户手动释放,避免内存泄漏。

4. 适用场景

此方法适合处理非常大的输入字符串,或需要返回新字符串而不修改原数据的情况。


性能与优化分析

1. 时间复杂度

三种方法的时间复杂度均为 O(n),其中 n 是原始字符串的长度。这是因为每个算法都需要遍历一次字符串。

2. 空间复杂度

  • 方法一:使用固定大小的目标数组,空间复杂度为 O(m)m 是目标字符串长度)。
  • 方法二:直接修改原字符串,空间复杂度为 O(1)
  • 方法三:动态分配内存,空间复杂度为 O(m),但更灵活。

3. 选择建议

  • 若原字符串可写且无需保留原始数据,优先选择方法二
  • 若需保留原始数据或处理动态长度,选择方法一或方法三

实际案例:过滤用户输入的非法符号

场景描述

假设需要从用户输入的文本中删除所有非字母字符(如数字、空格、标点符号)。

实现代码

#include <stdio.h>  
#include <ctype.h>  

// 函数功能:保留字母,删除其他字符  
void filter_letters_only(char *str) {  
    int slow = 0;  
    for (int fast = 0; str[fast] != '\0'; fast++) {  
        // 判断是否为字母(大写或小写)  
        if (isalpha(str[fast])) {  
            str[slow++] = str[fast];  
        }  
    }  
    str[slow] = '\0';  
}  

int main() {  
    char input[100];  
    printf("请输入文本: ");  
    fgets(input, sizeof(input), stdin);  

    filter_letters_only(input);  
    printf("过滤后仅含字母的文本: %s\n", input);  

    return 0;  
}  

代码解析

  • isalpha 函数:来自 <ctype.h>,用于判断字符是否为字母(A-Za-z)。
  • 原地修改:直接修改输入数组,无需额外空间。

结论

本文通过三个递进的 C 语言实例,详细讲解了如何删除字符串中的特殊字符。从基础的逐字符遍历到高效的双指针算法,再到动态内存管理的高级技巧,逐步展示了如何根据实际需求选择最优方案。

关键要点总结

  • 字符串操作需严格控制边界条件,避免越界或内存泄漏。
  • 特殊字符的定义需根据业务场景灵活调整。
  • 双指针算法在原地修改场景中具有显著优势。
  • 动态内存分配适用于处理不确定长度的输入。

掌握这些方法后,读者可以将其扩展到更多场景,例如过滤 HTML 标签、清理日志文件中的噪声数据等。希望本文能帮助你在 C 语言字符串处理领域迈出扎实的一步!

最新发布