Попытка понять fgets()

Я пишу программу для чтения файла построчно. Я знаю, что fgets() будет читать до новой строки, n-1 символов или EOF. Что меня смущает, так это то, как fgets() узнает о переходе на новую строку/где она находится в файле?

Чтобы было понятнее, я знаю, что в Java вы должны явно указать сканеру, чтобы он прочитал следующую строку. Но в C я просто помещаю fgets() в цикл while и верю, что он выполняет свою работу.

Как я могу логически убедить себя, что fgets() на самом деле переходит к следующей строке, а не читает одну и ту же строку снова и снова в этом цикле while (да, я знаю, что могу распечатать)?


person darylnak    schedule 20.03.2017    source источник
comment
Учитывая, что fgets() принимает указатель на файл, он просто продвинет этот указатель по файлу сразу за следующей новой строкой. Если бы вы использовали какие-либо функции чтения в этом файле (не только fgets), они бы начинались там, где fgets() оставила указатель файла: сразу после новой строки.   -  person    schedule 20.03.2017
comment
Это то, что требуется сделать. Я не знаю, почему (и даже если) Java делает то, что вы утверждаете, но если бы C fgets() сделал то же самое, это сломало бы каждую программу C, которая его использует.   -  person Jonathan Leffler    schedule 20.03.2017
comment
не читать одну и ту же строку снова и снова в этом цикле while: в некотором смысле вы не можете. Вы просто доверяете людям, которые реализовали библиотечную функцию. Поэтому, если каким-то образом где-то у вас есть библиотека стилей 1-го апреля, ваш сценарий может произойти (и такие функции, как ftell, будут аналогичным образом испорчены). Но уж точно не положено.   -  person    schedule 20.03.2017
comment
@Everet А, кажется, я начинаю понимать. Итак, если бы я хотел перезапустить файл с начала, я бы использовал fclose(myFilePtr), а затем снова запустил цикл?   -  person darylnak    schedule 20.03.2017
comment
Закрытие и открытие файла — это один из способов перезагрузки, да. Вероятно, проще (и немного быстрее) использовать rewind или fseek. Прочитайте справочные страницы для fgets, rewind, fseek и т. д. Или посмотрите, можете ли вы найти достаточно подробное руководство по доступу к файлам/потокам на C. Глава C IO в Викиучебнике может быть хорошим началом.   -  person    schedule 20.03.2017
comment
В общем, попытайтесь думать о файле как о непрерывной серии байтов, которые вы перемещаете, читая их по пути. Если ваш файл короткий, вы можете нарисовать различные действия на листе бумаги и посмотреть, как все движется, включая fgets.   -  person    schedule 20.03.2017
comment
Есть одна вещь, которую следует отметить относительно fgets: если параметр size короче строки, fgets будет считываться до этой точки. Следующая итерация продолжится с этой точки. Он не пропустит оставшуюся часть строки и начнет со следующей строки. Поэтому, если вы хотите читать полные строки, всегда следите за тем, чтобы параметр размера (и входной буфер) был такой же длины, как и самая длинная строка, которую вы хотите прочитать (включая новую строку).   -  person    schedule 20.03.2017
comment
Код знает, где находится конец строки, читая байты из файла до тех пор, пока один из этих байтов не станет символом новой строки ('\n').   -  person M.M    schedule 20.03.2017


Ответы (1)


Семантика fgets() довольно прямолинейна: считывайте байты из указателя потока FILE* до тех пор, пока:

  • целевой массив заполнен (прочитано и сохранено n-1 байт).
  • новая строка была прочитана из потока и сохранена в целевом массиве.
  • достигнут конец файла или произошла ошибка чтения (EOF было возвращено вызовом fgetc() или эквивалентным). условия конца файла и ошибки чтения можно отличить, вызвав ferr() и/или feof() после того, как fgets() вернет NULL.

Нулевой терминатор всегда сохраняется в массиве после считывания байтов из потока, если только конец файла не был достигнут немедленно (fgets() возвращает NULL) или если размер буфера указан равным 0.

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

fgets() потребляет байты, считанные из потока, и сохраняет их в целевой массив. Он не может прочитать одни и те же байты снова, если вы явно не выполните поиск в обратном направлении в потоке с помощью rewind(), fseek() или fsetpos(), если поток вообще поддерживает поиск. Потоки, прикрепленные к обычным файлам, обычно поддерживают поиск, но файлы, открытые в текстовом режиме, требуют специальной обработки в некоторых системах, особенно в Microsoft Windows. Также обратите внимание, что байт, возвращенный в поток с помощью ungetc(), будет прочитан с помощью fgets() перед любыми байтами из фактического потока.

fgets() сохраняет символ новой строки в целевом массиве, если строка была достаточно короткой, чтобы поместиться в массиве перед нулевым ограничителем.

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

Если при возврате из fgets() целевой массив не заканчивается новой строкой, произошло одно из следующих событий:

  • текущая строка была слишком длинной, и из потока было прочитано ровно столько байтов, сколько уместилось в массиве.
  • прочитанная строка была последней в файле, и файл не заканчивается последовательностью новой строки.
  • из файла был прочитан нулевой байт.

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

person chqrlie    schedule 20.03.2017
comment
Доступны ли только обычные файлы? Или, что более важно, устанавливает ли Стандарт какие-либо гарантии относительно того, когда файл должен поддерживать запросы позиционирования? - person ad absurdum; 20.03.2017
comment
Хороший ответ. Примечания: fgets() может перечитать последний символ с помощью ungetc(). Этот ответ опускает обсуждение редких ошибок ввода, которые не нужны для первоначального понимания fgets(), кроме как знать, что они существуют. - person chux - Reinstate Monica; 20.03.2017
comment
@david_bowling: никаких гарантий. - person rici; 20.03.2017
comment
@chux: я также скрывал от случайного читателя кровавые подробности потенциальных проблем с широкоориентированными потоками, вызывающими ошибки кодирования ... по уважительной причине. - person chqrlie; 20.03.2017
comment
Это было все, что мне нужно было знать о fgets(). Спасибо! - person Shahin; 27.10.2020