K&R练习:我的代码有效,但感觉很臭; 建议清理?

我正在研究K&R的书。 我读得比我做的更进一步,主要是因为时间不够。 我正赶上来,并完成了第1章的几乎所有练习,这是教程。

我的问题是练习1-18。 练习是:

编写程序以从输入行中删除尾随空白和制表符,并删除完全空行

我的代码(下面)做到了,并且有效。 我的问题是我实施的修剪方法。 感觉……错……不知何故。 就像我在代码审查中看到C#中的类似代码一样,我可能会疯了。 (C#是我的专长之一。)

任何人都可以提供一些关于清理它的建议 – 所述建议必须只使用K&R第1章的知识。(我知道有很多方法可以使用完整的C库来清理它;我们’我只是在这里谈论第1章和基本的stdio.h。)另外,在给出建议的时候,你能解释它为什么会有用吗? (毕竟,我是在努力学习!谁能比这里的专家更好地学习?)

#include  #define MAXLINE 1000 int getline(char line[], int max); void trim(char line[], char ret[]); int main() { char line[MAXLINE]; char out[MAXLINE]; int length; while ((length = getline(line, MAXLINE)) > 0) { trim(line, out); printf("%s", out); } return 0; } int getline(char line[], int max) { int c, i; for (i = 0; i = 0; --i) { if (ret[i] == ' ' || ret[i] == '\t') ret[i] = '\0'; else if (ret[i] != '\0' && ret[i] != '\r' && ret[i] != '\n') break; } for (i = 0; i < MAXLINE; ++i) { if (ret[i] == '\n') { break; } else if (ret[i] == '\0') { ret[i] = '\n'; ret[i + 1] = '\0'; break; } } } 

编辑:我感谢我在这里看到的所有有用的提示。 我想提醒大家,我仍然是一个带有C的n00b,特别是还没有达到指针。 (记住关于K&R的Ch.1的一点 – 第1章没有指针。)我“有点”得到一些解决方案,但它们仍然是一个先进的触摸我…

而我正在寻找的大多数是修剪方法本身 – 特别是我循环3次(感觉很脏)的事实。 我觉得如果我只是一个更聪明的触摸(即使没有C的高级知识),这本来可以更清洁。

没有理由有两个缓冲区,您可以修改输入线

 int trim(char line[]) { int len = 0; for (len = 0; line[len] != 0; ++len) ; while (len > 0 && line[len-1] == ' ' && line[len-1] == '\t' && line[len-1] == '\n') line[--len] = 0; return len; } 

通过返回行长度,可以通过测试非零长度行来消除空行

 if (trim(line) != 0) printf("%s\n", line); 

编辑:假设ASCII编码,您可以使while循环更简单。

 while (len > 0 && line[len-1] <= ' ') line[--len] = 0; 

如果你坚持第1章,这看起来对我很好。 以下是我从代码审查的角度推荐的内容:

在C中检查相等性时,始终先将常量置于其中

 if (1 == myvar) 

这样你就不会意外地做这样的事情:

 if (myvar = 1) 

你无法在C#中使用它,但它在C中编译得很好并且可以成为一个真正的调试恶魔。

trim()太大了。

我认为你需要的是一个strlen-ish函数(继续把它写成int stringlength(const char * s))。

然后你需要一个名为int scanback的函数(const char * s,const char * matches,int start),它从start开始,只要在s id中包含的字符被匹配,就会返回z,返回最后一个索引所在的位置找到一个匹配。

然后你需要一个名为int scanfront(const char * s,const char * matches)的函数,它从0开始并向前扫描,只要在s处扫描的字符包含在匹配项中,返回找到匹配项的最后一个索引。

然后你需要一个名为int charinstring(char c,const char * s)的函数,如果c包含在s中则返回非零,否则返回0。

你应该能够根据这些来编写修剪。

个人为while构造:

我更喜欢以下内容:

 while( (ret[i] = line[i]) ) i++; 

至:

 while ((ret[i] = line[i]) != '\0') ++i; 

他们都检查!= 0但第一个看起来更清洁。 如果char是其他任何0,那么循环体将执行其他它将突破循环。

对于’for’语句,虽然在语法上有效,但我发现以下内容:

 for ( ; i >= 0; --i) 

对我来说看起来很奇怪,确实是潜在错误的潜在噩梦解决方案。 如果我正在审查这段代码,那就像是一个发光的红色警告。 通常,您希望使用for循环来迭代已知次数,否则需要使用for循环。 (一如既往有规则的例外,但我发现这通常是正确的)。 上述声明可能变为:

 while (i) { if (ret[i] == ' ' || ret[i] == '\t') { ret[i--] = '\0'; } else if (ret[i] != '\0' && ret[i] != '\r' && ret[i] != '\n') { break; } } 

