如何以标准方式修剪前导/后缀空白?
是否有一种干净的,最好是标准的方法来修剪C中字符串的开头和结尾空格?我会自己动手,但我认为这是一个同样常见的解决方案,这是一个普遍的问题。
解决方案
如果可以修改字符串:
// 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] = '// 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; }'; return str; }
如果我们不能修改字符串,则可以使用基本相同的方法:
void str_trim(char *output, const char *text, int32 max_len) { int32 i, j, length; length = strlen(text); if (max_len < 0) { max_len = length + 1; } for (i=0; i<length; i++) { if ( (text[i] != ' ') && (text[i] != '\t') && (text[i] != '\n') && (text[i] != '\r')) { break; } } if (i == length) { // handle lines that are all whitespace output[0] = 0; return; } for (j=length-1; j>=0; j--) { if ( (text[j] != ' ') && (text[j] != '\t') && (text[j] != '\n') && (text[j] != '\r')) { break; } } length = j + 1 - i; strncpy(output, text + i, length); output[length] = 0; }
就个人而言,我会自己动手。我们可以使用strtok,但是这样做时要特别小心(尤其是要删除前导字符时),以知道什么是内存。
摆脱尾随的空格很容易,而且很安全,因为我们可以在最后一个空格的顶部放一个0,从末尾开始倒数。摆脱领先空间意味着要四处走动。如果我们想就地执行(可能明智的话),我们可以继续将所有内容移回一个字符,直到没有前导空格为止。或者,为了提高效率,我们可以找到第一个非空格字符的索引,然后将所有内容都移回该数字。或者,我们可以仅使用指向第一个非空格字符的指针(但随后我们需要以与strtok相同的方式小心)。
这是一个执行我们想要的功能。它应该处理字符串全为空格的简并情况。我们必须传入输出缓冲区和缓冲区的长度,这意味着我们必须传入分配的缓冲区。
while (isspace(* p)) p++;
循环中的if语句可能可以替换为isspace(text [i])或者isspace(text [j]),以使各行更易于阅读。我认为我是用这种方式设置它们的,因为有些字符我不想测试,但是看起来我现在涵盖了所有空白:-)
我不确定我们认为"无痛"。
C字符串非常痛苦。我们可以很容易地找到第一个非空白字符位置:
while (* q) q++; do { q--; } while (isspace(* q));
我们可以通过两个类似的琐碎动作找到最后一个非空白字符位置:
char *trim(char *str) { size_t len = 0; char *frontp = str; char *endp = NULL; if( str == NULL ) { return NULL; } if( str[0] == 'int main(int argc, char *argv[]) { char *sample_strings[] = { "nothing to trim", " trim the front", "trim the back ", " trim one char front and back ", " trim one char front", "trim one char back ", " ", " ", "a", "", NULL }; char test_buffer[64]; int index; for( index = 0; sample_strings[index] != NULL; ++index ) { strcpy( test_buffer, sample_strings[index] ); printf("[%s] -> [%s]\n", sample_strings[index], trim(test_buffer)); } /* The test prints the following: [nothing to trim] -> [nothing to trim] [ trim the front] -> [trim the front] [trim the back ] -> [trim the 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] [] -> [] */ return 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( str + len - 1 != endp ) *(endp + 1) = '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] = ''; else if( frontp != str && endp == frontp ) *str = 'r = strdup(s + start); r[end-start] = ''; } 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); }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); }';char * trim(char * s) { int l = strlen(s); while(isspace(s[l - 1])) --l; while(* s && isspace(* s)) ++s, --l; return strndup(s, l); }'; /* 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 = 'Ustr *s1 = USTR1(, " 12345 "); ustr_sc_trim_cstr(&s1, " "); assert(ustr_cmp_cstr_eq(s1, "12345"));'; } return str; }
(我免除了我们同时使用*
和++
运算符的痛苦。)
现在的问题是我们如何处理?眼前的数据类型并不是一个容易想到的强大的抽象字符串,而是一个存储字节数组。缺乏健壮的数据类型,不可能编写出与PHperytonby的chomp
函数相同的函数。这样的函数在C语言中会返回什么?
这是将字符串移到缓冲区的第一个位置的字符串。我们可能想要这种行为,以便如果我们动态分配了字符串,仍然可以在trim()返回的同一指针上释放它:
#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]!='#include <ctype.h> #include <string.h> /* Public domain implementations of in-place string trim functions Michael Burr [email protected] 2010 */ char* ltrim(char* s) { char* newstart = s; while (isspace( *newstart)) { ++newstart; } // newstart points to first non-whitespace char (which might be '') { if(ptr[j] == ' ' ) { j++; ptr[i] = ptr[j]; } else { i++; j++; ptr[i] = ptr[j]; } } printf("\noutput-%s\n",ptr); return 0; }#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') memmove( s, newstart, strlen( newstart) + 1); // don't forget to move the '#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); }' 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 '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] = '' terminator, in which case there's no problem writing // another there). *end = '##代码##'; return s; } char* trim( char* s) { return rtrim( ltrim( s)); }#include <ctype.h> void trim(char * const a) { char *p = a, *q = a; while (isspace(*q)) ++q; while (*q) *p++ = *q++; *p = '##代码##'; while (p > a && isspace(*--p)) *p = '##代码##'; } /* 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', '##代码##', ' ', ' ', 'f', 'o', 'o', 'b', 'a', 'r', '##代码##'}; fct_chk_eq_int(0, memcmp(s, r, sizeof(s))); } } FCT_QTEST_END(); } FCT_END();'; // Null terminate string. }
测试正确性:
##代码##源文件是trim.c。与'cc trim.c -o trim'一起编译。
我只包含代码是因为到目前为止发布的代码似乎不是最佳选择(并且我还没有代表要评论)。
##代码##strndup()是GNU扩展。如果我们没有它或者类似的东西,请自己动手。例如:
##代码##我的解决方案。字符串必须是可变的。与其他解决方案相比,它的优点是将非空格部分移到开头,这样我们就可以继续使用旧的指针,以防日后需要free()它。
##代码##此版本使用strndup()创建字符串的副本,而不是在原位对其进行编辑。 strndup()需要_GNU_SOURCE,因此也许我们需要使用malloc()和strncpy()来制作自己的strndup()。
##代码##使用字符串库,例如:
##代码##...正如我们所说的,这是一个"常见"问题,是的,我们需要包含一个#include左右,并且它不包含在libc中,但是不要去发明我们自己的存储随机指针的Hack作业,而size_t只会导致缓冲区溢出。
##代码##比赛有点晚了,但我会把日常工作弄得一团糟。它们可能不是最绝对有效的,但我相信它们是正确的并且很简单(使用rtrim()推动了复杂性包络):
##代码##这是我的C mini库,用于在适当位置并单独修剪左右,全部修剪,并修剪一组指定的字符(默认情况下为空白)。
strlib.h的内容:
##代码##strlib.c的内容:
##代码##一个主要的例程可以完成所有操作。
如果src == dst,它会修剪到位,否则,
它的工作方式类似于strcpy
例程。
它修剪字符串delim中指定的一组字符,如果为null,则修剪空白。
它修剪左,右,两者和全部(如tr)。
它没有太多内容,并且仅在字符串上迭代一次。有些人可能会抱怨修剪右部从左边开始,但是,不需要从左边开始的任何努力。 (必须以一种或者另一种方式到达字符串的末尾以进行正确的修整,因此我们不妨随心所欲地完成工作。)关于流水线化和缓存大小,可能会有一些争论,这些人都知道。由于该解决方案从左到右工作并且仅迭代一次,因此它也可以扩展为在流上工作。局限性:它不适用于unicode字符串。
这是我尝试的一种简单而正确的就地修剪功能。
##代码##这是一种类似于@ adam-rosenfields就地修改例程的解决方案,但无需使用strlen()。像@jkramer一样,字符串在缓冲区内向左调整,因此我们可以释放相同的指针。由于它不使用记忆,因此不适用于大型字符串。包括@ jfm3提及的++ /-运算符。包括基于FCTX的单元测试。
##代码##