C 中的就地修剪/剥离

有关就地算法的说明,请参见我之前关于零拷贝就地分割的文章

问题

你有一个 C 字符串,可能在开头和/或结尾包含空白字符。

trim_sample_string.c
char* s = " abc   \n\r";

使用就地算法,你想从此字符串中删除空白字符。

这也可以使用 boost::algorithm::trim 来完成,但它有与 boost::algorithm::split 相同的注意事项,如我之前关于 C 分割的文章中所讨论的

什么是空白字符

在本文范围内,我们将空白字符定义为满足以下条件的字符

isspace-prototype.c
isspace(char c)

ctype.h 返回 true。这可以适应特定用户需求。

从开头删除空白字符

删除字符串开头的空白字符很容易,但它有一个重要的注意事项。

trim_left.c
#include <ctype.h>

char* trimLeft(char* s) {
    while(isspace(*s)) {
        s++;
    }
    return s;
}

想法是创建一个前进到第一个非空白字符的新指针。现在说注意事项。

假设你的代码块如下所示:

trimleft_usage_good.c
char* s = strdup(/*...*/);
char* sLeftTrimmed = trimLeft(s);
// ... 用 sLeftTrimmed 做些什么
free(s);

此代码可以正常工作。但记住你总是需要释放 s,而不是 sLeftTrimmed。以下代码会导致未定义行为:

trimleft_usage_bad.c
char* s = strdup(/*...*/);
char* s = trimLeft(s);
// ... 用 sLeftTrimmed 做些什么
free(s);

此注意事项很危险,因为如果 s 在开头不包含空白字符,它将正常工作。然而,当空白字符被剥离时,free 可能做任何事情,例如什么都不做(导致 s 未被释放),破坏程序的其他部分(使其几乎无法调试)或只是随机崩溃。你应该阅读每个 C 程序员应该知道的关于未定义行为的知识来了解此类问题。

从结尾删除空白字符

这部分算法稍微复杂一些,但它不像 trimLeft() 那样受到错误 free() 用法的注意事项影响。

trim_right.c
#include <ctype.h>

char* trimRight(char* s) {
    //防止空字符串的保护
    int len = strlen(s);
    if(len == 0) {
        return s;
    }
    //实际算法
    char* pos = s + len - 1;
    while(pos >= s && isspace(*pos)) {
        *pos = '\0';
        pos--;
    }
    return s;
}

想法是将结尾的每个空格就地替换为 \0。所有函数

注意向后策略对 ASCII 编码效果很好,稍加修改也适用于 UTF-16 和 UTF-32 编码,但将其用于 UTF8 需要更广泛的回溯,直到找到码点的第一个字节。

双边修剪

给定修剪每侧的函数,修剪两侧就很简单了:

trim.c
char* trim(char* s) {
    return trimRight(trimLeft(s));
}

先修剪左侧的原因是通过 strlen()trimRight() 中搜索字符串的结尾可能由于较短的字符串而稍快。然而,对于实际用例,这应该没什么区别。

注意 trimLeft() 部分讨论的 free() 注意事项也适用于 trim()

另请参见完整 trim 示例


Check out similar posts by category: C/C++