K&R练习:我的代码有效,但感觉很臭;清理建议?
我正在研究K&R书。我读过的东西比做练习还要多,主要是因为没有时间。我正在追赶,并完成了第1章(即教程)中的几乎所有练习。
我的问题是练习1-18. 该练习是为了:
Write a program to remove trailing blanks and tabs from line of input, and to delete entirely blank lines
我的代码(如下)可以做到,并且可以正常工作。我的问题是我实施的修整方法。感觉……错了……以某种方式。就像我在Cin代码审查中看到类似的代码一样,我可能会发疯。 (成为我的专业之一。)
任何人都可以提供一些清理建议的建议-所说的建议只能使用K&R第1章中的知识。(我知道有无数种方法可以使用完整的C库进行清理;只是在这里谈论第1章和基本的stdio.h。)而且,在给出建议时,我们能解释一下为什么会有帮助吗? (毕竟,我是在努力学习!还有谁比这里的专家更好学?)
#include <stdio.h> #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 < max - 1 && (c = getchar()) != EOF && c != '\n'; ++i) line[i] = c; if (c == '\n') { line[i] = c; ++i; } line[i] = 'if (1 == myvar)'; return i; } void trim(char line[], char ret[]) { int i = 0; while ((ret[i] = line[i]) != 'if (myvar = 1)') ++i; if (i == 1) { // Special case to remove entirely blank line ret[0] = '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; }'; return; } for ( ; i >= 0; --i) { if (ret[i] == ' ' || ret[i] == '\t') ret[i] = 'if (trim(line) != 0) printf("%s\n", line);'; else if (ret[i] != 'while (len > 0 && line[len-1] <= ' ') line[--len] = 0;' && ret[i] != '\r' && ret[i] != '\n') break; } for (i = 0; i < MAXLINE; ++i) { if (ret[i] == '\n') { break; } else if (ret[i] == 'while( (ret[i] = line[i]) ) i++;') { ret[i] = '\n'; ret[i + 1] = 'while ((ret[i] = line[i]) != ''; break; } } }for ( ; i >= 0; --i)') ++i;
编辑:我感谢我在这里看到的所有有用的提示。我想提醒大家,我仍然是C的n00b,特别是还没有掌握指针。 (请记住,关于K&R的Ch.1的内容-Ch.1没有做指针。)我"有点"得到了其中的一些解决方案,但是对于我现在所处的位置,它们仍然是一种先进的方法。
而且我所寻找的大部分是trim方法本身-特别是我循环3次(感觉很脏)的事实。我觉得如果我只是更聪明一点(即使没有C的高级知识),这本可以变得更干净。
解决方案
如果我们坚持第1章,那对我来说看起来不错。从代码审查的角度来看,这是我的建议:
在C中检查相等性时,请始终将常量放在第一位
while (i) { if (ret[i] == ' ' || ret[i] == '\t') { ret[i--] = 'int main(void)'; } else if (ret[i] != 'void trim(char line[], char ret[]) { int i = 0; while ((ret[i] = line[i]) != '' && ret[i] != '\r' && ret[i] != '\n') { break; } }#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; }') ++i; if (i == 1) { // Special case to remove entirely blank line ret[0] = 'ret[i] != ''; return; } for (; i>=0; --i) { //continue backwards from the end of the line if ((ret[i] == ' ') || (ret[i] == '\t')) //remove trailing whitespace ret[i] = '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; } }' && ret[i] != '\r' && ret[i] != '\n'#include <stdbool.h> /* needed when using bool, false and true. C99 specific. */ #include <assert.h> /* needed for calling assert() */ typedef enum { TAB = '\t', BLANK = ' ' } WhiteSpace_e; typedef enum { ENDOFLINE = '\n', ENDOFSTRING = '##代码##' } 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 (i.e. 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; }'; else if ((ret[i] != '##代码##') && (ret[i] != '\r') && (ret[i] != '\n')) //...until we hit a word character break; } for (i=0; i<MAXLINE-1; ++i) { //-1 because we might need to add a character to the line if (ret[i] == '\n') //break on newline break; if (ret[i] == '##代码##') { //line doesn't have a \n -- add it ret[i] = '\n'; ret[i+1] = '##代码##'; break; } } }
这样,我们将永远不会意外地执行以下操作:
##代码##我们无法在C#中摆脱它,但是它在C中可以很好地进行编译,并且可能是真正的调试恶魔。
没有理由有两个缓冲区,我们可以在适当的位置修剪输入行
##代码##通过返回行长,可以通过测试非零长度的行来消除空白行
##代码##编辑:假设ASCII编码,我们可以使while循环更加简单。
##代码##个人为while构造:
我更喜欢以下内容:
##代码##到:
##代码##他们都检查!= 0,但第一个看起来比较干净。如果char的值为0,则循环体将执行,否则将退出循环。
同样对于" for"语句,虽然在语法上有效,但我发现以下内容:
##代码##只是对我来说看起来"很奇怪",的确是解决潜在错误的潜在噩梦解决方案。如果我正在查看此代码,它将像一个发光的红色警告一样。通常,我们希望使用for循环迭代已知的次数,否则考虑使用while循环。 (和往常一样,该规则也有例外,但Ive发现这通常是成立的)。上面的for语句可能变为:
##代码##首先:
##代码##我们知道main()的参数。没什么(或者是argc&argv,但我认为这不是第1章的材料。)
从风格上讲,我们可能想尝试K&R样式的括号。在垂直空间上,它们要容易得多:
##代码##(还添加了注释并修复了一个错误。)
一个大问题是MAXLINE常量的用法-main()仅将其用于line和out变量; trim(),仅对它们起作用,不需要使用常量。我们应该像在getline()中一样将大小作为参数传递。
trim()太大。
我认为我们需要的是功能强大的函数(继续并将其写入int stringlength(const char * s))。
然后,我们需要一个名为int scanback(const char * s,const char * matches,int start)的函数,该函数从start开始,只要在match中包含的s id处扫描的字符降到z,返回最后一个索引,其中找到一个匹配项。
然后,我们需要一个名为int scanfront(const char * s,const char * matches)的函数,该函数从0开始并向前扫描,只要匹配中包含在s处扫描的字符,则返回找到匹配项的最后一个索引。
然后,我们需要一个名为int charinstring(char c,const char * s)的函数,如果c包含在s中,则该函数返回非零值,否则返回0。
我们应该能够在这些方面进行修整。
这是我不知道第1章或者K&R中的内容时的练习技巧。我假设使用了指针?
##代码##我个人将这样的代码放入:
##代码##进入一个单独的函数(甚至是一个定义宏)
- trim实际上应该只使用1个缓冲区(如@Ferruccio所说)。
- 如@plinth所说,修剪需要分解
- trim不需要返回任何值(如果要检查空字符串,请测试line [0] == 0)
- 为了获得更好的C语言风格,请使用指针而不是索引
-转到行尾(以0结尾;
-虽然不在行首,并且当前字符为空格,但将其替换为0。
-退回一个字符
做同样事情的另一个例子。通过使用C99专用的东西做了一些轻微的违反。在K&R中找不到。还使用了starndard库一部分的assert()函数,但可能不会在K&R的第一章中介绍。
##代码##