В C, как я могу выдать ошибку, если входная строка слишком велика? - PullRequest
0 голосов
/ 14 апреля 2020

Я хочу прочитать список слов из файла, в котором по одному слову в строке. Слова должны иметь до 4 символов каждое. Как я могу выдать ошибку, если одна из строк длиннее этой?

Я попытался прочитать слова, используя fgets

char buf[5];
fgets(buf, 5, stdin);

и scanf

char buf[5];
scanf("%4s", &buf);

но в обоих случаях он разбивает длинные строки на более мелкие. Например, qwerasdf читается как два слова, qwer и asdf. Есть ли способ обнаружить, что он пытался прочитать длинную строку, содержащую более 4 символов, и вместо этого выдать ошибку?

Единственная альтернатива, которую я могу придумать, - это читать вводимые символы и заботиться о них. всего самого себя Но есть ли более простое решение с использованием функций из стандартной библиотеки?

Ответы [ 3 ]

2 голосов
/ 15 апреля 2020

Вы делаете отличный выбор, читая с fgets(), единственное правило, которое вы нарушаете, это не экономьте на размере буфера . Но, даже если вы это сделаете, вы можете правильно обрабатывать вещи с помощью fgets().

Когда вы читаете строку из файла, fgets() (или POSIX getline()) читает и включает '\n' как часть буфера они заполняют (если есть место). Если вы ожидаете до 4 символов, тогда размер буфера 5 слишком мал, чтобы вместить все ваши символы, символ с нулевым символом и символ '\n'. Ваша попытка прочитать 4-символьную строку ("cats") с 5-символьным буфером с fgets() приведет к buf удерживанию:

    +---+---+---+---+---+
    | c | a | t | s | \0|    -->   '\n' remains unread
    +---+---+---+---+---+

Вы также можете изящно справиться с этим ( но лучше не экономить на размере буфера ) Чтобы изящно справиться с проблемой, вам необходимо проверить:

  • , если '\n' - последний символ в буфере, полное чтение строки, обрезать '\n', перезаписывая символом с нулевым символом ;
  • в противном случае читать следующий символ;
    • если следующий символ равен '\n', тогда ОК, вы прочитали все символы и не осталось места для '\n', который вы только что прочитали и проверили - продолжайте читать следующую строку;
    • иначе, если следующий символ равен EOF, тогда вы читаете все символы в последней строке в файле с не-POSIX концом файла (без '\n' после последней строки данных), прерываете чтение l oop вы нашли EOF;
  • иначе дополнительные символы остаются непрочитанными в строке, читайте и отбрасывайте символы до тех пор, пока не будет найден следующий '\n' или EOF

Сложив эту логику c, вы можете сделать:

#include <stdio.h>
#include <string.h>

int main (void) {

    char buf[5];

    while (fgets (buf, 5, stdin)) {                 /* read each line */
        if (strchr (buf, '\n'))                     /* if '\n' found - line read */
            buf[strcspn (buf, "\n")] = 0;           /* nul-termiante at '\n' */
        else {  /* otherwise */
            int c = getchar();                      /* read next chars */
            if (c == '\n')                          /* if '\n', OK read next line */
                continue;
            else if (c == EOF)                      /* if EOF, OK, non-POSIX eof */
                break;
            fputs ("error: line too long - discarding remainder.\n", stderr);
            for (; c != '\n' && c != EOF; c = getchar()) {}
        }
    }
}

Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.

2 голосов
/ 14 апреля 2020

Вы можете проверить длину прочитанной строки, и поскольку fgets также читает символ новой строки, вы можете явно проверить '\ n' в качестве последнего входного символа.

char buf[6];
while (fgets(buf, sizeof(buf), stdin)) {
    if (strlen(buf) > 5
        || (strlen(buf) == 5 && buf[strlen(buf) - 1] != '\n')) {
        fprintf(stderr, "line too long\n");
        exit(EXIT_FAILURE);
    }
}    

Буфер должен состоять не менее шести символов: 4 входных символа + 1 символ новой строки + строка, заканчивающаяся байтом NUL.

1 голос
/ 14 апреля 2020

Здесь я сделал эту функцию для чтения файла char по char и возвращает только одну строку на вызов

, так что теперь вы можете читать ваш файл построчно, тип Line имеет массив символов value где мы храним строку и целые числа hasNextLine 1 или 0 (bool), которые сообщают вам, есть ли в файле другая строка или нет, это удобно, когда вы пересекаете файл oop строка за строкой.

#include <stdlib.h>
#include <stdio.h>

typedef struct {
  char *value;
  int hasNextLine;
} Line;

Line * getLine(FILE *file) {
  Line *line = (Line *)malloc(sizeof(Line));
  if(line == NULL) {
    return NULL;
  }
  line->value = NULL;
  line->hasNextLine = 1;
  int n = 0, c;
  while(1) {
    c = getc(file);
    char *tmpStr = (char *)realloc(line->value, n + 2);
    if(tmpStr == NULL) {
      line->hasNextLine = -1;
      return line;
    }
    line->value = tmpStr;
    if(c == EOF) {
      line->hasNextLine = 0;
      line->value[n] = '\0';
      return line;
    }
    if(c == '\n') {
      line->value[n] = '\0';
      return line;
    }
    line->value[n] = c;
    n++;
  }
  return line;
}

Использование:

// example reading one line

int main() {
  FILE *f = fopen("your_file.txt", "r");

  if(f == NULL) {
    printf("File not found!");
    return 1;
  }

  Line *l = getLine(f);

  if(l != NULL) {
    printf("%s\n", l->hasNextLine != -1 ? l->value :
      "Error: while getting the line");
    free(l->value);
    free(l);
  }

  fclose(f);
  return 0;
}
// example reading the whole file

int main() {
  FILE *f = fopen("your_file.txt", "r");

  if(f == NULL) {
    printf("File not found!");
    return 1;
  }

  Line *l;
  int hasNextLine;

  while(1) {
    l = getLine(f);
    if(l != NULL) {
      printf("%s\n", l->hasNextLine != -1 ? l->value :
        "Error: while getting the line");
      free(l->value);
      hasNextLine = l->hasNextLine;
      free(l);
    }
    if(hasNextLine <= 0) {
      break;
    }
  }

  fclose(f);
  return 0;
}

Вы можете сделать пользовательскую функцию для пользовательского ввода

char * sgetLine(char *msg) {
  printf("%s", msg);
  Line *l = getLine(stdin);
  char *strLine = NULL;
  if(l == NULL) {
    return NULL;
  }else {
    if(l->hasNextLine == -1) {
      free(l->value);
      free(l);
      return NULL;
    }
    strLine = l->value;
    free(l);
    return strLine;
  }
}

, так что теперь вы можете использовать один вызов функции распечатать вопрос и получить ответ (массив символов)

int main() {
  char *l = sgetLine("What is your name? ");
  if(l != NULL) {
    printf("%s\n", l);
  }
  free(l);
  return 0;
}
...