首先:

int main(void)

你知道main()的参数。 他们什么都没有。 (或argc&argv,但我认为这不是第1章的材料。)

Stylewise,你可能想尝试K&R风格的支架。 它们在垂直空间上更容易:

 void trim(char line[], char ret[]) { int i = 0; while ((ret[i] = line[i]) != '\0') ++i; if (i == 1) { // Special case to remove entirely blank line ret[0] = '\0'; return; } for (; i>=0; --i) { //continue backwards from the end of the line if ((ret[i] == ' ') || (ret[i] == '\t')) //remove trailing whitespace ret[i] = '\0'; else if ((ret[i] != '\0') && (ret[i] != '\r') && (ret[i] != '\n')) //...until we hit a word character break; } for (i=0; i 

(还添加了评论并修复了一个错误。)

一个很大的问题是使用MAXLINE常量 - main()专门将它用于lineout变量; trim(),只对它们起作用,不需要使用常量。 您应该将大小作为参数传递,就像在getline()中一样。

就个人而言,我会把这样的代码:

 ret[i] != '\0' && ret[i] != '\r' && ret[i] != '\n' 

进入一个单独的函数(甚至是一个定义宏)

  1. 修剪应该只使用1个缓冲区(如@Ferruccio所说)。
  2. 正如@plinth所说,需要打破修剪
  3. trim不需要返回任何值(如果要检查空字符串,测试行[0] == 0)
  4. 对于额外的C风味,使用指针而不是索引

-go到行尾(终止0; – 虽然不是在行的开头,当前字符是空格,用0.-off一个char替换它

 char *findEndOfString(char *string) { while (*string) ++string; return string; // string is now pointing to the terminating 0 } void trim(char *line) { char *end = findEndOfString(line); // note that we start at the first real character, not at terminating 0 for (end = end-1; end >= line; end--) { if (isWhitespace(*end)) *end = 0; else return; } } 

做同样事情的另一个例子。 通过使用C99特定的东西做了一些小的违规。 在K&R中找不到。 还使用了assert()函数,它是starndard库的一部分,但可能没有在K&R的第一章中介绍过。

 #include  /* needed when using bool, false and true. C99 specific. */ #include  /* needed for calling assert() */ typedef enum { TAB = '\t', BLANK = ' ' } WhiteSpace_e; typedef enum { ENDOFLINE = '\n', ENDOFSTRING = '\0' } EndofLine_e; bool isWhiteSpace( char character ) { if ( (BLANK == character) || (TAB == character ) ) { return true; } else { return false; } } bool isEndOfLine( char character ) { if ( (ENDOFLINE == character) || (ENDOFSTRING == character ) ) { return true; } else { return false; } } /* remove blanks and tabs (ie whitespace) from line-string */ void removeWhiteSpace( char string[] ) { int i; int indexOutput; /* copy all non-whitespace character in sequential order from the first to the last. whitespace characters are not copied */ i = 0; indexOutput = 0; while ( false == isEndOfLine( string[i] ) ) { if ( false == isWhiteSpace( string[i] ) ) { assert ( indexOutput <= i ); string[ indexOutput ] = string[ i ]; indexOutput++; } i++; /* proceed to next character in the input string */ } assert( isEndOfLine( string[ i ] ) ); string[ indexOutput ] = ENDOFSTRING; } 

这是我在练习时不知道第1章或K&R中的内容。我假设指针?

 #include "stdio.h" size_t StrLen(const char* s) { // this will crash if you pass NULL size_t l = 0; const char* p = s; while(*p) { l++; ++p; } return l; } const char* Trim(char* s) { size_t l = StrLen(s); if(l < 1) return 0; char* end = s + l -1; while(s < end && (*end == ' ' || *end == '\t')) { *end = 0; --end; } return s; } int Getline(char* out, size_t max) { size_t l = 0; char c; while(c = getchar()) { ++l; if(c == EOF) return 0; if(c == '\n') break; if(l < max-1) { out[l-1] = c; out[l] = 0; } } return l; } #define MAXLINE 1024 int main (int argc, char * const argv[]) { char line[MAXLINE]; while (Getline(line, MAXLINE) > 0) { const char* trimmed = Trim(line); if(trimmed) printf("|%s|\n", trimmed); line[0] = 0; } return 0; }