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

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

  

编写程序以删除尾随空白和   输入行中的选项卡,以及完全删除空白行

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

任何人都可以提供一些关于清理它的建议 - 所述建议必须仅使用K& S第1章的知识。 R.(我知道有很多方法可以使用完整的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;
        }
    }
}
编辑:我感谢我在这里看到的所有有用的提示。我想提醒大家,我仍然是一个带有C的n00b,特别是还没有达到指针。 (记住关于K&amp; R-Ch.1的Ch.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函数(继续把它写成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循环来迭代已知次数,否则cosider一会儿循环。 (一如既往有规则的例外,但我发现这通常是正确的)。以上陈述可能成为:

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&amp; argv,但我认为这不是第1章的材料。)

Stylewise,你可能想尝试K&amp; 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-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;
        }
    }
}

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

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

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

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

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

  1. trim应该只使用1个缓冲区(如@Ferruccio所说)。
  2. 修剪需要打破,正如@plinth所说
  3. trim不需要返回任何值(如果要检查空字符串,测试行[0] == 0)
  4. 获取额外的C风味,使用指针而不是索引
  5. -go到行尾(终止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;
      }
    }
    

做同样事情的另一个例子。通过使用C99特定的东西做了一些小的违规。在K&amp; R中找不到。还使用了assert()函数,它是starndard库的一部分,但可能没有在K&amp; 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;

}

这是我在不知道第1章或K&amp; 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;
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top