Как читать файл построчно в C?

У меня есть текстовый файл до 100 IP-адресов, по 1 на строку. Мне нужно прочитать каждый адрес, как строку, в массив под названием "список". Во-первых, я предполагаю, что "список" должен быть двумерным массивом символов. Каждый IP-адрес имеет длину 11 символов, 12, если вы включаете "\0", поэтому я объявил список следующим образом:

char list[100][12];

Далее я пытаюсь использовать fgets для чтения потока:

  for (i = 0; i < 100; i++)  
  {  
      if (feof(stream))  
          break;  
          for (j = 0; j < 12; j++)  
          fgets(&list[i][j], 12, stream);  
      count++;  
  }

Чтобы проверить, правильно ли прочитаны строки, я пытаюсь вывести их:

  for (i = 0; i < 5; i++)  
  {  
      for (j = 0; j < 11; j++)  
          printf("%c", list[i][j]);  
      printf("\n");  
  }

После запуска программы понятно, что что-то не так. Будучи новичком, я не уверен, что, но я предполагаю, что я читаю файл неправильно. Там нет ошибок. Компилируется, но печатает странный адрес в две строки.

Редактировать:

Я заменил код fgets следующим:

for (i = 0; i < 100; i++)
  {
      if (feof(stream))
          break;
      fgets(list[i], 12, stream);
      count++;
  }

Теперь он печатает пять строк, но это "случайные" символы из памяти.

7 ответов

Решение

Во-первых, чтение:

      for (j = 0; j < 12; j++)  
      fgets(&list[i][j], 12, stream);  

У вас есть большая проблема прямо здесь. Это попытка прочитать строку в каждом последующем символе в вашем массиве.

В общем, я думаю, что вы делаете это намного сложнее, чем нужно. Думайте о вашем массиве как о 100 строках, и fgets будет работать со строкой одновременно. Это означает, что чтение может выглядеть примерно так:

for (i=0; i<100 && fgets(list[i], 11, string); i++)
    ;

Есть еще одна мелочь, с которой нужно разобраться: fgets() обычно сохраняет новую строку в конце каждой строки. Таким образом, вам может потребоваться оставить место для 13 символов (11 для адреса, 1 для новой строки, 1 для терминатора NUL), иначе вы можете захотеть прочитать данные во временный буфер и скопировать их в свой файл. list только после того, как вы сняли новую строку.

В вашем текущем коде для печати строк вы работаете по одному символу за раз, что может работать, но излишне сложно. Несколько человек предложили использовать% s printf, что само по себе хорошо. Однако для этого вам необходимо немного упростить индексирование. Печать первых шести адресов будет выглядеть примерно так:

for (i=0; i<6; i++)
    printf("%s", list[i]);

Ваш звонок в fgets читает до 11 символов из потока в массив. Таким образом, вы не хотите вызывать это один раз для каждого символа каждой строки.

Подумайте об этих циклах: при i=0 и j=0 он читает до 11 символов &list[0][0], Затем с i=0 и j=1, он читает еще 11 символов &list[0][1], Это неверно по двум причинам: он перезаписывает результат последнего вызова и потенциально записывает больше байтов, чем может удержать list[0].

Не использовать feof() как ваше состояние петли; он не вернет true до тех пор, пока вы не попытаетесь прочитать после конца файла, то есть ваш цикл будет выполняться слишком много раз. Проверьте результат вашего входного звонка (используете ли вы fgets() или же fscanf()), чтобы увидеть, если это удалось, затем проверьте feof() если вы получили ошибку

