Skip to main content
1 vote

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

  1. #include "stdafx.h"
  2. #include <iostream>
  3. #include <fstream>
  4. using namespace std;
  5. int _tmain(int argc, _TCHAR* argv[])
  6. {
  7. ofstream txtfile;
  8. txtfile.open("hello.txt");
  9. txtfile << "Hello World!";
  10. txtfile.close();
  11. return 0;
  12. }

Эта программа, насколько вы, должно быть помните, записывает в текстовый файл hello world строку Hello World!

done.PNG

Как мы видим, для того, чтобы записать нечто в файл, необходимо сначала создать поток, открыть файл, что-то туда отправить, закрыть файл. Не сложно догадаться, что для того, чтобы это что-то из файла считать, порядок действий должен быть таким же. Продемонстрируем это на примере.

  1. #include "stdafx.h"
  2. #include <iostream>
  3. #include <fstream>
  4. #include <string>
  5.  
  6. using namespace std;
  7.  
  8. int _tmain(int argc, _TCHAR* argv[])
  9. {
  10. ifstream txtfile("D:\hello.txt");
  11. string str;
  12. txtfile >> str;
  13. cout << str;
  14. getchar();
  15. return 0;
  16. }

В прошлый раз мы использовали поток ofstream, но он не работает, если нам надо что-то считать из файла, поэтому мы использовали поток ifstream и сразу связали его с нашим файлом, в который что-то добавили. Что видим на экране:

1.PNG

Немного не то – почему-то считали только до пробела. Это произошло потому что мы не указали до какого момента считывать, а программа сама определить это не может. Очевидно, что надо считывать до тех пор, пока не будет достигнут конец файла. В этом нам поможет EOF. Модифицируем написанный код и посмотрим, что получится.

  1. #include "stdafx.h"
  2. #include <iostream>
  3. #include <fstream>
  4. #include <string>
  5.  
  6. using namespace std;
  7.  
  8. int _tmain(int argc, _TCHAR* argv[])
  9. {
  10. ifstream txtfile("D:\hello.txt");
  11. string str;
  12. while (!txtfile.eof())
  13. {
  14. txtfile >> str;
  15. cout << str;
  16. }
  17. getchar();
  18. return 0;
  19. }

Что мы сделали: процедуру считывания текста из файла занесли в цикл, условием выхода из которого является достижение конца этого файла. Говоря совсем простым языком, мы будем считывать файл до тех пор, пока он не кончится. Каков результат?

2.PNG

Лучше, но все равно ужасно.
Эврика! Строки-то не разделяем. Верно, давайте исправим этот беспредел.

  1. #include "stdafx.h"
  2. #include <iostream>
  3. #include <fstream>
  4. #include <string>
  5.  
  6. using namespace std;
  7.  
  8. int _tmain(int argc, _TCHAR* argv[])
  9. {
  10. ifstream txtfile("D:\hello.txt");
  11. string str;
  12. while (!txtfile.eof())
  13. {
  14. txtfile >> str;
  15. cout << str << endl;
  16. }
  17. getchar();
  18. return 0;
  19. }

3.PNG

Почему так произошло: когда мы пытались считать текст из файла в первый раз, у нас результатом было только слово some, то есть то, что идет до пробела. Потом мы добились того, что файл считывается до конца, но выводится на экран сплошным текстом, соответственно, программа считывает файл, видит пробел и завершает считывание, идет дальше и натыкается на условие, по которому надо читать дальше, ситуация повторяется. А тут мы после каждого вывода курсор переводим, получается, что каждое слово выводится на отдельной строке.
Как же нам выкрутиться из сложившейся ситуации. Поможет нам волшебный метод getline(). В качестве параметров необходимо указать имя переменной, связанной с файлом, и имя строки, в которую должно происходить считывание из файла. Посмотрим на переработанный код.

  1. #include "stdafx.h"
  2. #include <iostream>
  3. #include <fstream>
  4. #include <string>
  5.  
  6. using namespace std;
  7.  
  8. int _tmain(int argc, _TCHAR* argv[])
  9. {
  10. ifstream txtfile("D:\hello.txt");
  11. string str;
  12. while (getline(txtfile, str))
  13. {
  14. cout << str << endl;
  15. }
  16. getchar();
  17. return 0;
  18. }

Если у вас с английским более-менее взаимопонимание налажено, то метод getline можно перевести как «получить строку». То есть этот метод читает полностью строку со всеми пробелами и как только доходит до символа, строку завершающего, прекращает работу.

