8

私はK&Rの本に取り組んでいます。主に時間がないため、演習よりも先に読みました。私は追いついてきて、チュートリアルである第 1 章からほとんどすべての演習を完了しました。

私の問題は演習1-18でした。演習は次のとおりです。

入力行から末尾の空白とタブを削除し、空白行全体を削除するプログラムを作成してください

私のコード(以下)はそれを行い、機能します。それに関する私の問題は、私が実装したトリム方法です。それは...間違っている...どういうわけか感じます。たとえば、コード レビューで C# の同様のコードを見たら、私は気が狂ってしまうでしょう。(C# は私の専門の 1 つです。)

これをクリーンアップするためのアドバイスを誰か提供できますか? アドバイスは 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] = '\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 の初心者であり、具体的にはまだポインターに到達していないことを人々に思い出させたいと思います。(K&R の Ch.1 について少し覚えておいてください -- Ch.1 はポインターを実行しません。) 私はそれらの解決策のいくつかを「ちょっと」得ましたが、それらはまだ私がいる場所に対して少し進んでいます ...

そして、私が探しているもののほとんどは、trim メソッド自体です。具体的には、3回ループしているという事実です (とても汚れているように感じます)。私がもう少し賢ければ (C の高度な知識がなくても)、これはもっときれいだったかもしれません。

4

9 に答える 9

9

第1章に固執している場合、それは私にはかなり良さそうです. コードレビューの観点から私が推奨するものは次のとおりです。

C で等価性をチェックするときは、常に定数を最初に置きます

if (1 == myvar)

そうすれば、次のようなことを誤って行うことは決してありません。

if (myvar = 1)

C# ではこれを回避することはできませんが、C では問題なくコンパイルされ、デバッグが非常に困難になる可能性があります。

于 2008-10-02T11:56:01.550 に答える
5

2 つのバッファを持つ理由はありません。入力行をその場でトリミングできます。

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;
于 2008-10-02T12:06:31.930 に答える
1

個人的に while 構造の場合:

私は次のことを好みます:

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

に:

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

どちらも != 0 に対してチェックしますが、最初のほうが少しきれいに見えます。char が 0 以外の場合、ループ本体が実行されます。それ以外の場合は、ループから抜け出します。

また、「for」ステートメントについては、構文的に有効ですが、次のことがわかります。

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

私には「奇妙」に見え、実際、潜在的なバグに対する潜在的な悪夢のような解決策です。このコードを見直していたら、赤く光る警告のようなものでしょう。通常、既知の回数反復するために for ループを使用するか、それ以外の場合は while ループを使用します。(いつものように、ルールには例外がありますが、これは一般的に当てはまることがわかりました)。上記の for ステートメントは次のようになります。

while (i)
{
        if (ret[i] == ' ' || ret[i] == '\t')
        {
            ret[i--] = '\0';
        }
        else if (ret[i] != '\0' && ret[i] != '\r' && ret[i] != '\n')
        {
            break;
        }
}
于 2008-10-02T12:19:00.340 に答える
1

トリム()が大きすぎます。

私が必要だと思うのは、strlen っぽい関数です (先に進んで、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 を返します。

これらの観点からトリムを書くことができるはずです。

于 2008-10-02T12:39:36.483 に答える
0
  1. トリムは実際には1つのバッファのみを使用する必要があります(@Ferruccioが言うように)。
  2. @plinthが言うように、トリムを分割する必要があります
  3. トリムは値を返す必要はありません (空の文字列をチェックする場合は、line[0] == 0 をテストします)
  4. 追加の C フレーバーについては、インデックスではなくポインターを使用します

- 行末に移動 (終了 0; - 行頭ではなく、現在の文字が空白の場合は、0 に置き換えます。 - 1 文字戻します。

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;
  }
}
于 2008-10-02T13:42:40.623 に答える
0

同じことを行う別の例。C99 固有のものを使用して、軽微な違反を行いました。K&Rにはありません。また、標準ライブラリの一部である assert() 関数を使用しましたが、おそらく K&R の第 1 章ではカバーされていません。

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

}
于 2008-10-02T19:03:06.343 に答える
0

初めに:

int main(void)

main() のパラメーターはわかっています。彼らは何もありません。(または argc&argv ですが、それは第 1 章の資料ではないと思います。)

スタイルに関しては、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-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;
        }
    }
}

(また、コメントを追加し、1 つのバグを修正しました。)

大きな問題は、MAXLINE 定数の使用です。main() は、これをline変数とout変数に排他的に使用します。それらにのみ作用するトリム()は、定数を使用する必要はありません。getline() で行ったのと同じように、サイズをパラメーターとして渡す必要があります。

于 2008-10-02T12:26:23.380 に答える
0

これは、第 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;
}
于 2008-10-02T13:21:41.220 に答える
0

個人的には、次のようなコードを配置します。

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

別の関数 (または定義マクロ) に

于 2008-10-02T13:29:30.123 に答える