Упражнение K & R:Мой код работает, Но кажется Вонючим;Совет по уборке?

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

  •  03-07-2019
  •  | 
  •  

Вопрос

Я работаю над книгой о K & R.Я читал дальше вперед, чем выполнял упражнения, в основном из-за нехватки времени.Я наверстываю упущенное и выполнил почти все упражнения из главы 1, которая является учебным пособием.

Моей проблемой было упражнение 1-18.Упражнение заключается в том, чтобы:

Напишите программу для удаления завершающих пробелов и табуляций из строки ввода, а также для удаления полностью пустых строк

Мой код (ниже) делает это и работает.Моя проблема с этим заключается в методе trim, который я реализовал.Это чувствуется ...неправильно ...каким-то образом.Например, если бы я увидел подобный код на C # в обзоре кода, я бы, наверное, сошел с ума.(C # - одна из моих специальностей.)

Кто-нибудь может предложить несколько советов по очистке этого - с той оговоркой, что указанный совет должен использовать только знания из главы 1 K & 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 и, в частности, еще не добрался до указателей.(Помните немного о главе 1 K & R - в главе 1 нет указателей.) Я "вроде" понимаю некоторые из этих решений, но они все еще немного продвинуты для того места, где я нахожусь...

И большая часть того, что я ищу, - это сам метод trim, в частности тот факт, что я перебираю 3 времена (которые кажутся такими грязными).Я чувствую, что если бы я был чуть-чуть умнее (даже без продвинутых знаний языка Си), это могло бы быть чище.

Это было полезно?

Решение

Нет причин иметь два буфера, вы можете обрезать строку ввода на месте

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

Редактировать:Вы можете сделать цикл while еще проще, предполагая кодировку ASCII.

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

Другие советы

Если вы придерживаетесь главы 1, то, по-моему, это выглядит довольно неплохо.Вот что я бы порекомендовал с точки зрения проверки кода:

При проверке равенства в C всегда ставьте константу первой

if (1 == myvar)

Таким образом, вы никогда случайно не сделаете что-то подобное:

if (myvar = 1)

Вам это не сойдет с рук в C #, но на C это прекрасно компилируется и может быть настоящим дьяволом в отладке.

функция trim() слишком велика.

Что, я думаю, вам нужно, так это strlen-иш функция (продолжайте и напишите ее int stringlength(const char * s)).

Затем вам нужна функция с именем int scanback(const char * s, const char *matches, int start), которая начинается с start, опускается до z до тех пор, пока сканируемый символ по идентификатору s, содержащемуся в matches, возвращает последний индекс, в котором найдено совпадение.

Затем вам нужна функция с именем int scanfront (const char * s, const char * matches), которая начинается с 0 и выполняет сканирование вперед до тех пор, пока символ, сканируемый в s, содержится в matches, возвращая последний индекс, в котором найдено совпадение.

Затем вам нужна функция с именем int charinstring(char c, const char * s), которая возвращает ненулевое значение, если c содержится в s, 0 в противном случае.

Вы должны быть в состоянии написать trim в терминах этого.

Лично для 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;
        }
}

Прежде всего:

int main(пустота)

Вы знаете параметры для 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;
        }
    }
}

(Также добавлены комментарии и исправлена одна ошибка.)

Большой проблемой является использование константы MAXLINE - main() использует ее исключительно для линия и вон переменные;trim(), которая работает только с ними, не нуждается в использовании константы.Вы должны передать размер (ы) в качестве параметра точно так же, как вы это сделали в getline().

Лично я бы разместил код следующим образом:

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

в отдельную функцию (или даже в определяющий макрос)

  1. trim действительно должен использовать только 1 буфер (как говорит @Ferruccio).
  2. отделку нужно разбить, как говорит @plinth
  3. trim не должен возвращать никакого значения (если вы хотите проверить наличие пустой строки, проверьте строку [0] == 0)
  4. для придания дополнительного вкуса C используйте указатели, а не индексы

-перейти к концу строки (заканчивается 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 & R.также использовалась функция assert(), которая является частью библиотеки starndard, но, вероятно, не описана в первой главе K & 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 & 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