4.PNG

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

  1. #include "stdafx.h"
  2. #include <iostream>
  3. #include <fstream>
  4. #include <string>
  5.  
  6. using namespace std;
  7.  
  8. int _tmain(int argc, _TCHAR* argv[])
  9. {
  10. ofstream txtfile("D:\hello.txt");
  11. char mas[100];
  12. for (int i = 0; i < 100; i++)
  13. {
  14. mas[i] = i;
  15. txtfile.put(mas[i]);
  16. }
  17. getchar();
  18. return 0;
  19. }

Все знакомо, все делали поясню лишь то, что у нас происходит в массиве. Мы инициализируем каждый элемент массива и выводим так же посимвольно в файл (в скобках txtfile.put указываем символ, который требуется записать в данный момент). Сам файл выглядит примерно так:

5.PNG

Тут, я думаю, тоже все понятно, в целом на этом можно закончить, но еще один момент считаю необходимым рассказать – безопасность.
Все, что мы сегодня рассмотрели, будет работать при условии, что файл есть там, куда мы ссылаемся, что мы не напутали ничего с его именем, и что не возникло никаких ошибок чтения/записи. Если ошибка возникнет, мы не сможем работать должным образом с файлом. Поэтому немного модифицируем программу, считывающую данные из файла. Давайте сначала просто прикинем какие ошибки и в какой последовательности надо отлавливать.

1. Перед тем как работать с файлом, к нему надо обратиться. То есть в программе в первую очередь надо проверить открыт ли файл. Помните как мы раньше проверяли существование переменной, точнее, прошел ли процесс инициализации или нет? Тут можно поступить аналогичным образом.
2. Файл найден, мы его стараемся открыть, и тут-то нам нужно проверить, возникли ли ошибки при открытии. В этом нам поможет стандартный метод good. То есть, если при открытии файла не возникло ошибок, данный метод вернет нам единицу (true), в противном случае – 0 (false).

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

  1. #include "stdafx.h"
  2. #include <iostream>
  3. #include <fstream>
  4. #include <string>
  5.  
  6. using namespace std;
  7.  
  8. int _tmain(int argc, _TCHAR* argv[])
  9. {
  10. ifstream txtfile("D:\hello.txt");
  11. string str;
  12. if (txtfile)
  13. {
  14. if (txtfile.good())
  15. {
  16. while (getline(txtfile, str))
  17. {
  18. cout << str << endl;
  19. }
  20.  
  21. }
  22. else
  23. {
  24. cout << "can't open file" << endl;
  25. }
  26. }
  27. else
  28. {
  29. cout << "file not found" << endl;
  30. }
  31. getchar();
  32. return 0;
  33. }

Проверим работоспособность нашего кода, вызвав ошибку. Тогда, в зависимости от ошибки, у пользователю будет представлено одно из заготовленных сообщений, например вот такое.

Теперь точно все, на этом сегодняшний урок подошел к концу, надеюсь, что все было понятно, если же нет – добро пожаловать в комментарии. И традиционное домашнее задание: создать новый текстовый файл, записать в него последовательность чисел. Дописать в файл формулу последовательности. Вывести содержимое файла на экран. На выполнение как всегда неделя, удачи и до следующего урока!

Аватар пользователя Илья Медведев

Пятый пример

    1. #include "stdafx.h"
    2. #include <iostream>
    3. #include <fstream>
    4. #include <string>
    5.  
    6. using namespace std;
    7.  
    8. int _tmain(int argc, _TCHAR* argv[])
    9. {
    10. ifstream txtfile("D:\hello.txt");
    11. string str;
    12. while (!txtfile.eof())
    13. {
    14. txtfile.getline(str, len);
    15. cout << str << endl;
    16. }
    17. getchar();
    18. return 0;
    19. }

    Откуда взялся len?

    1. while (!txtfile.eof())
    2. {
    3. txtfile.getline(str, len);

    man getline(http://man7.org/linux/man-pages/man3/getline.3.html) намекает, что этот код можно сократить до
    1. while (getline(txtfile, str))

Аватар пользователя GoodWin

len от слова лень Smile

Пытался переписывать примеры, вот len и остался, спасибо, что заметил, код изменен на тот, что ты посоветовал.

Аватар пользователя Илья Медведев

Теперь пример не соответствует описанию Smile

В качестве параметров надо указать имя целевого массива, то есть массива куда мы будем записывать строки, и длину этого массива. Укажем длину 100 – для нашего примера ее будет достаточно. Правда, есть загвоздка в этом деле. Беда в том, что в этом случае string использовать нельзя. Без угрызений совести заменим string на char (благо в рамках данной задачи нас никто ругать не будет), да не просто на char заменим, а на массив char. Посмотрим на переработанный код.

А вот в последнем примере можно реализовать как я предложил