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

时间:2020-03-06 15:01:17  来源:igfitidea点击:

我正在研究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]) != '
for (  ; i >= 0; --i)
') ++i;
'; break; } } }

编辑:我感谢我在这里看到的所有有用的提示。我想提醒大家,我仍然是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]) != '
#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] != '
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'
'; return; } for (; i>=0; --i) { //continue backwards from the end of the line if ((ret[i] == ' ') || (ret[i] == '\t')) //remove trailing whitespace ret[i] = '
#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; } } }
' && ret[i] != '\r' && ret[i] != '\n') { 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的第一章中介绍。

##代码##