【发布时间】:2010-09-12 11:09:09
【问题描述】:
是否有一种干净的、最好是标准的方法从 C 中的字符串中修剪前导和尾随空格?我会自己动手,但我认为这是一个常见问题,但也有同样常见的解决方案。
【问题讨论】:
标签: c string whitespace trim
是否有一种干净的、最好是标准的方法从 C 中的字符串中修剪前导和尾随空格?我会自己动手,但我认为这是一个常见问题,但也有同样常见的解决方案。
【问题讨论】:
标签: c string whitespace trim
如果可以修改字符串:
// Note: This function returns a pointer to a substring of the original string.
// If the given string was allocated dynamically, the caller must not overwrite
// that pointer with the returned value, since the original pointer must be
// deallocated using the same allocator with which it was allocated. The return
// value must NOT be deallocated using free() etc.
char *trimwhitespace(char *str)
{
char *end;
// Trim leading space
while(isspace((unsigned char)*str)) str++;
if(*str == 0) // All spaces?
return str;
// Trim trailing space
end = str + strlen(str) - 1;
while(end > str && isspace((unsigned char)*end)) end--;
// Write new null terminator character
end[1] = '\0';
return str;
}
如果你不能修改字符串,那么你可以使用基本相同的方法:
// Stores the trimmed input string into the given output buffer, which must be
// large enough to store the result. If it is too small, the output is
// truncated.
size_t trimwhitespace(char *out, size_t len, const char *str)
{
if(len == 0)
return 0;
const char *end;
size_t out_size;
// Trim leading space
while(isspace((unsigned char)*str)) str++;
if(*str == 0) // All spaces?
{
*out = 0;
return 1;
}
// Trim trailing space
end = str + strlen(str) - 1;
while(end > str && isspace((unsigned char)*end)) end--;
end++;
// Set output size to minimum of trimmed string length and buffer size minus 1
out_size = (end - str) < len-1 ? (end - str) : len-1;
// Copy trimmed string and add null terminator
memcpy(out, str, out_size);
out[out_size] = 0;
return out_size;
}
【讨论】:
str 是一个局部变量,改变它不会改变传入的原始指针。C 中的函数调用总是按值传递,从不传递-参考。
free() 函数的有效参数。恰恰相反——我设计它是为了避免为了效率而分配内存。如果传入的地址是动态分配的,那么调用者仍然负责释放该内存,并且调用者需要确保不要用此处返回的值覆盖该值。
isspace 的参数转换为unsigned char,否则您会调用未定义的行为。
这是一个将字符串移动到缓冲区的第一个位置的方法。你可能想要这种行为,这样如果你动态分配字符串,你仍然可以在 trim() 返回的同一个指针上释放它:
char *trim(char *str)
{
size_t len = 0;
char *frontp = str;
char *endp = NULL;
if( str == NULL ) { return NULL; }
if( str[0] == '\0' ) { return str; }
len = strlen(str);
endp = str + len;
/* Move the front and back pointers to address the first non-whitespace
* characters from each end.
*/
while( isspace((unsigned char) *frontp) ) { ++frontp; }
if( endp != frontp )
{
while( isspace((unsigned char) *(--endp)) && endp != frontp ) {}
}
if( frontp != str && endp == frontp )
*str = '\0';
else if( str + len - 1 != endp )
*(endp + 1) = '\0';
/* Shift the string so that it starts at str so that if it's dynamically
* allocated, we can still free it on the returned pointer. Note the reuse
* of endp to mean the front of the string buffer now.
*/
endp = str;
if( frontp != str )
{
while( *frontp ) { *endp++ = *frontp++; }
*endp = '\0';
}
return str;
}
正确性测试:
#include <stdio.h>
#include <string.h>
#include <ctype.h>
/* Paste function from above here. */
int main()
{
/* The test prints the following:
[nothing to trim] -> [nothing to trim]
[ trim the front] -> [trim the front]
[trim the back ] -> [trim the back]
[ trim front and back ] -> [trim front and back]
[ trim one char front and back ] -> [trim one char front and back]
[ trim one char front] -> [trim one char front]
[trim one char back ] -> [trim one char back]
[ ] -> []
[ ] -> []
[a] -> [a]
[] -> []
*/
char *sample_strings[] =
{
"nothing to trim",
" trim the front",
"trim the back ",
" trim front and back ",
" trim one char front and back ",
" trim one char front",
"trim one char back ",
" ",
" ",
"a",
"",
NULL
};
char test_buffer[64];
char comparison_buffer[64];
size_t index, compare_pos;
for( index = 0; sample_strings[index] != NULL; ++index )
{
// Fill buffer with known value to verify we do not write past the end of the string.
memset( test_buffer, 0xCC, sizeof(test_buffer) );
strcpy( test_buffer, sample_strings[index] );
memcpy( comparison_buffer, test_buffer, sizeof(comparison_buffer));
printf("[%s] -> [%s]\n", sample_strings[index],
trim(test_buffer));
for( compare_pos = strlen(comparison_buffer);
compare_pos < sizeof(comparison_buffer);
++compare_pos )
{
if( test_buffer[compare_pos] != comparison_buffer[compare_pos] )
{
printf("Unexpected change to buffer @ index %u: %02x (expected %02x)\n",
compare_pos, (unsigned char) test_buffer[compare_pos], (unsigned char) comparison_buffer[compare_pos]);
}
}
}
return 0;
}
源文件是 trim.c。使用 'cc -Wall trim.c -o trim' 编译。
【讨论】:
isspace 的参数转换为unsigned char,否则您会调用未定义的行为。
isspace() 那么为什么" " 和"\n" 之间会有区别?我为换行添加了单元测试,对我来说它看起来不错...ideone.com/bbVmqo
*(endp + 1) = '\0';。答案的示例测试使用了 64 的缓冲区,从而避免了这个问题。
我的解决方案。字符串必须是可变的。其他一些解决方案的优势在于它将非空格部分移动到开头,因此您可以继续使用旧指针,以防您以后必须 free() 它。
void trim(char * s) {
char * p = s;
int l = strlen(p);
while(isspace(p[l - 1])) p[--l] = 0;
while(* p && isspace(* p)) ++p, --l;
memmove(s, p, l + 1);
}
此版本使用 strndup() 创建字符串的副本,而不是在原地编辑它。 strndup() 需要 _GNU_SOURCE,所以也许你需要用 malloc() 和 strncpy() 制作自己的 strndup()。
char * trim(char * s) {
int l = strlen(s);
while(isspace(s[l - 1])) --l;
while(* s && isspace(* s)) ++s, --l;
return strndup(s, l);
}
【讨论】:
trim() 如果s 是"" 则调用UB,因为第一个isspace() 调用将是isspace(p[-1]) 和p[-1] 不一定引用合法位置。
isspace 的参数转换为unsigned char,否则您会调用未定义的行为。
if(l==0)return;以避免零长度str
这是我的 C 迷你库,用于修剪左、右、两者、全部、就地和单独,以及修剪一组指定的字符(或默认情况下的空白)。
#ifndef STRLIB_H_
#define STRLIB_H_ 1
enum strtrim_mode_t {
STRLIB_MODE_ALL = 0,
STRLIB_MODE_RIGHT = 0x01,
STRLIB_MODE_LEFT = 0x02,
STRLIB_MODE_BOTH = 0x03
};
char *strcpytrim(char *d, // destination
char *s, // source
int mode,
char *delim
);
char *strtriml(char *d, char *s);
char *strtrimr(char *d, char *s);
char *strtrim(char *d, char *s);
char *strkill(char *d, char *s);
char *triml(char *s);
char *trimr(char *s);
char *trim(char *s);
char *kill(char *s);
#endif
#include <strlib.h>
char *strcpytrim(char *d, // destination
char *s, // source
int mode,
char *delim
) {
char *o = d; // save orig
char *e = 0; // end space ptr.
char dtab[256] = {0};
if (!s || !d) return 0;
if (!delim) delim = " \t\n\f";
while (*delim)
dtab[*delim++] = 1;
while ( (*d = *s++) != 0 ) {
if (!dtab[0xFF & (unsigned int)*d]) { // Not a match char
e = 0; // Reset end pointer
} else {
if (!e) e = d; // Found first match.
if ( mode == STRLIB_MODE_ALL || ((mode != STRLIB_MODE_RIGHT) && (d == o)) )
continue;
}
d++;
}
if (mode != STRLIB_MODE_LEFT && e) { // for everything but trim_left, delete trailing matches.
*e = 0;
}
return o;
}
// perhaps these could be inlined in strlib.h
char *strtriml(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_LEFT, 0); }
char *strtrimr(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_RIGHT, 0); }
char *strtrim(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_BOTH, 0); }
char *strkill(char *d, char *s) { return strcpytrim(d, s, STRLIB_MODE_ALL, 0); }
char *triml(char *s) { return strcpytrim(s, s, STRLIB_MODE_LEFT, 0); }
char *trimr(char *s) { return strcpytrim(s, s, STRLIB_MODE_RIGHT, 0); }
char *trim(char *s) { return strcpytrim(s, s, STRLIB_MODE_BOTH, 0); }
char *kill(char *s) { return strcpytrim(s, s, STRLIB_MODE_ALL, 0); }
一个主程序可以完成所有工作。
如果 src == dst 则修剪到位,否则,
它的工作方式类似于strcpy 例程。
它修剪字符串 delim 中指定的一组字符,如果为 null,则修剪空格。
它修剪左、右、两者和所有(如 tr)。
它没有太多内容,它只对字符串进行一次迭代。有些人可能会抱怨修剪右侧从左侧开始,但是,无论如何都不需要从左侧开始的 strlen。 (你必须以一种或另一种方式到达字符串的末尾以进行正确的修剪,所以你最好边做边做。)关于流水线和缓存大小等可能会有争论——谁知道呢.由于该解决方案从左到右工作并且只迭代一次,因此它也可以扩展到流上工作。限制:它不适用于 unicode 字符串。
【讨论】:
dtab[*d] 在将其用作数组索引之前不会将 *d 转换为 unsigned int。在带有签名字符的系统上,这将读取到dtab[-127],这将导致错误并可能崩溃。
dtab[*delim++] 上的潜在未定义行为,因为必须将 char 索引值强制转换为 unsigned char。代码假定为 8 位 char。 delim 应声明为 const char *。 dtab[0xFF & (unsigned int)*d] 会比 dtab[(unsigned char)*d] 更清晰。该代码适用于 UTF-8 编码的字符串,但不会去除非 ASCII 间距序列。
这是我对一个简单但正确的就地修剪功能的尝试。
void trim(char *str)
{
int i;
int begin = 0;
int end = strlen(str) - 1;
while (isspace((unsigned char) str[begin]))
begin++;
while ((end >= begin) && isspace((unsigned char) str[end]))
end--;
// Shift all characters back to the start of the string array.
for (i = begin; i <= end; i++)
str[i - begin] = str[i];
str[i - begin] = '\0'; // Null terminate string.
}
【讨论】:
str is "". Prevents str[-1]`时改成while ((end >= begin) && isspace(str[end]))防止UB。
isspace 的参数强制转换为unsigned char,否则您会调用未定义的行为。
<ctype.h> 中的函数旨在与 int 一起使用,它们表示 unsigned char 或特殊值 EOF。见stackoverflow.com/q/7131026/225757。
装饰派对迟到了
特点:
1. 与其他许多答案一样,快速修剪开头。
2. 走到尽头后,修剪右边,每个循环只做1次测试。与 @jfm3 类似,但适用于全空白字符串)
3. 当char 是有符号的char 时,为避免未定义的行为,请将*s 转换为unsigned char。
字符处理 "在所有情况下,参数都是
int,其值应表示为unsigned char或应等于宏EOF的值。如果参数有任何其他值,行为未定义。” C11 §7.4 1
#include <ctype.h>
// Return a pointer to the trimmed string
char *string_trim_inplace(char *s) {
while (isspace((unsigned char) *s)) s++;
if (*s) {
char *p = s;
while (*p) p++;
while (isspace((unsigned char) *(--p)));
p[1] = '\0';
}
// If desired, shift the trimmed string
return s;
}
@chqrlie 评论上面不会移动修剪后的字符串。这样做....
// Return a pointer to the (shifted) trimmed string
char *string_trim_inplace(char *s) {
char *original = s;
size_t len = 0;
while (isspace((unsigned char) *s)) {
s++;
}
if (*s) {
char *p = s;
while (*p) p++;
while (isspace((unsigned char) *(--p)));
p[1] = '\0';
// len = (size_t) (p - s); // older errant code
len = (size_t) (p - s + 1); // Thanks to @theriver
}
return (s == original) ? s : memmove(original, s, len + 1);
}
【讨论】:
这是一个类似于@adam-rosenfields 就地修改例程的解决方案,但无需使用 strlen()。像@jkramer 一样,字符串在缓冲区内左调整,因此您可以释放相同的指针。对于大字符串来说不是最优的,因为它不使用 memmove。包括@jfm3 提到的 ++/-- 运算符。包括基于FCTX 的单元测试。
#include <ctype.h>
void trim(char * const a)
{
char *p = a, *q = a;
while (isspace(*q)) ++q;
while (*q) *p++ = *q++;
*p = '\0';
while (p > a && isspace(*--p)) *p = '\0';
}
/* See http://fctx.wildbearsoftware.com/ */
#include "fct.h"
FCT_BGN()
{
FCT_QTEST_BGN(trim)
{
{ char s[] = ""; trim(s); fct_chk_eq_str("", s); } // Trivial
{ char s[] = " "; trim(s); fct_chk_eq_str("", s); } // Trivial
{ char s[] = "\t"; trim(s); fct_chk_eq_str("", s); } // Trivial
{ char s[] = "a"; trim(s); fct_chk_eq_str("a", s); } // NOP
{ char s[] = "abc"; trim(s); fct_chk_eq_str("abc", s); } // NOP
{ char s[] = " a"; trim(s); fct_chk_eq_str("a", s); } // Leading
{ char s[] = " a c"; trim(s); fct_chk_eq_str("a c", s); } // Leading
{ char s[] = "a "; trim(s); fct_chk_eq_str("a", s); } // Trailing
{ char s[] = "a c "; trim(s); fct_chk_eq_str("a c", s); } // Trailing
{ char s[] = " a "; trim(s); fct_chk_eq_str("a", s); } // Both
{ char s[] = " a c "; trim(s); fct_chk_eq_str("a c", s); } // Both
// Villemoes pointed out an edge case that corrupted memory. Thank you.
// http://stackoverflow.com/questions/122616/#comment23332594_4505533
{
char s[] = "a "; // Buffer with whitespace before s + 2
trim(s + 2); // Trim " " containing only whitespace
fct_chk_eq_str("", s + 2); // Ensure correct result from the trim
fct_chk_eq_str("a ", s); // Ensure preceding buffer not mutated
}
// doukremt suggested I investigate this test case but
// did not indicate the specific behavior that was objectionable.
// http://stackoverflow.com/posts/comments/33571430
{
char s[] = " foobar"; // Shifted across whitespace
trim(s); // Trim
fct_chk_eq_str("foobar", s); // Leading string is correct
// Here is what the algorithm produces:
char r[16] = { 'f', 'o', 'o', 'b', 'a', 'r', '\0', ' ',
' ', 'f', 'o', 'o', 'b', 'a', 'r', '\0'};
fct_chk_eq_int(0, memcmp(s, r, sizeof(s)));
}
}
FCT_QTEST_END();
}
FCT_END();
【讨论】:
另一种,一条线做真正的工作:
#include <stdio.h>
int main()
{
const char *target = " haha ";
char buf[256];
sscanf(target, "%s", buf); // Trimming on both sides occurs here
printf("<%s>\n", buf);
}
【讨论】:
%n 转换说明符的跳过字符的计数器,最后恐怕手动操作会更简单。
我不喜欢这些答案中的大多数,因为他们做了以下一项或多项......
这是我的版本:
void fnStrTrimInPlace(char *szWrite) {
const char *szWriteOrig = szWrite;
char *szLastSpace = szWrite, *szRead = szWrite;
int bNotSpace;
// SHIFT STRING, STARTING AT FIRST NON-SPACE CHAR, LEFTMOST
while( *szRead != '\0' ) {
bNotSpace = !isspace((unsigned char)(*szRead));
if( (szWrite != szWriteOrig) || bNotSpace ) {
*szWrite = *szRead;
szWrite++;
// TRACK POINTER TO LAST NON-SPACE
if( bNotSpace )
szLastSpace = szWrite;
}
szRead++;
}
// TERMINATE AFTER LAST NON-SPACE (OR BEGINNING IF THERE WAS NO NON-SPACE)
*szLastSpace = '\0';
}
【讨论】:
isspace 的参数强制转换为unsigned char,否则您会调用未定义的行为。
while (isspace((unsigned char) *szWrite)) szWrite++; 会阻止这种情况。代码还会复制所有尾随空格。
*szWrite = *szRead才会跳过这种情况下的写入,但是我们添加了另一个比较/分支。使用现代 CPU/MMU/BP,我不知道该检查是损失还是收益。使用更简单的处理器和内存架构,只进行复制并跳过比较会更便宜。
我不确定你认为什么是“无痛”。
C 字符串非常痛苦。我们可以很容易地找到第一个非空白字符的位置:
而 (isspace(* p)) p++;我们可以通过两个类似的琐碎动作找到最后一个非空白字符的位置:
而 (* q) q++; 做{ q--; } 而 (isspace(* q));(我已经免除了您同时使用 * 和 ++ 运算符的痛苦。)
现在的问题是你会用这个做什么?手头的数据类型实际上并不是一个很容易想到的大而健壮的抽象String,而实际上只是一个存储字节数组。由于缺乏健壮的数据类型,因此不可能编写出与 PHperytonby 的 chomp 函数相同的函数。 C 中这样的函数会返回什么?
【讨论】:
do { q--; } ... 之前进行一次检查以了解*q != 0。
使用string library,例如:
Ustr *s1 = USTR1(\7, " 12345 ");
ustr_sc_trim_cstr(&s1, " ");
assert(ustr_cmp_cstr_eq(s1, "12345"));
...正如您所说,这是一个“常见”问题,是的,您需要包含一个 #include 左右,它不包含在 libc 中,但不要发明自己的 hack 作业来存储随机指针和 size_t 那样只会导致缓冲区溢出。
【讨论】:
如果您使用的是glib,那么您可以使用g_strstrip
【讨论】:
聚会很晚了……
没有回溯的单程前向扫描解决方案。源字符串中的每个字符都被精确地测试 一次 两次。 (所以它应该比这里的大多数其他解决方案更快,特别是如果源字符串有很多尾随空格。)
这包括两种解决方案,一种是将源字符串复制并修剪为另一个目标字符串,另一种是将源字符串修剪到位。两个函数使用相同的代码。
(可修改的)字符串被原地移动,因此指向它的原始指针保持不变。
#include <stddef.h>
#include <ctype.h>
char * trim2(char *d, const char *s)
{
// Sanity checks
if (s == NULL || d == NULL)
return NULL;
// Skip leading spaces
const unsigned char * p = (const unsigned char *)s;
while (isspace(*p))
p++;
// Copy the string
unsigned char * dst = (unsigned char *)d; // d and s can be the same
unsigned char * end = dst;
while (*p != '\0')
{
if (!isspace(*dst++ = *p++))
end = dst;
}
// Truncate trailing spaces
*end = '\0';
return d;
}
char * trim(char *s)
{
return trim2(s, s);
}
【讨论】:
'\0'比较,然后用@987654323测试@。用isspace() 测试所有字符似乎很浪费。对于非病理情况,从字符串末尾回溯应该更有效。
trim() 好的。角落案例:当d,s 和s < d 重叠时,trim2(char *d, const char *s) 有问题。
trim() 应该如何表现?您要求修剪字符串并将其复制到字符串本身占用的内存中。与memmove() 不同,这需要在进行修剪之前确定源字符串的长度,这需要额外扫描整个字符串。最好编写一个不同的rtrim2() 函数,该函数知道将源向后复制到目标,并且可能需要一个额外的源字符串长度参数。
为了保持这种增长,还有一个带有可修改字符串的选项:
void trimString(char *string)
{
size_t i = 0, j = strlen(string);
while (j > 0 && isspace((unsigned char)string[j - 1])) string[--j] = '\0';
while (isspace((unsigned char)string[i])) i++;
if (i > 0) memmove(string, string + i, j - i + 1);
}
【讨论】:
strlen() 返回一个可能超出int 范围的size_t。空白不限于空格字符。最后但最重要的是:strcpy(string, string + i * sizeof(char)); 上的未定义行为,因为源数组和目标数组重叠。使用memmove() 而不是strcpy()。
while (isspace((int)string[i])) string[i--] = '\0'; 可能会超出字符串的开头。您应该将此循环与前面和后面的行结合起来并编写while (i > 0 && isspace((unsigned char)string[--i])) { string[i] = '\0'; } size_t end = i;
end 没有指向结尾的空字节,而您的end = ++i; 对于包含所有空白字符的字符串仍然存在问题。我刚刚修复了代码。
我知道有很多答案,但我在这里发布我的答案,看看我的解决方案是否足够好。
// Trims leading whitespace chars in left `str`, then copy at almost `n - 1` chars
// into the `out` buffer in which copying might stop when the first '\0' occurs,
// and finally append '\0' to the position of the last non-trailing whitespace char.
// Reture the length the trimed string which '\0' is not count in like strlen().
size_t trim(char *out, size_t n, const char *str)
{
// do nothing
if(n == 0) return 0;
// ptr stop at the first non-leading space char
while(isspace(*str)) str++;
if(*str == '\0') {
out[0] = '\0';
return 0;
}
size_t i = 0;
// copy char to out until '\0' or i == n - 1
for(i = 0; i < n - 1 && *str != '\0'; i++){
out[i] = *str++;
}
// deal with the trailing space
while(isspace(out[--i]));
out[++i] = '\0';
return i;
}
【讨论】:
isspace(*str) UB 当*str < 0.
size_t n 很好,但是当n 太小而不能完整修剪字符串时,界面不会以任何方式通知调用者。考虑trim(out, 12, "delete data not")
在字符串中跳过前导空格的最简单方法是,恕我直言,
#include <stdio.h>
int main()
{
char *foo=" teststring ";
char *bar;
sscanf(foo,"%s",bar);
printf("String is >%s<\n",bar);
return 0;
}
【讨论】:
" foo bar "。
好的,这是我对这个问题的看法。我相信这是修改字符串的最简洁的解决方案(free 将起作用)并避免任何 UB。对于小字符串,它可能比涉及 memmove 的解决方案更快。
void stripWS_LT(char *str)
{
char *a = str, *b = str;
while (isspace((unsigned char)*a)) a++;
while (*b = *a++) b++;
while (b > str && isspace((unsigned char)*--b)) *b = 0;
}
【讨论】:
b > str 测试只需要一次。 *b = 0; 只需要一次。
#include <ctype.h>
#include <string.h>
char *trim_space(char *in)
{
char *out = NULL;
int len;
if (in) {
len = strlen(in);
while(len && isspace(in[len - 1])) --len;
while(len && *in && isspace(*in)) ++in, --len;
if (len) {
out = strndup(in, len);
}
}
return out;
}
isspace 有助于修剪所有空白。
strndup通过排除空格来创建新的字符串缓冲区。【讨论】:
strndup() 不是 C 标准的一部分,而只是 Posix。但由于它很容易实现,所以没什么大不了的。
trim_space("") 返回NULL。我希望有一个指向"" 的指针。 int len; 应该是 size_t len;。 isspace(in[len - 1])UB 当in[len - 1] < 0.
len = strlen(in); 之前的初始while (isspace((unsigned char) *in) in++; 会比后面的while(len && *in && isspace(*in)) ++in, --len; 更有效
这个简短而简单,使用 for 循环并且不会覆盖字符串边界。
如果需要,您可以将测试替换为 isspace()。
void trim (char *s) // trim leading and trailing spaces+tabs
{
int i,j,k, len;
j=k=0;
len = strlen(s);
// find start of string
for (i=0; i<len; i++) if ((s[i]!=32) && (s[i]!=9)) { j=i; break; }
// find end of string+1
for (i=len-1; i>=j; i--) if ((s[i]!=32) && (s[i]!=9)) { k=i+1; break;}
if (k<=j) {s[0]=0; return;} // all whitespace (j==k==0)
len=k-j;
for (i=0; i<len; i++) s[i] = s[j++]; // shift result to start of string
s[i]=0; // end the string
}//_trim
【讨论】:
就个人而言,我会自己动手。您可以使用 strtok,但您需要注意这样做(尤其是在删除前导字符时),您知道什么是内存。
删除尾随空格很容易,而且非常安全,因为您只需将 0 放在最后一个空格的顶部,从末尾倒数。摆脱领先空间意味着移动事物。如果您想在原地执行此操作(可能是明智的),您可以继续将所有内容移回一个字符,直到没有前导空格。或者,为了更有效,您可以找到第一个非空格字符的索引,然后将所有内容移回该数字。或者,您可以只使用指向第一个非空格字符的指针(但是您需要像使用 strtok 一样小心)。
【讨论】:
#include "stdafx.h"
#include "malloc.h"
#include "string.h"
int main(int argc, char* argv[])
{
char *ptr = (char*)malloc(sizeof(char)*30);
strcpy(ptr," Hel lo wo rl d G eo rocks!!! by shahil sucks b i g tim e");
int i = 0, j = 0;
while(ptr[j]!='\0')
{
if(ptr[j] == ' ' )
{
j++;
ptr[i] = ptr[j];
}
else
{
i++;
j++;
ptr[i] = ptr[j];
}
}
printf("\noutput-%s\n",ptr);
return 0;
}
【讨论】:
比赛有点晚了,但我会把我的日常工作投入到战斗中。它们可能不是最绝对有效的,但我相信它们是正确的并且很简单(rtrim() 推动了复杂性的极限):
#include <ctype.h>
#include <string.h>
/*
Public domain implementations of in-place string trim functions
Michael Burr
michael.burr@nth-element.com
2010
*/
char* ltrim(char* s)
{
char* newstart = s;
while (isspace( *newstart)) {
++newstart;
}
// newstart points to first non-whitespace char (which might be '\0')
memmove( s, newstart, strlen( newstart) + 1); // don't forget to move the '\0' terminator
return s;
}
char* rtrim( char* s)
{
char* end = s + strlen( s);
// find the last non-whitespace character
while ((end != s) && isspace( *(end-1))) {
--end;
}
// at this point either (end == s) and s is either empty or all whitespace
// so it needs to be made empty, or
// end points just past the last non-whitespace character (it might point
// at the '\0' terminator, in which case there's no problem writing
// another there).
*end = '\0';
return s;
}
char* trim( char* s)
{
return rtrim( ltrim( s));
}
【讨论】:
char 参数转换为 isspace() 到 (unsigned char) 以避免对潜在负值的未定义行为。如果没有必要,也要避免在ltrim() 中移动字符串。
到目前为止,大多数答案都执行以下操作之一:
strlen(),第二次遍历整个字符串。这个版本只做一次,不回溯。因此它可能比其他的表现更好,尽管只有在通常有数百个尾随空格的情况下(这在处理 SQL 查询的输出时并不罕见。)
static char const WHITESPACE[] = " \t\n\r";
static void get_trim_bounds(char const *s,
char const **firstWord,
char const **trailingSpace)
{
char const *lastWord;
*firstWord = lastWord = s + strspn(s, WHITESPACE);
do
{
*trailingSpace = lastWord + strcspn(lastWord, WHITESPACE);
lastWord = *trailingSpace + strspn(*trailingSpace, WHITESPACE);
}
while (*lastWord != '\0');
}
char *copy_trim(char const *s)
{
char const *firstWord, *trailingSpace;
char *result;
size_t newLength;
get_trim_bounds(s, &firstWord, &trailingSpace);
newLength = trailingSpace - firstWord;
result = malloc(newLength + 1);
memcpy(result, firstWord, newLength);
result[newLength] = '\0';
return result;
}
void inplace_trim(char *s)
{
char const *firstWord, *trailingSpace;
size_t newLength;
get_trim_bounds(s, &firstWord, &trailingSpace);
newLength = trailingSpace - firstWord;
memmove(s, firstWord, newLength);
s[newLength] = '\0';
}
【讨论】:
strspn() 和strcspn()。这是非常低效的,而且开销将使单次前传的未经证实的优势相形见绌。 strlen() 通常用非常有效的代码内联扩展,这不是一个真正的问题。修剪字符串的开头和结尾将比测试字符串中的每个字符的白度要快得多,即使在具有非常少或没有非白色字符的字符串的特殊情况下也是如此。
这是我能想到的最短的实现:
static const char *WhiteSpace=" \n\r\t";
char* trim(char *t)
{
char *e=t+(t!=NULL?strlen(t):0); // *e initially points to end of string
if (t==NULL) return;
do --e; while (strchr(WhiteSpace, *e) && e>=t); // Find last char that is not \r\n\t
*(++e)=0; // Null-terminate
e=t+strspn (t,WhiteSpace); // Find first char that is not \t
return e>t?memmove(t,e,strlen(e)+1):t; // memmove string contents and terminator
}
【讨论】:
char *trim(char *s) { char *p = s, *e = s + strlen(s); while (e > s && isspace((unsigned char)e[-1])) { *--e = '\0'; } while (isspace((unsigned char)*p)) { p++; } if (p > s) { memmove(s, p, e + 1 - p); } return s; }
这些函数会修改原来的缓冲区,所以如果动态分配,原来的 指针可以被释放。
#include <string.h>
void rstrip(char *string)
{
int l;
if (!string)
return;
l = strlen(string) - 1;
while (isspace(string[l]) && l >= 0)
string[l--] = 0;
}
void lstrip(char *string)
{
int i, l;
if (!string)
return;
l = strlen(string);
while (isspace(string[(i = 0)]))
while(i++ < l)
string[i-1] = string[i];
}
void strip(char *string)
{
lstrip(string);
rstrip(string);
}
【讨论】:
rstrip() 在空字符串上调用未定义的行为。 lstrip() 在具有较长的空白字符初始部分的字符串上不必要地慢。 isspace() 不应传递 char 参数,因为它在不同于 EOF 的负值上调用未定义的行为。
您对使用头文件 Shlwapi.h 中定义的 StrTrim 函数有何看法?它是直截了当的,而不是您自己定义的。
详情可见:
http://msdn.microsoft.com/en-us/library/windows/desktop/bb773454(v=vs.85).aspx
如果你有char ausCaptain[]="GeorgeBailey ";StrTrim(ausCaptain," ");
这将使ausCaptain 成为"GeorgeBailey" 而不是"GeorgeBailey "。
【讨论】:
为了从两边修剪我的琴弦,我使用了 oldie 但 gooody ;) 它可以修剪 ascii 小于空格的任何内容,这意味着控制字符也将被修剪!
char *trimAll(char *strData)
{
unsigned int L = strlen(strData);
if(L > 0){ L--; }else{ return strData; }
size_t S = 0, E = L;
while((!(strData[S] > ' ') || !(strData[E] > ' ')) && (S >= 0) && (S <= L) && (E >= 0) && (E <= L))
{
if(strData[S] <= ' '){ S++; }
if(strData[E] <= ' '){ E--; }
}
if(S == 0 && E == L){ return strData; } // Nothing to be done
if((S >= 0) && (S <= L) && (E >= 0) && (E <= L)){
L = E - S + 1;
memmove(strData,&strData[S],L); strData[L] = '\0';
}else{ strData[0] = '\0'; }
return strData;
}
【讨论】:
size_t 而不是unsigned int。该代码有很多冗余测试,并在strncpy(strData,&strData[S],L) 上调用未定义的行为,因为源和目标数组重叠。使用memmove() 而不是strncpy()。
我只包含代码,因为到目前为止发布的代码似乎不是最理想的(而且我还没有代表发表评论。)
void inplace_trim(char* s)
{
int start, end = strlen(s);
for (start = 0; isspace(s[start]); ++start) {}
if (s[start]) {
while (end > 0 && isspace(s[end-1]))
--end;
memmove(s, &s[start], end - start);
}
s[end - start] = '\0';
}
char* copy_trim(const char* s)
{
int start, end;
for (start = 0; isspace(s[start]); ++start) {}
for (end = strlen(s); end > 0 && isspace(s[end-1]); --end) {}
return strndup(s + start, end - start);
}
strndup() 是一个 GNU 扩展。如果您没有它或类似的东西,请自己动手。例如:
r = strdup(s + start);
r[end-start] = '\0';
【讨论】:
isspace(0) 被定义为假,你可以简化这两个函数。还将memmove() 移动到if 块内。
这里我使用动态内存分配将输入字符串修剪为函数trimStr。首先,我们找出输入字符串中有多少个非空字符。然后,我们分配一个具有该大小的字符数组并处理以空字符结尾的字符。当我们使用这个函数时,我们需要释放主函数内部的内存。
#include<stdio.h>
#include<stdlib.h>
char *trimStr(char *str){
char *tmp = str;
printf("input string %s\n",str);
int nc = 0;
while(*tmp!='\0'){
if (*tmp != ' '){
nc++;
}
tmp++;
}
printf("total nonempty characters are %d\n",nc);
char *trim = NULL;
trim = malloc(sizeof(char)*(nc+1));
if (trim == NULL) return NULL;
tmp = str;
int ne = 0;
while(*tmp!='\0'){
if (*tmp != ' '){
trim[ne] = *tmp;
ne++;
}
tmp++;
}
trim[nc] = '\0';
printf("trimmed string is %s\n",trim);
return trim;
}
int main(void){
char str[] = " s ta ck ove r fl o w ";
char *trim = trimStr(str);
if (trim != NULL )free(trim);
return 0;
}
【讨论】:
这是我的做法。它会在适当的位置修剪字符串,因此不必担心释放返回的字符串或丢失指向已分配字符串的指针。这可能不是最短的答案,但对大多数读者来说应该很清楚。
#include <ctype.h>
#include <string.h>
void trim_str(char *s)
{
const size_t s_len = strlen(s);
int i;
for (i = 0; i < s_len; i++)
{
if (!isspace( (unsigned char) s[i] )) break;
}
if (i == s_len)
{
// s is an empty string or contains only space characters
s[0] = '\0';
}
else
{
// s contains non-space characters
const char *non_space_beginning = s + i;
char *non_space_ending = s + s_len - 1;
while ( isspace( (unsigned char) *non_space_ending ) ) non_space_ending--;
size_t trimmed_s_len = non_space_ending - non_space_beginning + 1;
if (s != non_space_beginning)
{
// Non-space characters exist in the beginning of s
memmove(s, non_space_beginning, trimmed_s_len);
}
s[trimmed_s_len] = '\0';
}
}
【讨论】: