K & R Übung: Mein Code funktioniert, fühlt sich aber Stinky; Hinweise für die Cleanup?

StackOverflow https://stackoverflow.com/questions/161873

  •  03-07-2019
  •  | 
  •  

Frage

Ich arbeite an dem K & R Buch. Ich habe weiter voraus gelesen, als ich Übungen gemacht habe, vor allem aus Mangel an Zeit. Ich aufholen und haben fast alle Übungen aus Kapitel 1, die das Tutorial ist fertig.

Mein Problem war Übung 18.01. Die Übung ist:

  

Schreiben Sie ein Programm folgende Leerzeichen zu entfernen und   Registerkarten von Eingabezeile, und ganz leere Zeilen löschen

Mein Code (unten) das tut, und arbeitet. Mein Problem mit ihm ist die Trimmung Methode, die ich umgesetzt. Es fühlt sich an ... falsch ... irgendwie. Wie, wenn ich einen ähnlichen Code in C # in einem Code-Review sah, würde ich wahrscheinlich verrückt werden. (C # eine meiner Spezialitäten ist.)

Kann ich jemand einen Rat bietet diese auf Reinigung - mit dem Fang, dass der Rat nur über Wissen nutzen, von Kapitel 1 von K & R. (ich weiß, dass es eine Unmenge Möglichkeiten, dies zu bereinigen, die vollständige C-Bibliothek ; wir reden nur Kapitel 1 und Grund stdio.h hier) auch wenn die Ratschläge zu geben, können Sie erklären, warum es helfen.? (Ich bin, nachdem alle, versuchen zu lernen! Und wer besser aus als die Experten hier zu lernen?)

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

EDIT: Ich schätze all die nützlichen Tipps, die ich hier bin zu sehen. Ich möchte Leute daran zu erinnern, dass ich immer noch ein n00b mit C bin, und haben gesagt noch nicht auf Zeiger aufgestanden. (Denken Sie daran, die etwas über Kanal 1 von K & R - Kanal 1 keine Zeiger macht.) I „irgendwie“ einige dieser Lösungen bekommen, aber sie sind immer noch ein Hauch fortgeschritten für, wo ich bin bei ...

Und die meisten von dem, was ich suche ist die Trimmung Methode selbst - nämlich die Tatsache, dass ich Schleife durch 3 mal (was fühlt sich so schmutzig). Ich fühle mich wie wenn ich nur ein Hauch klüger (auch ohne fortgeschrittene Kenntnisse von C), das sauberer sein können.

War es hilfreich?

Lösung

Es gibt keinen Grund zwei Puffer zu haben, können Sie die Eingabezeile anstelle trimmen

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

Durch die Zeilenlänge zurückkehrt, können Sie Leerzeilen beseitigen, indem die Prüfung für Nicht-Null-Länge-Linien

if (trim(line) != 0)
    printf("%s\n", line);

EDIT:. Sie können die while-Schleife noch einfacher machen, ASCII-Kodierung unter der Annahme,

while (len > 0 && line[len-1] <= ' ')
    line[--len] = 0;

Andere Tipps

Wenn Sie mit Kapitel 1 kleben, das sieht mir ziemlich gut. Hier ist, was würde ich von einem Code-Review Standpunkt empfehlen:

Bei Gleichheit in C überprüft, immer setzen die konstante erste

if (1 == myvar)

Auf diese Weise werden Sie nie versehentlich etwas tun:

if (myvar = 1)

Sie können nicht mit dem in C # weg, aber es kompiliert in C in Ordnung und kann ein echter Teufel zu debuggen sein.

trim () ist zu groß.

Was ich glaube, Sie brauchen, ist eine strlen-ish-Funktion (gehen Sie vor und schreiben Sie es stringlength (const char * s) int).

Dann müssen Sie eine Funktion int Scanback genannt (const char * s, const char * Streichhölzer, int start), die beim Start beginnt, geht nach unten, so lange bis z wie das Zeichen an s-ID in Streichhölzer enthalten gescannt wird, kehrt die letzter Index, in dem eine Übereinstimmung gefunden wird.

Dann müssen Sie eine Funktion int Scanfront (const char * s, const char * matches) genannt, die bei 0 beginnt und tasten nach vorne, solange das Zeichen an s abgetastet wird, wird in Spielen enthält, den letzten Index zurückkehren, wo ein Spiel gefunden wird.

Dann müssen Sie eine Funktion namens int charinstring (char c, const char * s), die ungleich Null zurück, wenn c in s enthalten ist, ansonsten 0.

Es soll möglich sein, trim schreiben in Bezug auf diesen.

Persönlich für während Konstrukte:

Ich ziehe es folgende Möglichkeiten:

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

zu:

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

Sie beide Check gegen! = 0, aber der erste sieht ein wenig sauberer. Wenn die Zeichen etwas anderes thah 0 ist, dann wird der Schleifenkörper ausgeführt werden sonst wird es aus der Schleife brechen.

Auch für ‚für‘ Aussagen, während syntaktisch gültig ist, finde ich, dass die folgenden Möglichkeiten:

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

sieht nur ‚ungerade‘ mir und in der Tat ist eine potentielle Alptraum Lösung für potenzielle Fehler. Wenn ich diesen Code war die Überprüfung, würde es so wie eine glühende rote Warnung sein. Normalerweise wollen Sie für Schleifen verwenden, um eine bekannte Anzahl von Malen iteriert, sonst eine while-Schleife cosider. (Wie immer gibt es Ausnahmen von der Regel, aber Ive festgestellt, dass dies in der Regel gilt). Die oben für Erklärung könnte sein:

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

Zu allererst:

  

int main (void)

Sie kennen die Parameter main (). Sie sind nichts. (Oder argc & argv, aber ich glaube nicht, dass das Kapitel 1 Material.)

Stilistisch, möchten Sie vielleicht K & R-Stil Klammern versuchen. Sie sind viel leichter auf dem vertikalen Raum:

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

(auch hinzugefügt Kommentare und ein Fehler behoben.)

Ein großes Problem ist die Verwendung des MaxLine konstant - main () verwendet sie ausschließlich für die Zeile und aus Variablen; trim (), die nur auf sich arbeiten muss nicht die Konstante verwenden. Sie sollten die Größe (n) als Parameter übergeben wie Sie in getline tat ().

Persönlich würde ich Code so:

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

in eine separate Funktion (oder sogar ein definieren Makro)

  1. trim in der Tat einen Puffer nur verwenden sollte (wie @Ferruccio sagt).
  2. muss trim aufgebrochen werden, wie @plinth sagt
  3. trim braucht keinen Wert zurückgeben (wenn Sie für leere Zeichenfolge überprüfen möchten, Testlinie [0] == 0)
  4. für zusätzliche C Geschmack, Verwendung Zeiger anstatt Indizes

-go zum Ende der Zeile (Abschluss 0; -Während nicht am Anfang der Zeile und aktuellen Zeichen ist Raum, ersetzen Sie es mit 0. -BACK off ein Zeichen

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

Ein weiteres Beispiel für das gleiche tun. Hat einige kleinere Verletzung von C99-spezifischen Dinge verwenden. das wird nicht in K & R gefunden werden. auch verwendet, um die assert () Funktion, den Teil der starndard Bibliothek ist, werden aber wahrscheinlich nicht im ersten Kapitel von K & R abgedeckt.

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

}

Hier ist mein Stich an der Übung, ohne zu wissen, was in Kapitel 1 oder K & R. Ich gehe davon aus Zeigern?

#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;
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top