如何以标准方式修剪前导/后缀空白?
是否有一种干净的,最好是标准的方法来修剪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] = 'r = strdup(s + start);
r[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* 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);
}
';
else if( frontp != str && endp == frontp )
*str = '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 '#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] = '#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.
}
' terminator, in which case there's no problem writing
// another there).
*end = '##代码##';
return s;
}
char* trim( char* s)
{
return rtrim( ltrim( s));
}
')
{
if(ptr[j] == ' ' )
{
j++;
ptr[i] = ptr[j];
}
else
{
i++;
j++;
ptr[i] = ptr[j];
}
}
printf("\noutput-%s\n",ptr);
return 0;
}
测试正确性:
##代码##源文件是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的单元测试。
##代码##