if (fgets(buffer, sizeof buffer, stream) != NULL)
{
  // process the input buffer
}
else if (feof(stream)
{
  // handle end of file
}
else
{
  // handle read error other than EOF
}

fgets() читает целые строки, а не отдельные символы, поэтому вы не хотите передавать адрес каждого отдельного символа в вашей строке. Назовите это так вместо этого:

if (fgets(list[i], sizeof list[i], stream) != NULL)
{
  // process input address
}

А теперь, для обычной болтовни Боде о массивах и указателях...

Когда выражение массива появляется в большинстве контекстов, тип выражения неявно преобразуется из "массива N-элемента T" в "указатель на T", а значением выражения является адрес первого элемента массива. Исключениями из этого правила являются случаи, когда выражение массива является операндом sizeof или же & операторы, или это строковый литерал, который используется в качестве инициализатора в объявлении. Когда вы слышите, как люди говорят "массивы и указатели - это одно и то же", они используют это правило. Массивы и указатели - это совершенно разные животные, но в некоторых случаях они могут использоваться взаимозаменяемо.

Обратите внимание, что в приведенном выше коде я прошел list[i] в качестве первого аргумента для fgets() без каких-либо украшений (таких как & оператор). Хотя тип list[i] это "массив из 12 элементов char", в этом контексте он неявно преобразуется в тип "pointer to char", а значением будет адрес list[i][0], Обратите внимание, что я также передал это же выражение sizeof оператор. В этом случае тип выражения массива не преобразуется в тип указателя, а оператор sizeof возвращает количество байтов в типе массива (12).

Просто чтобы прибить это:

Тип выражения Неявно преобразуется в
----------      ----             ----
list            char [100][12]   char (*)[12] (указатель на массив из 12 элементов char)
list[i]         char [12]        char *
list[i][j]      char             N/A

Что все это означает, что fgets() будет читать до следующих 12 символов (при условии, что он не попадет на новую строку или EOF) и сохранит его, начиная с list[i][0], Обратите внимание, что fgets() запишет завершающий нулевой символ (0) в конец вашей строки. Обратите внимание, что если fgets() встречает символ новой строки и в целевом массиве есть место для него и завершающего nul, fgets() будет хранить завершающий символ новой строки перед нулевым символом. Так что, если ваш входной файл имеет строку вроде

1.1.1.1\n

тогда содержимое вашего входного буфера после чтения будет "1.1.1.1\n\0xxx" где x это какое-то случайное значение. Если вы не хотите, чтобы новая строка была там, вы можете использовать strchr() Функция, чтобы найти его, а затем переписать его с 0:

char *newline;
...
if ((newline = strchr(input[i], '\n')) != NULL)
{
  *newline = 0;
}

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

Собираем все вместе:

char list[100][13];
...
for (i = 0; i < 100; ++)
{
  if (fgets(list[i], sizeof list[i], stream) != NULL)
  {
    char *newline = strchr(list[i], '\n');
    if (newline != NULL)
      *newline = 0;
    printf("Read address \"%s\"\n", list[i]);
    count++;
  }
  else if (feof(stream))
  {
    printf("Reached end of file\n");
    break;
  }
  else
  {
    printf("Read error on input; aborting read loop\n");
    break;
  }
}

Второй цикл не нужен и он портит вашу память. Вы должны сделать что-то вроде этого,

for (i = 0; i < 100; i++)
{
if (feof(stream))
break;
fgets(&list[i][j], 12, stream);
count++;
}

To check to see if the strings were read properly, I attempt to output them:

for (i = 0; i < 5; i++)
{
printf("%s\n", list[i]);
}

Символ новой строки заставляет fgets перестать читать, но он считается допустимым символом и поэтому включен в строку, скопированную в str.

Возможно, вы читаете первые 12 символов в первом вызове fgets, затем второй вызов поймает новую строку, затем третий вызов получит следующую строку.

Попробуйте использовать fgets с ограничением в 15 символов и расширить буфер.

Я написал функцию для чтения строк. Я думаю, что это должно быть безопасно.

Проверьте: io_readline

https://github.com/arhuaco/junkcode/blob/master/junk/misc/atail.c

Для (i = 0; i<100; i ++) {

   if (feof(fp))
       break;

   fscanf(fp,"%s\n",list[i]);

}

Другие вопросы по тегам