代码之家  ›  专栏  ›  技术社区  ›  John Rudy

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

  •  8
  • John Rudy  · 技术社区  · 16 年前

    我正在研究K&这本书。我读得比做练习更远,主要是因为时间不够。我正在学习,已经完成了第1章的几乎所有练习,这是教程。

    我的问题是练习1-18。这项工作的目的是:

    编写一个程序来删除尾随空格和 从输入行中删除制表符,并完全删除空行

    有谁能提供一些关于清理这些的建议吗?有一个问题是,这些建议只能使用《K&(我知道有无数种方法可以使用完整的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] = '\0'; 
        return i;
    }
    
    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)
        {
            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;
            }
        }
    }
    

    我要寻找的大部分是trim方法本身——特别是我正在循环的事实 3. 时代(感觉如此肮脏)。我觉得如果我再聪明一点(即使没有C语言的高级知识),这可能会更干净。

    9 回复  |  直到 10 年前
        1
  •  9
  •   Eric Z Beard    16 年前

    如果你坚持第一章,我觉得这很好。以下是我从代码审查的角度提出的建议:

    在C中检查相等时,始终将常量放在第一位

    if (1 == myvar)
    

    if (myvar = 1)
    

    在C#中,您无法摆脱这种情况,但它在C中编译得很好,而且可能是一个真正的调试难题。

        2
  •  5
  •   Ferruccio    16 年前

    没有理由有两个缓冲区,您可以在适当的位置修剪输入行

    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;
    
        3
  •  1
  •   plinth    16 年前

    修剪()太大了。

    然后需要一个名为int scanback(const char*s,const char*matches,int start)的函数,该函数从开始处开始,一直到z,只要在匹配项中包含的s id处扫描的字符返回找到匹配项的最后一个索引。

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

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

    你应该能够用这些来写trim。

        4
  •  1
  •   TK.    16 年前

    个人对于while构造:

    我比较喜欢以下几点:

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

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

    他们两人都核对过了!=0,但第一个看起来更干净一些。如果char不是0,那么循环体将执行,否则它将跳出循环。

    对于“for”语句,虽然在语法上是有效的,但我发现:

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

    对我来说,这看起来很“奇怪”,实际上是潜在bug的噩梦解决方案。如果我回顾这段代码,它会像一个发光的红色警告一样。通常,您希望使用for循环进行已知次数的迭代,否则会对while循环进行排序。(和往常一样,这条规则也有例外,但我发现这通常是正确的)。上述声明可能成为:

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

    首先:

    内部主(空)

    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-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] == '\0') { //line doesn't have a \n -- add it
                ret[i] = '\n';
                ret[i+1] = '\0';
                break;
            }
        }
    }
    

    (还添加了注释并修复了一个bug。)

    一个大问题是MAXLINE常量的使用——main()专门将其用于 出来

        6
  •  0
  •   ilitirit    16 年前

    我个人会这样写代码:

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

    转换为单独的函数(甚至定义宏)

        7
  •  0
  •   user3458 user3458    16 年前
    1. trim实际上应该只使用1个缓冲区(正如@Ferruccio所说)。
    2. 正如@plinth所说,修剪需要分解
    3. trim不需要返回任何值(如果要检查空字符串,请测试行[0]==0)
    4. 对于额外的C风格,使用指针而不是索引

    -转到行的末尾(终止于0; -当不在行首且当前字符为空格时,将其替换为0。

    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;
      }
    }
    
        8
  •  0
  •   Frederico Frederico    16 年前

    做同样事情的另一个例子。使用C99特定的东西做了一些轻微的违规行为。这在K&R.还使用了assert()函数,该函数是标准库的一部分,但在K&R

    #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 = '\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 (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;
    
    }
    
        9
  •  0
  •   orj    16 年前

    以下是我在不知道第1章或K&中内容的情况下尝试的练习;我想是指针?

    #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;
    }