Ofstream c что это
Работаем с потоками на C++ ofstream и ifstream. В пример показана программка генерирующая HTML файл, с данными в таблице, которые мы введем в программу. Используем потоковый ввод/вывод.
Потоки для работы с файлами создаются как объекты следующих классов:
- ofstream — для вывода (записи) данных в файл;
- ifstream — для ввода (чтения) данных из файла;
- fstream — для чтения и для записи данных (двунаправленный обмен).
Чтобы использовать эти классы, в текст программы необходимо включить дополнительный заголовочный файл fstream.h. После этого в программе можно определять конкретные файловые потоки, соответствующих типов (объекты классов ofstream, ifstream, fstream), например, таким образом:
- ofstream outFile; // Выходной файловый поток.
- ifstream inFile; // Входной файловый поток.
- fstream ioFile; // Файловый поток для ввода и вывода.
Создание файлового потока (объекта соответствующего класса) связывает имя потока с выделяемым для него буфером и инициализирует переменные состояния потока. Так как перечисленные классы файловых потоков наследуют свойства класса ios, то и переменные состояния каждого файлового потока наследуются из этого базового класса. Так как файловые классы являются производными от классов ostream (класс ofstream), istream (класс ifstream), stream (класс fstream), то они поддерживают описанный в предыдущих шагах форматированный и бесформатный обмен с файлами. Однако прежде чем выполнить обмен, необходимо открыть соответствующий файл и связать его с файловым потоком.
Открытие файла в самом общем смысле означает процедуру, информирующую систему о тех действиях, которые предполагается выполнять с файлом. Существуют функции стандартной библиотеки языка С для открытия файлов fopen(), open(). Но работая с файловыми потоками библиотеки ввода-вывода языка С++, удобнее пользоваться компонентными функциями соответствующих классов.
Создав файловый поток, можно «присоединить» его к конкретному файлу с помощью компонентной функции open(). Функция open() унаследована каждым из файловых классов ofstream, ifsream, fstream от класса fstreambase. С ее помощью можно не только открыть файл, но и связать его с уже определенным потоком. Формат функции:
Автор: admin | 30.03.2008 | Камменты: 11
Комментарии
Все клево! но нельзя ли было переменные ближе к их использованию объявлять, а то путаница небольшая возникает
нельзя ли задавать в качастве пути/имени удаленный компьютер? например (“\\192.168.1.1\qwe\1.txt”)
А почему бы вместо ifsteam и ofstream просто не использовать fstream, так же вроде удобнее.
>>А почему бы вместо ifsteam и ofstream просто не использовать fstream, так же вроде удобнее.
А сли у тя прав на запись нет? Или файловая смонтирована только на чтение? Твоя попытка открыть такой файо тихо умрёт верув -1. + если ты открыл файл толко на запись то никакой хакер черз твою программу не сможет прочесть его содержимое а это многого стоит.
>> + если ты открыл файл толко на запись то никакой >>хакер черз твою программу не сможет прочесть его >>содержимое а это многого стоит.
ухахах а ещё инопланетяне существуют;)
Комментарий от Drakon4ik [ Октябрь 6, 2010, 06:54 ]
есть методы граббинга информации с открытых удалённым пользователем окон. ок
а можно читать посимвольно или кусками? а то взял прочитал файл с большим количеством пустых строк, так оказалось что нули и переводы строк этот >> полностью игнорирует. а мне именно нули были нужны.
| | получилось ввелось как |||||| , записало первый символ, а при чтении пробелов ничего не поменялось. читал как
char x = ‘r’;
somfile >> x;
someotherfile >while(80х25 не прочтется);
Ахахаххх. Нашли решение вместо пробелов использовать _. А что использовать для перевода строки, ахахахх. Ржака-аббасака.
О боже, видимо, мне не суждено стать программистом(
Ofstream c что это
На этом шаге мы рассмотрим организацию связи файлов с потоками .
- ofstream — для вывода (записи) данных в файл;
- ifstream — для ввода (чтения) данных из файла;
- fstream — для чтения и для записи данных (двунаправленный обмен).
Чтобы использовать эти классы, в текст программы необходимо включить дополнительный заголовочный файл fstream.h . После этого в программе можно определять конкретные файловые потоки, соответствующих типов (объекты классов ofstream, ifstream, fstream ), например, таким образом:
ofstream outFile; // Определяется выходной файловый поток. ifstream inFile; // Определяется входной файловый поток. fstream ioFile; // Определяется файловый поток для ввода и вывода.
Создание файлового потока (объекта соответствующего класса) связывает имя потока с выделяемым для него буфером и инициализирует переменные состояния потока. Так как перечисленные классы файловых потоков наследуют свойства класса ios , то и переменные состояния каждого файлового потока наследуются из этого базового класса. Так как файловые классы являются производными от классов ostream (класс ofstream ), istream (класс ifstream ), stream (класс fstream ), то они поддерживают описанный в предыдущих шагах форматированный и бесформатный обмен с файлами. Однако прежде чем выполнить обмен, необходимо открыть соответствующий файл и связать его с файловым потоком .
Открытие файла в самом общем смысле означает процедуру, информирующую систему о тех действиях, которые предполагается выполнять с файлом. Существуют функции стандартной библиотеки языка С для открытия файлов fopen() , open() . Но работая с файловыми потоками библиотеки ввода-вывода языка С++ , удобнее пользоваться компонентными функциями соответствующих классов.
Создав файловый поток, можно «присоединить» его к конкретному файлу с помощью компонентной функции open() . Функция open() унаследована каждым из файловых классов ofstream, ifsream, fstream от класса fstreambase . С ее помощью можно не только открыть файл, но и связать его с уже определенным потоком. Формат функции:
void open(const char *fileName, int mode = умалчиваемое_значение, int protection = умалчиваемое_значение);
Первый параметр — fileName — имя уже существующего или создаваемого заново файла. Это строка, определяющая полное или сокращенное имя файла в формате, регламентированном операционной системой. Второй параметр — mode (режим) — дизъюнкция флагов, определяющих режим работы с открываемым файлом (например, только запись или только чтение). Флаги определены следующим образом:
enum ios::open_mode < in = 0x01, // Открыть только для чтения. out = 0x02, // Открыть только для записи. ate = 0x04, // При открытии искать конец файла. арр = 0x08, // Дописывать данные в конец файла. trunc = 0x10, // Вместо существующего создать новый файл. nocreate = 0x20, // Не открывать новый файл (Для // несуществующего файла функция open выдаст ошибку). noreplace = 0x40, // Не открывать существующий файл. // (Для существующего выходного файла, не имеющего режимов ate // или арр, выдать ошибку). binary = 0x80 // Открыть для двоичного (не текстового) обмена. >;
Назначения флагов поясняют комментарии, однако надеяться, что именно такое действие на поток будет оказывать тот или иной флаг в конкретной реализации библиотеки ввода-вывода, нельзя. Даже сам автор языка С++ Б.Страуструп говорит о том, что смысл значений open_mode , скорее всего, будет зависеть от реализации. Например, различие между флагами ios::ate и ios::app проявляется весьма редко, и часто они действуют одинаково. Однако ниже в пояснениях к программе OOР19_3.СРР приведен пример использования флага ios:: арр в конструкторе класса ofstream , где использование ios::ate приведет к ошибке открытия файла. Умалчиваемое значение параметра mode зависит от типа потока, для которого вызывается функция open() .
Третий параметр — protection (защита) — определяет защиту и достаточно редко используется. Точнее, он устанавливается по умолчанию и умалчиваемое значение обычно устраивает программиста.
Как обычно вызов функции open() осуществляется с помощью уточненного имени
имя_объекта класса.вызов_принадлежащей_классу_функции
Итак, открытие и присоединение файла к конкретному файловому потоку обеспечивается таким вызовом функции open() :
имя_потока.open(имя_файла, режим, защита);
Здесь имя_потока — имя одного из объектов, принадлежащих классам ofstream, ifstream, fstream . Примеры вызовов для определенных выше потоков:
outFile.open("С:\\USER\\RESULT.DAT"); inFile.open("DATA.TXT"); ioFile.open("CHANGE.DAT",ios::out);
При открытии файлов с потоками класса ofstream второй параметр по умолчанию устанавливается равным ios::out , т.е. файл открывается только для вывода. Таким образом, файл C:\USER\RESULT.DAT после удачного выполнения функции open() будет при необходимости (если он не существовал ранее) создан, а затем открыт для вывода (записи) данных в текстовом режиме обмена и присоединен к потоку outFile . Теперь к потоку outFile может применяться, например, операция включения
Поток inFile класса ifstream в нашем примере присоединяется функцией open() к файлу с именем DATA.TXT . Этот файл открывается для чтения из него данных в текстовом режиме. Если файла с именем DATA.TXT не существует, то попытка вызвать функцию inFile.open() приведет к ошибке.
Для проверки удачности завершения функции open() используется перегруженная операция !. Если унарная операция ! применяется к потоку, то результат ненулевой при наличии ошибок. Если ошибок не было, то выражение !имя_потока имеет нулевое значение. Таким образом, можно проверить результат выполнения функции open():
. . . . if (!inFile) < cerr . . . .
Для потоков класса fstream второй аргумент функции ореn() должен быть задан явно, так как по умолчанию неясно, в каком направлении предполагается выполнять обмен с потоком. В примере файл CHANGE.DAT открывается для записи и связывается с потоком ioFile , который будет выходным потоком до тех пор, пока с помощью повторного открытия файла явно не изменится направление обмена с файлом или потоком. (Чтобы изменить режимы доступа к файлу, его нужно предварительно закрыть с помощью функции close() , унаследованной всеми тремя файловыми классами из базового класса fstreambase .)
В классе fstreambase , который служит основой для файловых классов, имеются и другие средства для открытия уже существующих файлов.
Если файл явно создан с помощью библиотечной функции "нижнего уровня" creat() , то для него определен дескриптор файла. Этот дескриптор можно использовать в качестве фактического параметра функции fstreambase::attach() . При вызове этой функции используется уточненное имя, содержащее название того потока, который предполагается присоединить к уже созданному файлу с известным дескриптором:
#include // Классы файловых потоков. #include // Константы режимов доступа к файлам. . . . . . char name[20]; // Вспомогательный массив. cin >> name; // Ввести имя создаваемого файла. int descrip = create(name,S_WRITE); // Создать файл. if (descrip == -1) < cout // Определение выходного файлового потока: ofstream fileOut; // Присоединение потока к файлу: fileOut.attach(descrip); if (!fileOut) < cerr . . . . .
В классах ifstream, ofstream, fstream определены конструкторы, позволяющие по-иному выполнять создание и открытие файлов. Типы конструкторов для потоков разных классов очень похожи: имя_класса(); создает поток, не присоединяя его ни к какому файлу; имя_класса (int fd); создает поток и присоединяет его к уже открытому файлу, дескриптор которого используется в качестве параметра fd ; имя_класса (int fd, char *buf, int); создает поток, присоединяя его к уже открытому файлу с дескриптором fd , и использует явно заданный буфер (параметр buf ); имя_класса (char *FileName, int mode, int = . ); создает поток, присоединяет его к файлу с заданным именем FileName , а при необходимости предварительно создает файл с таким именем.
Детали и особенности перечисленных конструкторов лучше изучать по документации конкретной библиотеки ввода-вывода.
Работая со средствами библиотечных классов ввода-вывода, чаще всего употребляют конструктор без параметров и конструктор, в котором явно задано имя файла. Примеры обращений к конструкторам без параметров:
ifstream fi; // Создает входной файловый поток fi. оstream fo; // Создает выходной файловый поток fo. fstream ff; // Создает файловый поток ввода-вывода ff.
После выполнения каждого из этих конструкторов файловый поток можно присоединить к конкретному файлу, используя уже упомянутую компонентную функцию open() :
void open(char *FileName, int режим, int защита);
fi.open("File1.txt",ios::in); // Поток fi соединен с файлом File1.txt. fi.close(); // Разорвана связь потока fi с файлом File1.txt. fi.open("File2.txt"); // Поток fi присоединен к файлу File2.txt. fо.open("NewFile"); // Поток fo присоединяется к файлу NewFile; // если такой файл отсутствует - он будет создан.
При обращении к конструктору с явным указанием в параметре имени файла остальные параметры можно не указывать, они выбираются по умолчанию.
Примеры: ifstream flow1("File.1"); создает входной файловый поток с именем flow1 для чтения данных. Разыскивается файл с названием File.1 . Если такой файл не существует, то конструктор завершает работу аварийно. Проверка:
if (!flow1) cerrofstream flow2("File.2"); создается выходной файловый поток с именем flow2 для записи информации. Если файл с названием File.2 не существует, он будет создан, открыт и соединен с потоком flow2 . Если файл уже существует, то предыдущий вариант будет удален и пустой файл создается заново. Проверка:
if (!flow2) cerrfstream flow3("File.3"); создается файловый поток flow3 , открывается файл File.3 и присоединяется к потоку flow3 .
На следующем шага мы закончим рассматривать работу с файлами .
Ofstream c что это
БлогNot. Лекции по C/C++: работа с файлами (fstream)
Лекции по C/C++: работа с файлами (fstream)
Механизм ввода-вывода, разработанный для обычного языка С, не соответствует общепринятому сегодня стилю объектно-ориентированного программирования, кроме того, он активно использует операции с указателями, считающиеся потенциально небезопасными в современных защищённых средах выполнения кода. Альтернативой при разработке прикладных приложений является механизм стандартных классов ввода-вывода, предоставляемый стандартом языка C++.
Открытие файлов
Наиболее часто применяются классы ifstream для чтения, ofstream для записи и fstream для модификации файлов.
Все поточные классы ввода-вывода являются косвенными производными от общего предка ios , полностью наследуя его функциональность. Так, режим открытия файлов задает член данных перечисляемого типа open_mode, который определяется следующим образом:
enum open_mode < app, binary, in, out, trunc, ate >;Ниже приведены возможные значения флагов и их назначение.
Режим | Назначение |
in | Открыть для ввода (выбирается по умолчанию для ifstream) |
out | Открыть для вывода (выбирается по умолчанию для ofstream) |
binary | Открыть файл в бинарном виде |
aрр | Присоединять данные; запись в конец файла |
ate | Установить файловый указатель на конец файла |
trunc | Уничтожить содержимое, если файл существует (выбирается по умолчанию, если флаг out указан, а флаги ate и арр — нет) |
Например, чтобы открыть файл с именем test.txt для чтения данных в бинарном виде, следует написать:
ifstream file; file.open ("test.txt", ios::in | ios::binary);
Оператор логического ИЛИ ( | ) позволяет составить режим с любым сочетанием флагов. Так, чтобы, открывая файл по записи, случайно не затереть существующий файл с тем же именем, надо использовать следующую форму:
ofstream file; file.open ("test.txt", ios::out | ios::app);
Предполагается, что к проекту подключён соответствующий заголовочный файл:
#include
Для проверки того удалось ли открыть файл, можно применять конструкцию
if (!file) < //Обработка ошибки открытия файла >
Операторы включения и извлечения
fileМожно также записывать текстовую строку по частям:
fileОператор endl завершает ввод строки символом "возврат каретки":
fileС помощью оператора включения несложно записывать в файл значения переменных или элементов массива:
ofstream file ("Temp.txt"); char buff[] = "Текстовый массив содержит переменные"; int vx = 100; float pi = 3.14159; fileВ результате выполнения кода образуется три строки текстового файла Temp.txt :
Текстовый массив содержит переменные 100 3.14159Обратите внимание, что числовые значения записываются в файл в виде текстовых строк, а не двоичных значений.
Оператор извлечения ( >> )производит обратные действия. Казалось бы, чтобы извлечь символы из файла Temp.txt , записанного ранее, нужно написать код наподобие следующего:
ifstream file ("Temp.txt"); char buff[100]; int vx; float pi; file >> buff >> vx >> pi;Однако оператор извлечения остановится на первом попавшемся разделителе (символе пробела, табуляции или новой строки). Таким образом, при разборе предложения "Текстовый массив содержит переменные" только слово "Текстовый" запишется в массив buff , пробел игнорируется, а слово "массив" станет значением целой переменной vx и исполнение кода "пойдет вразнос" с неминуемым нарушением структуры данных. Далее, при обсуждении класса ifstream , будет показано, как правильно организовать чтение файла из предыдущего примера.
Класс ifstream: чтение файлов
Как следует из расшифровки названия, класс ifstream предназначен для ввода файлового потока. Далее перечислены основные методы класса. Большая часть из них унаследована от класса istream и перегружена с расширением родительской функциональности. К примеру, функция get , в зависимости от параметра вызова, способна считывать не только одиночный символ, но и символьный блок.
Метод | Описание |
open | Открывает файл для чтения |
get | Читает один или более символов из файла |
getline | Читает символьную строку из текстового файла или данные из бинарного файла до определенного ограничителя |
read | Считывает заданное число байт из файла в память |
eof | Возвращает ненулевое значение (true), когда указатель потока достигает конца файла |
peek | Выдает очередной символ потока, но не выбирает его |
seekg | Перемещает указатель позиционирования файла в заданное положение |
tellg | Возвращает текущее значение указателя позиционирования файла |
close | Закрывает файл |
Теперь понятно, как нужно модифицировать предыдущий пример, чтобы использование оператора извлечения данных давало ожидаемый результат:
ifstream file("Temp.txt"); char buff[100]; int vx; float pi; file.getline(buff, sizeof(buff)); file >> vx >> pi:
Метод getline прочитает первую строку файла до конца, а оператор >> присвоит значения переменным.
Следующий пример показывает добавление данных в текстовый файл с последующим чтением всего файла. Цикл while (1) используется вместо while(!file2.eof()) по причинам, которые обсуждались в предыдущей лекции.
#include #include using namespace std; int main() < ofstream file; file.open("test.txt",ios::out|ios::app); if (!file) < cout for (int i=0; i int a,k=0; while (1) < file2 >> a; if (file2.eof()) break; cout cout
Этот код под ОС Windows также зависит от наличия в последней строке файла символа перевода строки, надежнее было бы сделать так:
while (1)
Явные вызовы методов open и close не обязательны. Действительно, вызов конструктора с аргументом позволяет сразу же, в момент создания поточного объекта file , открыть файл:
ifstream file("test.txt");
Вместо метода close можно использовать оператор delete , который автоматически вызовет деструктор объекта file и закроет файл. Код цикла while обеспечивает надлежащую проверку признака конца файла.
Класс ofstream: запись файлов
Класс ofstream предназначен для вывода данных из файлового потока. Далее перечислены основные методы данного класса.
Метод | Описание |
open | Открывает файл для записи |
put | Записывает одиночный символ в файл |
write | Записывает заданное число байт из памяти в файл |
seekp | Перемещает указатель позиционирования в указанное положение |
tellp | Возвращает текущее значение указателя позиционирования файла |
close | Закрывает файл |
Описанный ранее оператор включения удобен для организации записи в текстовый файл:
ofstream file ("temp.txt"); if (!file) return; for (int i=1; iБинарные файлы
В принципе, бинарные данные обслуживаются наподобие текстовых. Отличие состоит в том, что если бинарные данные записываются в определенной логической структуре, то они должны считываться из файла в переменную того же структурного типа.
Первый параметр методов write и read (адрес блока записи/чтения) должен иметь тип символьного указателя char * , поэтому необходимо произвести явное преобразование типа адреса структуры void * . Второй параметр указывает, что бинарные блоки файла имеют постоянный размер байтов независимо от фактической длины записи. Следующее приложение дает пример создания и отображения данных простейшей записной книжки. Затем записи файла последовательно считываются и отображаются на консоли.
#define _CRT_SECURE_NO_WARNINGS #include #include #include using namespace std; struct Notes < // структура данных записной книжки char Name[60]; // Ф.И.О. char Phone[16]; // телефон int Age; // возраст >; int main() < setlocale(LC_ALL, "Russian"); Notes Note1= < "Грозный Иоанн Васильевич", "не установлен", 60 >; Notes Note2= < "Годунов Борис Федорович ", "095-111-2233 ", 30 >; Notes Note3= < "Романов Петр Михайлович ", "812-333-2211 ", 20 >; ofstream ofile("Notebook.dat", ios::binary); ofile.write((char*)&Note1, sizeof(Notes)); // 1-й блок ofile.write((char*)&Note2, sizeof(Notes)); // 2-й блок ofile.write((char*)&Note3, sizeof(Notes)); // 3-й блок ofile.close(); // закрыть записанный файл ifstream ifile("Notebook.dat", ios::binary); Notes Note; // структурированная переменная char str[80]; // статический буфер строки // Считывать и отображать строки в цикле, пока не eof while (!ifile.read((char*)&Note, sizeof(Notes)).eof()) < sprintf(str, "%s\tТел: %s\tВозраст: %d", Note.Name, Note.Phone, Note.Age); cout ifile.close(); // закрыть прочитанный файл cin.sync(); cin.get(); return 0; >P.S. При выполнении этого и других листингов в Visual Studio последних версий может дополнительно понадобиться подключение директивы _CRT_SECURE_NO_WARNINGS.
В результате выполнения этого кода образуется бинарный файл Notebook.dat из трех блоков размером по 80 байт каждый (при условии, что символы - однобайтовые). Естественно, вы можете использовать другие поточные методы и проделывать любые операции над полями определенной структуры данных.
Класс fstream: произвольный доступ к файлу
Предположим что в нашей записной книжке накопилось 100 записей, а мы хотим считать 50-ю. Конечно, можно организовать цикл и прочитать все записи с первой по заданную. Очевидно, что более целенаправленное решение - установить указатель позиционирования файла pos прямо на запись 50 и считать ее:
ifstream ifile("Notebook.dat", ios::binary); int pos = 49 * sizeof(Notes); ifile.seekg(pos); // поиск 50-й записи Notes Note; //Notes – описанная выше структура "запись" ifile.read((char*)&Note, sizeof(Notes));Подобные операции поиска эффективны, если файл состоит из записей известного и постоянного размера. Чтобы заменить содержимое произвольной записи, надо открыть поток вывода в режиме модификации:
ofstream ofilе ("Notebook.dat", ios::binary | ios::ate); int pos = 49 * sizeof(Notes); ofile.seekp(pos); // поиск 50-й записи Notes Note50 = ; ofile.write((char*)&Note, sizeof(Notes)); // заменаЕсли не указать флаг ios::ate (или ios::app ), то при открытии бинарного файла Notebook.dat его предыдущее содержимое будет стерто!
Дополнительно может понадобиться указать, откуда отсчитывается смещение.
f2.seekp(0, ios_base::beg); //ноль байт от начала файлаНаконец, можно открыть файл одновременно для чтения/записи, используя методы, унаследованные поточным классом fstream от своих предшественников. Поскольку класс fstream произведен от istream и ostream (родителей ifstream и ofstream соответственно), все упомянутые ранее методы становятся доступными в приложении.
В следующем примере показана перестановка первой и третьей записей файла Notebook.dat .
#include #include #include using namespace std; struct Notes < char Name[60]; char Phone[16]; int Age; >; int main() < setlocale(LC_ALL, "Russian"); Notes Note1, Note3; // Открыть файл на чтение/запись одновременно fstream file("Notebook.dat", ios::binary | ios::in | ios::out); file.seekg(2 * sizeof(Notes)); // найти и считать Note3 file.read((char*)&Note3, sizeof(Notes)); file.seekg(0); // найти и считать Note1 file.read((char*)&Note1, sizeof(Notes)); file.seekg(0); // Note1 file.close(); cin.sync(); cin.get(); return 0; >В конструкторе объекта file надо указать флаги ios::in и ios::out , разрешая одновременное выполнение операций чтения и записи. В результате выполнения этого кода первая и третья записи бинарного файла Notebook.dat поменяются местами.
Дополнительные примеры по теме есть в этой заметке.
05.11.2015, 09:45 [187490 просмотров]
Файловый ввод-вывод в C++
В языке C++ ввод-вывод осуществляется через объекты классов ifstream (для чтения данных) и ofstream (для вывода данных). Эти классы объявлены в заголовочном файле fstream .
При создании этих объектов нужно передать в конструктор один параметр - имя файла.
После этого с этими объектами можно работать точно так же, как с объектами cin и сout .
После окончания работы с файлами, файлы нужно "закрыть" эти объекты, вызвав для них метод close() .
Приведем пример программы, которая считывает два числа из файла с именем input.txt и выводит результат в файл output.txt .
#include
using namespace std;int main()
ifstream fin("input.txt");
ofstream fout("output.txt");
int a, b;
fin >> a >> b;
fout fin.close();
fout.close();
>Аналогично для считывания строки до символа конца строки из файла можно использовать функцию getline с двумя параметрами - файловый объект, из которого читаются данные и строка, куда записывается результат. Например:
Для проверки состояния файла можно использовать метод файла eof() . Он возвращает значение true или false в зависимости от того, был ли обнаружен конец файла при чтении:
Но при использовании этого метода могут возникнуть затруднения, например, с тем, что если после последнего числа в файле стоит символ конца строки, то состояние "достигнут конец файла" произойдет не после чтения последнего числа, а после следующего чтения.
Более надежный (и простой!) способ считать последовательность чисел из файла до конца файла -- использование значения, возвращаемого при считывании: