Файловый ввод-вывод в 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 в зависимости от того, был ли обнаружен конец файла при чтении:
Но при использовании этого метода могут возникнуть затруднения, например, с тем, что если после последнего числа в файле стоит символ конца строки, то состояние «достигнут конец файла» произойдет не после чтения последнего числа, а после следующего чтения.
Более надежный (и простой!) способ считать последовательность чисел из файла до конца файла — использование значения, возвращаемого при считывании:
Как передать ofstream в функцию
Для работы с файлами в стандартной библиотеке определен заголовочный файл fstream , который определяет базовые типы для чтения и записи файлов. В частности, это:
- ifstream : для чтения с файла
- ofstream : для записи в файл
- fstream : совмещает запись и чтение
Для работы с данными типа wchar_t для этих потоков определены двойники:
- wifstream
- wofstream
- wfstream
Открытие файла
При операциях с файлом вначале необходимо открыть файл с помощью функции open() . Данная функция имеет две версии:
- open(путь)
- open(путь, режим)
Для открытия файла в функцию необходимо передать путь к файлу в виде строки. И также можно указать режим открытия. Список доступных режимов открытия файла:
- ios::in : файл открывается для ввода (чтения). Может быть установлен только для объекта ifstream или fstream
- ios::out : файл открывается для вывода (записи). При этом старые данные удаляются. Может быть установлен только для объекта ofstream или fstream
- ios::app : файл открывается для дозаписи. Старые данные не удаляются.
- ios::ate : после открытия файла перемещает указатель в конец файла
- ios::trunc : файл усекается при открытии. Может быть установлен, если также установлен режим out
- ios::binary : файл открывается в бинарном режиме
Если при открытии режим не указан, то по умолчанию для объектов ofstream применяется режим ios::out , а для объектов ifstream — режим ios::in . Для объектов fstream совмещаются режимы ios::out и ios::in .
std::ofstream out; // поток для записи out.open("hello1.txt"); // окрываем файл для записи std::ofstream out2; out2.open("hello2.txt", std::ios::app); // окрываем файл для дозаписи std::ofstream out3; out2.open("hello3.txt", std::ios::out | std::ios::trunc); // установка нескольких режимов std::ifstream in; // поток для чтения in.open("hello4.txt"); // окрываем файл для чтения std::fstream fs; // поток для чтения-записи fs.open("hello5.txt"); // окрываем файл для чтения-записи
Однако в принципе необязательно использовать функцию open для открытия файла. В качестве альтернативы можно также использовать конструктор объектов-потоков и передавать в них путь к файлу и режим открытия:
fstream(путь) fstream(путь, режим)
При вызове конструктора, в который передан путь к файлу, данный файл будет автоматически открываться:
std::ofstream out("hello.txt"); std::ifstream in("hello.txt"); std::fstream fs("hello.txt", std::ios::app);
В данном случае предполагается, что файл «hello.txt» располагается в той же папке, где и файл программы.
Вообще использование конструкторов для открытия потока является более предпочтительным, так как определение переменной, представляющей файловой поток, уже преполагает, что этот поток будет открыт для чтения или записи. А использование конструктора избавит от ситуации, когда мы забудем открыть поток, но при этом начнем его использовать.
В процессе работы мы можем проверить, окрыт ли файл с помощью функции is_open() . Если файл открыт, то она возвращает true:
std::ifstream in; // поток для чтения in.open(«hello.txt»); // окрываем файл для чтения // если файл открыт if (in.is_open())
Закрытие файла
После завершения работы с файлом его следует закрыть с помощью функции close() . Также стоит отметить, то при выходе объекта потока из области видимости, он удаляется, и у него автоматически вызывается функция close.
#include #include int main() < std::ofstream out; // поток для записи out.open("hello.txt"); // окрываем файл для записи out.close(); // закрываем файл std::ifstream in; // поток для чтения in.open("hello.txt"); // окрываем файл для чтения in.close(); // закрываем файл std::fstream fs; // поток для чтения-записи fs.open("hello.txt"); // окрываем файл для чтения-записи fs.close(); // закрываем файл >
5. Передача потоков функциям в качестве параметров
копирование выполняется специальной функцией «copy_to(. )».
copy_to( in_stream, out_stream );
// Конец главной функции
// Функция для копирования файла в другой файл и на экран
void copy_to( ifstream& in, ofstream& out )
Программа 6.2
6. Операторы ввода/вывода «>>» и «
До сих пор рассматривались способы записи и чтения из файлов
отдельных символов. На нижнем уровне, скрытом внутри классов ofstream
and ifstream, объекты этих классов всегда работают с файлами как с
последовательностями символов. Поэтому данные других типов («int»,
«double» и др.) для записи в файл должны быть преобразованы в
последовательность символов. При чтении из файла эти
последовательности должны быть преобразованы обратно.
Некоторые преобразования типов данных автоматически
использовались для ввода с клавиатуры и вывода на экран
При использовании операторов «>>» и »
каждого записанного значения записывать еще как минимум один символ ‘ ‘
(пробел) или служебный символ ‘\n’ (маркер конца строки). Это гарантирует,
что элементы данных будут корректно отделены в файле друг от друга, и их
можно будет извлекать оттуда с помощью оператора
6.3. Сначала она создает файл «Integers.txt», записывает в него целые числа
51, 52, 53, 54 и 55, а затем считывает эти числа из файла.
ifstream in_stream1; // Поток для подсчета целых чисел.
ifstream in_stream2; // Поток для подсчета символов.
for ( count = 1; count
// Подсчет количества целых чисел в файле
// Подсчет количества символов, не являющихся разделителями
Программа 6.3
Программа 6.3 выведет на экран следующие сообщения:
В файле хранится 5 целых чисел,
представленных с помощью 10 символов.
При подсчете символов в последней части программы 6.3 снова
обратите внимание на то, что, в отличие от функции «get(. )», оператор «>>»
игнорирует в файле пробелы (которые разделяют пять целых чисел).
Задание:
Напишите программу, печатающую на экране содержимое
собственного исходного файла на Си++.
Разработайте программу, которая (1) начинается с оператора вывода
и затем (2) копирует собственный исходный файл на Си++ в файл «Clip.cpp»
и на экран, при этом пропуская все комментарии между маркерами «/* . */» (и
маркеры комментариев тоже).
Получившийся файл «Clip.cpp» должен компилироваться и работать
точно так же, как и исходная программа.
Подсказки: (1) вам может пригодиться функция «putback()«; (2) для
отслеживания состояния, находитесь ли вы внутри комментария или
нет, можете применить логический «флаг».
Напишите программу, которая подсчитывает и выводит на экран
количество символов (включая пробелы) в собственном исходном файле.
Что выведет на экран следующая программа?
// Создание текстового файла и запись в него двух целых чисел
// Попытка чтения из только что созданного файла
// «Integers.txt» символа, затем целого числа,
// затем снова символа, затем опять целого числа.
in_stream >> character >> integer;
in_stream >> character >> integer;
Напишите программу, печатающую на экране содержимое
собственного исходного файла на Си++.
using namespace std;

Разработайте программу, которая (1) начинается с оператора вывода
и затем (2) копирует собственный исходный файл на Си++ в файл «Clip.cpp»
и на экран, при этом пропуская все комментарии между маркерами «/* . */» (и
маркеры комментариев тоже).
Получившийся файл «Clip.cpp» должен компилироваться и работать
точно так же, как и исходная программа.
using namespace std;

Напишите программу, которая подсчитывает и выводит на экран
количество символов (включая пробелы) в собственном исходном файле.
Язык C++
Давайте напишем функцию add_item , которая принимает контейнер vector и добавляет в конец контейнера новый элемент. Мы могли бы начать со следующего кода:
#include #include #include using namespace std; // Здесь есть проблема void add_item(vectorstring> vec) vec.push_back("New item!"); > int main() vectorstring> vec; add_item(vec); for (string s : vec) cout <s <'\n'; > return 0; >
Если мы скомпилируем и запустим эту программу, то обнаружим, что после вызова функции add_item контейнер vec остался пустым. Проблема в том, что мы передали в функцию копию объекта vec . Внутри функции к этой копии был добавлен новый элемент, а после выхода из функции копия была удалена.
Следующий вариант нашей программы уже будет делать то что мы хотим:
// Здесь есть проблема vectorstring> add_item(vectorstring> vec) vec.push_back("New item!"); return vec; > int main() vectorstring> vec; vectorstring> vec2 = add_item(vec); for (string s : vec2) cout <s <'\n'; > return 0; >
Такая реализация, однако, является очень плохой идеей. Мы всего лишь хотели добавить один элемент в вектор, а вместо этого получили копию вектора с добавленным новым элементом. Помимо неверной логики работы, мы получили потенциальную проблему с производительностью: вместо константного времени мы тратим линейное время на добавление элемента в вектор.
Правильное решение нашей задачи в C++ выглядит следующим образом:
void add_item(vectorstring>& vec) vec.push_back("New item!"); > int main() vectorstring> vec; add_item(vec); for (string s : vec) cout <s <'\n'; > return 0; >
Символ амперсанд & позволяет передать в функцию ссылку на параметр. Работа с параметром внутри функции не изменяется, но вместо копии мы имеем дело именно с тем объектом, который был передан в функцию. Таким образом, мы избавились от лишнего копирования и реализовали правильную логику работы программы.
Рассмотрим другой пример. Допустим, мы хотим передать вектор в функцию, которая будет анализировать элементы вектора, но не будет его изменять. Например:
// Здесь есть проблема int count_greetings(vectorstring>& vec) int counter = 0; for (string s : vec) if (s == "Hello") ++counter; > > return counter; >
Мы уже достаточно грамотные и сразу передали вектор по ссылке, чтобы избежать ненужного копирования. Однако в текущем виде функция count_greetings имеет другую, более тонкую, проблему. Если мы нарушим договоренность и изменим вектор внутри функции count_greetings , то компилятор не увидит в этом проблемы. Проблему будем искать мы, когда поймем, что в каком-то месте нашей программы происходит неправильная манипуляция с вектором.
Хорошим стилем в данном случае является передача параметра по константной ссылке:
int count_greetings(const vectorstring>& vec) int counter = 0; for (const string& s : vec) if (s == "Hello") ++counter; > > return counter; >
Теперь компилятор не позволит изменить объект vec внутри функции count_greetings . Кроме того, теперь в коде явно выражена мысль о том, что объект передается в функцию только для чтения. Такой код проще читать и понимать логику его работы. Обратите внимание, что мы воспользовались константной ссылкой при определении переменной в цикле for . Здесь мы имеем дело с аналогичной ситуацией: в предыдущей версии в переменную s по очереди копировался каждый элемент вектора. Теперь же мы перебираем в цикле константные ссылки на объекты, не копируя их.
Иногда необходимо изменять элементы вектора в цикле. В таком случае необходимо использовать неконстантную ссылку:
for (string& s : vec) s.push_back('!'); >
Передача константной ссылки на объект в функцию, которая не имеет право изменять объект, имеет смысл только в том случае, если копирование объекта является дорогой операцией. В частности, нет никакого смысла в передаче по ссылке объектов int или double . Это наоборот может привести к потере производительности. Если же мы имеем дело со сложным объектом, таким как string или любым контейнером, то передача по константой ссылке является единственным верным решением.
Использование ссылок в C++ не ограничивается передачей параметров в функции, но с этого примера проще всего начать знакомство со ссылками. Ключевое слово const также имеет разнообразные применения в C++. О некоторых из них мы поговорим в дальнейшем.
Резюме
Мы обсудили три способа передачи параметров в функцию:
- передача копии
- передача по ссылке
- передача по константной ссылке
Передавать копию объекта имеет смысл, если копирование стоит дешево, либо когда того требует логика программы. Передача по ссылке используется, если необходимо модифицировать передаваемый объект. Передача по константной ссылке позволяет избежать копирования больших объектов.
Источники
- isocpp.org/wiki/faq/references
- Введение
- Настройка рабочей среды
- Язык C++
- Работа с потоками ввода-вывода
- Строки
- Контейнеры стандартной библиотеки C++
- Эффективная передача параметров в функцию
- Алгоритмы стандартной библиотеки C++
- Итераторы
- Библиотеки numeric и random
- Классы
- Наследование
- Динамическое выделение памяти
- Обобщенное программирование