C 中的就地修剪/剥离
有关就地算法的说明,请参见我之前关于零拷贝就地分割的文章
问题
你有一个 C 字符串,可能在开头和/或结尾包含空白字符。
char* s = " abc \n\r";使用就地算法,你想从此字符串中删除空白字符。
这也可以使用 boost::algorithm::trim 来完成,但它有与 boost::algorithm::split 相同的注意事项,如我之前关于 C 分割的文章中所讨论的
什么是空白字符
在本文范围内,我们将空白字符定义为满足以下条件的字符
isspace(char c)从 ctype.h 返回 true。这可以适应特定用户需求。
从开头删除空白字符
删除字符串开头的空白字符很容易,但它有一个重要的注意事项。
#include <ctype.h>
char* trimLeft(char* s) {
while(isspace(*s)) {
s++;
}
return s;
}想法是创建一个前进到第一个非空白字符的新指针。现在说注意事项。
假设你的代码块如下所示:
char* s = strdup(/*...*/);
char* sLeftTrimmed = trimLeft(s);
// ... 用 sLeftTrimmed 做些什么
free(s);此代码可以正常工作。但记住你总是需要释放 s,而不是 sLeftTrimmed。以下代码会导致未定义行为:
char* s = strdup(/*...*/);
char* s = trimLeft(s);
// ... 用 sLeftTrimmed 做些什么
free(s);此注意事项很危险,因为如果 s 在开头不包含空白字符,它将正常工作。然而,当空白字符被剥离时,free 可能做任何事情,例如什么都不做(导致 s 未被释放),破坏程序的其他部分(使其几乎无法调试)或只是随机崩溃。你应该阅读每个 C 程序员应该知道的关于未定义行为的知识来了解此类问题。
从结尾删除空白字符
这部分算法稍微复杂一些,但它不像 trimLeft() 那样受到错误 free() 用法的注意事项影响。
#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 需要更广泛的回溯,直到找到码点的第一个字节。
双边修剪
给定修剪每侧的函数,修剪两侧就很简单了:
char* trim(char* s) {
return trimRight(trimLeft(s));
}先修剪左侧的原因是通过 strlen() 在 trimRight() 中搜索字符串的结尾可能由于较短的字符串而稍快。然而,对于实际用例,这应该没什么区别。
注意 trimLeft() 部分讨论的 free() 注意事项也适用于 trim()。
另请参见完整 trim 示例