K & amp; Ejercicio R: Mi código funciona, pero se siente apestoso; Consejos para la limpieza?

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

  •  03-07-2019
  •  | 
  •  

Pregunta

Estoy trabajando en el libro K & amp; R. He leído más adelante de lo que he hecho los ejercicios, sobre todo por falta de tiempo. Me estoy poniendo al día y he hecho casi todos los ejercicios del capítulo 1, que es el tutorial.

Mi problema fue el ejercicio 1-18. El ejercicio es:

  

Escriba un programa para eliminar los espacios en blanco y   pestañas de la línea de entrada, y para eliminar líneas totalmente en blanco

Mi código (abajo) hace eso, y funciona. Mi problema con esto es el método de recorte que implementé. Se siente ... mal ... de alguna manera. Como si viera un código similar en C # en una revisión de código, probablemente me volvería loco. (C # es una de mis especialidades.)

¿Alguien puede ofrecer algún consejo para limpiar esto? Con el problema de que dicho consejo solo tiene que usar el conocimiento del Capítulo 1 de K & amp; R. (Sé que hay mil maneras de limpiar esto utilizando la biblioteca completa de C; simplemente estamos hablando del Capítulo 1 y del stdio.h básico aquí). Además, al dar el consejo, ¿puede explicar por qué ayudará? (¡Después de todo, estoy tratando de aprender! ¿Y de quién es mejor para aprender que los expertos aquí?)

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

EDITAR: Aprecio todos los consejos útiles que estoy viendo aquí. Me gustaría recordarle a la gente que todavía soy un n00b con C, y específicamente no me he incorporado a los punteros todavía. (Recuerda que el bit sobre Ch.1 de K & amp; R - Ch.1 no hace punteros). I " kinda " Obtén algunas de esas soluciones, pero aún son un toque avanzado para donde estoy ...

Y la mayor parte de lo que busco es el método de recorte en sí mismo, específicamente el hecho de que estoy haciendo un bucle en 3 veces (lo que se siente tan sucio). Siento que si solo fuera un toque más inteligente (incluso sin el conocimiento avanzado de C), esto podría haber sido más limpio.

¿Fue útil?

Solución

No hay ninguna razón para tener dos buffers, puede recortar la línea de entrada en su lugar

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

Al devolver la longitud de la línea, puede eliminar las líneas en blanco al probar las líneas de longitud diferente a cero

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

EDIT: puede hacer que el bucle while sea aún más simple, asumiendo la codificación ASCII.

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

Otros consejos

Si sigues con el capítulo 1, me parece muy bien. Esto es lo que recomendaría desde un punto de vista de revisión de código:

Al verificar la igualdad en C, siempre coloque la constante primero

if (1 == myvar)

De esa manera nunca harás algo así como accidentalmente:

if (myvar = 1)

No puede salirse con la suya en C #, pero se compila bien en C y puede ser un verdadero diablo para depurar.

trim () es demasiado grande.

Lo que creo que necesitas es una función strlen-ish (adelante y escríbala int stringlength (const char * s)).

Luego necesita una función llamada int scanback (const char * s, const char * matches, int start) que comienza en el inicio, se reduce a z siempre que el carácter que se esté escaneando en s id esté contenido en match, devuelva el último índice donde se encuentra una coincidencia.

Luego necesita una función llamada int scanfront (const char * s, const char * matches) que comienza en 0 y escanea hacia adelante siempre que el carácter que se está escaneando en s esté incluido en las coincidencias, devolviendo el último índice donde se encuentra una coincidencia. se encuentra.

Entonces necesitas una función llamada int charinstring (char c, const char * s) que devuelve un valor distinto de cero si c está contenido en s, 0 en caso contrario.

Debería poder escribir recortes en términos de estos.

Personalmente para mientras construye:

Prefiero lo siguiente:

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

a:

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

Ambos verifican contra! = 0, pero el primero se ve un poco más limpio. Si el char es algo distinto a 0, entonces el cuerpo del bucle se ejecutará, de lo contrario se romperá fuera del bucle.

También para las declaraciones 'for', aunque son sintácticamente válidas, encuentro lo siguiente:

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

me parece "extraño" y, de hecho, es una posible solución de pesadilla para posibles errores. Si estuviera revisando este código, sería como una advertencia roja brillante como. Por lo general, desea utilizar los bucles para iterar un número conocido de veces, de lo contrario, considere un bucle while. (como siempre hay excepciones a la regla, pero he encontrado que esto generalmente es cierto). Lo anterior por declaración podría convertirse en:

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

En primer lugar:

  

int main (void)

Usted conoce los parámetros de main (). No son nada (O argc & amp; argv, pero no creo que sea el material del Capítulo 1).

Stylewise, es posible que desee probar los soportes K & amp; R-style. Son mucho más fáciles en el espacio vertical:

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

(También se agregaron comentarios y se corrigió un error).

Un gran problema es el uso de la constante MAXLINE - main () lo usa exclusivamente para las variables line y out ; trim (), que solo está trabajando en ellos, no necesita usar la constante. Debe pasar el tamaño (s) como parámetro tal como lo hizo en getline ().

Personalmente pondría un código como este:

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

en una función separada (o incluso una macro definida)

  1. el ajuste debe usar 1 búfer solamente (como dice @Ferruccio).
  2. se debe dividir el ajuste, como dice @plinth
  3. el ajuste no necesita devolver ningún valor (si desea verificar si hay una cadena vacía, pruebe la línea [0] == 0)
  4. para un sabor C adicional, use punteros en lugar de índices

-go hasta el final de la línea (terminando 0; Si bien no al comienzo de la línea y el carácter actual es espacio, reemplácelo con 0. -después de una charla

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

Otro ejemplo de hacer lo mismo. Hizo alguna infracción menor usando cosas específicas de C99. que no se encontrará en K & amp; R. También usé la función assert () que forma parte de la biblioteca starndard, pero probablemente no esté cubierta en el capítulo uno de 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;

}

Aquí está mi intento por el ejercicio sin saber qué hay en el Capítulo 1 o K & amp; R. ¿Asumo punteros?

#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;
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top