Как открывать файловые потоки с анонимными конструкторами

Обновлено: 03.07.2024

В C++11, работа с потокам осуществляется по средствам класса std::thread (доступного из заголовочного файла <thread> ), который может работать с регулярными функциями, лямбдами и функторами. Кроме того, он позволяет вам передавать любое число параметров в функцию потока.

В этом примере, thr — это объект, представляющий поток, в котором будет выполняться функция threadFunction() . Вызов join блокирует вызывающий поток (в нашем случае — поток main) до тех пор, пока thr (а точнее threadFunction() ) не выполнит свою работу. Если функция потока возвращает значение — оно будет проигнорировано. Однако принять функция может любое количество параметров.

Несмотря на то, что передавать можно любое число параметров, все они были переданы по значению Если в функцию необходимо передать параметры по ссылке, они должны быть обернуты в std::ref или std::cref , как в примере:

Программа напечатает в консоль 2. Если не использовать std::ref , то результатом работы программы будет 1.

Помимо метода join , следует рассмотреть еще один, похожий метод — detach .
detach позволяет отсоединить поток от объекта, иными словами, сделать его фоновым. К отсоединенным потокам больше нельзя применять join .

Также следует отметить, что если функция потока кидает исключение, то оно не будет поймано try-catch блоком. Т.е. следующий код не будет работать (точнее работать то будет, но не так как было задумано: без перехвата исключений):

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

    : возвращает id текущего потока : говорит планировщику выполнять другие потоки, может использоваться при активном ожидании : блокирует выполнение текущего потока в течение установленного периода : блокирует выполнение текущего потока, пока не будет достигнут указанный момент времени

Блокировки

    : обеспечивает базовые функции lock() и unlock() и не блокируемый метод try_lock() : может войти «сам в себя» : в отличие от обычного мьютекса, имеет еще два метода: try_lock_for() и try_lock_until() : это комбинация timed_mutex и recursive_mutex

Программа должна выдавать примерно следующее:

Перед обращением к общим данным, мьютекс должен быть заблокирован методом lock , а после окончания работы с общими данными — разблокирован методом unlock .

Следующий пример показывает простой потокобезопасный контейнер (реализованный на базе std::vector ), имеющий методы add() для добавления одного элемента и addrange() для добавления нескольких элементов.
Примечание: и всё же этот контейнер не является полностью потокобезопасным по нескольким причинам, включая использование va_args . Также, метод dump() не должен принадлежать контейнеру, а должен быть автономной функцией. Цель этого примера в том, что показать основные концепции использования мьютексов, а не не сделать полноценный, безошибочный, потокобезопасный контейнер.

При выполнении этой программы произойдет deadlock (взаимоблокировка, т.е. заблокированный поток так и останется ждать). Причиной является то, что контейнер пытается получить мьютекс несколько раз до его освобождения (вызова unlock ), что невозможно. Здесь и выходит на сцену std::recursive_mutex , который позволяет получать тот же мьютекс несколько раз. Максимальное количество получения мьютекса не определено, но если это количество будет достигно, то lock бросит исключение std::system_error. Поэтому, решение проблемы в коде выше (кроме изменения реализации addrange() , чтобы не вызывались lock и unlock ), заключается в замене мьютекса на std::recursive_mutex .

Теперь, результат работы программы будет следующего вида:

    : когда объект создан, он пытается получить мьютекс (вызывая lock() ), а когда объект уничтожен, он автоматически освобождает мьютекс (вызывая unlock() ) : в отличие от lock_guard , также поддерживает отложенную блокировку, временную блокировку, рекурсивную блокировку и использование условных переменных

Можно поспорить насчет того, что метод dump() должен быть константным, ибо не изменяет состояние контейнера. Попробуйте сделать его таковым и получите ошибку при компиляции:

Мьютекс (не зависимо от формы реализации), должен быть получен и освобожден, а это подразумевает использование не константных методов lock() и unlock() . Таким образом, аргумент lock_guard не может быть константой. Решение этой проблемы заключается в том, чтобы сделать мьютекс mutable , тогда спецификатор const будет игнорироваться и это позволит изменять состояние из константных функций.

  • defer_lock типа defer_lock_t : не получать мьютекс
  • try_to_lock типа try_to_lock_t : попытаться получить мьютекс без блокировки
  • adopt_lock типа adopt_lock_t : предполагается, что у вызывающего потока уже есть мьютекс
    : блокирует мьютекс, используя алгоритм избегания deadlock'ов (используя lock() , try_lock() и unlock() ) : пытается блокировать мьютексы в порядке, в котором они были указаны

Предположим, что эта функция вызвана из двух разных потоков, из первого потока: элемент удаляется из 1 контейнера и добавляется во 2, из второго потока, наоборот, элемент удаляется из 2 контейнера и добавляется в 1. Это может вызвать deadlock (если контекст потока переключается от одного потока к другому, сразу после первой блокировки).

Для решения этой проблемы можно использовать std::lock , который гарантирует блокировку безопасным (с точки зрения взаимоблокировки) способом:

Работа файлового ввода/вывода в языке C++ почти аналогична работе обычных потоков ввода/вывода (но с небольшими нюансами).

Классы файлового ввода/вывода

Есть три основных класса файлового ввода/вывода в языке C++:

ofstream (является дочерним классу ostream);

fstream (является дочерним классу iostream ).

С помощью этих классов можно выполнить однонаправленный файловый ввод, однонаправленный файловый вывод и двунаправленный файловый ввод/вывод. Для их использования нужно всего лишь подключить заголовочный файл fstream.

В отличие от потоков cout, cin, cerr и clog, которые сразу же можно использовать, файловые потоки должны быть явно установлены программистом. То есть, чтобы открыть файл для чтения и/или записи, нужно создать объект соответствующего класса файлового ввода/вывода, указав имя файла в качестве параметра. Затем, с помощью оператора вставки ( << ) или оператора извлечения ( >> ), можно записывать данные в файл или считывать содержимое файла. После проделывания данных действий нужно закрыть файл — явно вызвать метод close() или просто позволить файловой переменной ввода/вывода выйти из области видимости (деструктор файлового класса ввода/вывода закроет этот файл автоматически вместо нас).

Файловый вывод

Для записи в файл используется класс ofstream . Например:

// Класс ofstream используется для записи данных в файл. // Если мы не можем открыть этот файл для записи данных, cerr << "Uh oh, SomeText.txt could not be opened for writing!" << endl ; // Когда outf выйдет из области видимости, то деструктор класса ofstream автоматически закроет наш файл

Обратите внимание, мы также можем использовать метод put() для записи одного символа в файл.

Файловый ввод

Теперь мы попытаемся прочитать содержимое файла, который создали в предыдущем примере. Обратите внимание, ifstream возвратит 0 , если мы достигли конца файла (это удобно для определения «длины» содержимого файла). Например:

// ifstream используется для чтения содержимого файла. // Если мы не можем открыть этот файл для чтения его содержимого, cerr << "Uh oh, SomeText.txt could not be opened for reading!" << endl ; // то перемещаем эти данные в строку, которую затем выводим на экран // Когда inf выйдет из области видимости, то деструктор класса ifstream автоматически закроет наш файл

Результат выполнения программы:

Хм, это не совсем то, что мы хотели. Как мы уже узнали на предыдущих уроках, оператор извлечения работает с «отформатированными данными», т.е. он игнорирует все пробелы, символы табуляции и символ новой строки. Чтобы прочитать всё содержимое как есть, без его разбивки на части (как в примере, приведенном выше), нам нужно использовать метод getline():

// ifstream используется для чтения содержимого файлов. // Мы попытаемся прочитать содержимое файла SomeText.txt // Если мы не можем открыть файл для чтения его содержимого, cerr << "Uh oh, SomeText.txt could not be opened for reading!" << endl ; // то перемещаем то, что можем прочитать, в строку, а затем выводим эту строку на экран // Когда inf выйдет из области видимости, то деструктор класса ifstream автоматически закроет наш файл

Результат выполнения программы:

Буферизованный вывод

Вывод в языке C++ может быть буферизован. Это означает, что всё, что выводится в файловый поток, не может сразу же быть записанным на диск (в конкретный файл). Это сделано, в первую очередь, по соображениям производительности. Когда данные буфера записываются на диск, то это называется очисткой буфера. Одним из способов очистки буфера является закрытие файла. В таком случае всё содержимое буфера будет перемещено на диск, а затем файл будет закрыт.

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

Также буфер можно очистить вручную, используя метод ostream::flush() или отправив std::flush в выходной поток. Любой из этих способов может быть полезен для обеспечения немедленной записи содержимого буфера на диск в случае сбоя программы.

Интересный нюанс: Поскольку std::endl; также очищает выходной поток, то его чрезмерное использование (приводящее к ненужным очисткам буфера) может повлиять на производительность программы (так как очистка буфера в некоторых случаях может быть затратной операцией). По этой причине программисты, которые заботятся о производительности своего кода, часто используют \n вместо std::endl для вставки символа новой строки в выходной поток, дабы избежать ненужной очистки буфера.

Режимы открытия файлов

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

app — открывает файл в режиме добавления;

ate — переходит в конец файла перед чтением/записью;

binary — открывает файл в бинарном режиме (вместо текстового режима);

in — открывает файл в режиме чтения (по умолчанию для ifstream );

out — открывает файл в режиме записи (по умолчанию для ofstream );

trunc — удаляет файл, если он уже существует.

Можно указать сразу несколько флагов путем использования побитового ИЛИ (|).

ifstream по умолчанию работает в режиме ios::in;

ofstream по умолчанию работает в режиме ios::out;

fstream по умолчанию работает в режиме ios::in ИЛИ ios::out, что означает, что вы можете выполнять как чтение содержимого файла, так и запись данных в файл.

Работа с файлами с использованием конструкций языка Си была рассмотрена здесь.

Для программиста открытый файл представляется как последовательность считываемых или записываемых данных. При открытии файла с ним связывается поток ввода-вывода . Выводимая информация записывается в поток, вводимая информация считывается из потока.

Для работы с файлами необходимо подключить заголовочный файл <fstream> . В нем определены несколько классов и подключены заголовочные файлы

Файловый ввод-вывод аналогичен стандартному вводу-выводу, единственное отличие – это то, что ввод-вывод выполнятся не на экран, а в файл.

Если ввод-вывод на стандартные устройства выполняется с помощью объектов cin и cout , то для организации файлового ввода-вывода достаточно создать собственные объекты, которые можно использовать аналогично этим операторам.

При работе с файлом можно выделить следующие этапы:

  • создать объект класса fstream (возможно, ofstream или ifstream );
  • связать объект класса fstream с файлом, который будет использоваться для операций ввода-вывода;
  • осуществить операции ввода-вывода в файл;
  • закрыть файл.

Работа с файлами в C++

В результате будет создан файл

Режимы открытия файлов устанавливают характер использования файлов. Для установки режима в классе ios предусмотрены константы, которые определяют режим открытия файлов.

Константа Описание
ios::in открыть файл для чтения
ios::out открыть файл для записи
ios::ate при открытии переместить указатель в конец файла
ios::app открыть файл для записи в конец файла
ios::trunc удалить содержимое файла, если он существует
ios::binary открытие файла в двоичном режиме

Режимы открытия файлов можно устанавливать непосредственно при создании объекта или при вызове метода open() .

ofstream fout( "file.txt" , ios::app);
fout.open( "file.txt" , ios::app);

Режимы открытия файлов можно комбинировать с помощью поразрядной логической операции ИЛИ | , например:

ios::out | ios::in - открытие файла для записи и чтения.

Произвольный доступ к файлу

Система ввода-вывода С++ позволяет осуществлять произвольный доступ с использованием методов seekg() и seekp() .

  • ifstream &seekg(Смещение, Позиция);
  • ofstream &seekp(Смещение, Позиция);

Смещение определяет область значений в пределах файла ( long int ).

Система ввода-вывода С++ обрабатывает два указателя, ассоциированные с каждым файлом:

  • get pointer g - определяет, где именно в файле будет производиться следующая операция ввода;
  • put pointer p - определяет, где именно в файле будет производиться следующая операция вывода.

Позиция смещения определяется как

Позиция Значение
ios::beg Начало файла
ios::cur Текущее положение
ios::end Конец файла

Всякий раз, когда осуществляются операции ввода или вывода, соответствующий указатель автоматически перемещается.
С помощью методов seekg() и seekp() можно получить доступ к файлу в произвольном месте.

Можно определить текущую позицию файлового указателя, используя следующие функции:

  • streampos tellg() - позиция для ввода
  • streampos tellp() - позиция для вывода

В результате выполнения первой части программы будет создан файл

Вторая часть программы выведет в консоль

Ещё один пример. Допустим, нам нужно заполнять таблицу

Причем каждая вновь введенная строка должна размещаться в таблице непосредственно под "шапкой".

Алгоритм решения задачи следующий:

  • формируем очередную строку для вывода
  • открываем файл для чтения, считываем из него данные и сохраняем их в массив строк
  • закрываем файл
  • открываем файл для записи
  • выводим "шапку" таблицы
  • выводим новую строку
  • выводим все сохраненные строки обратно в файл, начиная со строки после шапки
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

fstream inOut;
inOut.open( "file.txt" , ios::in); // открываем файл для ввода
// Считываем из файла имеющиеся данные
int count = 0;
while (inOut.getline(line[count], 100)) count++;
inOut.close(); // закрываем файл


Результат выполнения:


Файл данных

Полученный файл данных:

Здравствуйте Елена. Скажите, а можно ли как то узнать наступивший конец файла. Например, если количество строк в файле неизвестно, а нужно организовать цикл по их считыванию и прервать его по окончанию файла. Елена, здравствуйте! Помогите, пожалуйста. Есть код, работающий, но вывод делает в консоль, а так как вывод очень большой, то все результаты там не помещаются, только малая последняя часть. Подскажите, пожалуйста, где и что изменить, чтоб вывод записывало в файл. Если можно очень подробно или прям конкретным примером, я со всем этим только только столкнулась, поэтому пока не особо соображаю(( Вот мой код 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 import static java.lang.System.out;
import java.util.Arrays;
class Combinations
private static final int M = 12;
private static final int N = 24;
private static int [] generateCombinations( int [] arr)
if (arr == null)
arr = new int [M];
for ( int i = 0; i < M; i++)
arr[i] = i + 1;
return arr;
>
for ( int i = M - 1; i >= 0; i--)
if (arr[i] < N - M + i + 1)
arr[i]++;
for ( int j = i; j < M - 1; j++)
arr[j + 1] = arr[j] + 1;
return arr;
>
return null;
>
public static void main(String args[])
int [] arr = null;
while ((arr = generateCombinations(arr)) != null)
out.println(Arrays.toString(arr));
>
>
В настройках консоли (меню в верхнем левом углу) изменить размер буфера Думаю, что "в правильную". Можно попробовать взять, например, среднее арифметическое для каждой точки из двух файлов. Но это нужно анализировать при отладке. Елена, здравствуйте, с вами можно как-то связаться, помимо того как здесь в комментариях? Можно через форму обратной связи на сайте или через ВК Елена, здравствуйте. Подскажите, пожалуйста, как можно сделать так, чтобы из первой строки файла считывался размер массива, а из второй - его элементы, количество которых равно значению из первой строки. Не нашла у вас на сайте ничего про построчный ввод из файла. Заранее спасибо. Ввод из файла осуществляется аналогично консольному вводу. После считывания порции данных указатель позиции файла автоматически перемещается на ее конец. Да, но у вас в статьях нет ничего про, к примеру, getline. По вашим статьям я изучаю практически все, очень понятно и доходчиво написано, но этот блок то ли я не могу найти, то ли его нет. Подскажите, у вас на сайте есть статья, где более подробно описывается файловый ввод и вывод? Здравствуйте, попыталась сделать вывод данных в файл, но почему то только создался документ, а данных там нет. Что не так?

ofstream fout( "file.txt" , ios::out);
fout.open( "file.txt" , ios::out);
for ( int i = 0; i < SIZE; i++)
for ( int j = 0; j < SIZE; j++)
printf( "%5d " , a[i][j]);
printf( "\n" );

Вы выводите в файл указатель на строку, а не элементы матрицы Не совсем понимаю цель этого действия. Почему нельзя создать пустую строку? А вообще sr[0]=0; И ещё один вопрос. Можно ли ввести несколько слов с консоли в одно значение char? Можно ли как-то скопировать половину строки ? Выше вы копировали от середины и до конца, но мне нужно наоборот- от начала до середины ( или 10-го символа) Вроде рассмотрены оба варианта: поиск позиции от начала строки и от конца Не совсем правильно сформулировала , от определённого индекса до определённого индекса. То есть, у нас есть строка(char sr[50]="blablablabla";) Я хочу скопировать с третього индекса по 10. Мы с файлом работе м или со строкой? Если со строкой, то функция strncpy(&sr[3], 7, dst); 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 надо сделать типа чтобы еще можно было добавить людей в список и выровнять это все Почему файл объявлен как поток вывода, а открывается для ввода? Чему равен count? Где тут люди? Почему при вводе нет цикла? 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

Файловая система C++. Общие принципы работы. Примеры. Открытие/закрытие файла

Содержание

  • 1. Подключение доступа к классам организации работы с файловой системой C++
  • 2. Создание файлового потока. Каким образом осуществляется связь потока с файлом? Функция open()
  • 3. Способы открытия файла. Функция openmode()
  • 4. Функция is_open() . Проверка открытия файла. Пример
  • 5. Функция close() . Закрытие файла. Пример
  • 6. Примеры открытия/закрытия файла функцией open() . Проверка открытия файла функцией is_open()

Поиск на других ресурсах:

1. Подключение доступа к классам организации работы с файловой системой C++

Файловый ввод/вывод есть частью общей системы ввода/вывода языка C++. Для того, чтобы в программе можно было использовать файловый ввод/вывод предварительно должен быть подключен модуль fstream

В этом модуле реализованы основные классы для работы с файловой системой:

  • ifstream – используется для организации ввода из файлового потока;
  • ofstream – используется для организации вывода в файл (файловый поток);
  • fstream – используется для обеспечения ввода/вывода.
2. Создание файлового потока. Каким образом осуществляется связь потока с файлом? Функция open()

Чтобы создать файловый поток нужно объявить экземпляр одного из классов ifstream , ofstream или fstream

Созданный поток связывается с файлом с помощью функции open() , которая имеет различные реализации в разных классах.

В классе ifstream функция open() имеет следующий прототип

В классе ofstream функция open() имеет следующий прототип

  • filename – имя открываемого файла;
  • mode – параметр, который задает способ открытия файла. В этом параметре значение ios::out означает, что файл открывается для вывода. Значение ios::trunc означает, что предшествующее содержимое существующего файла с таким самым именем будет удалено, а длина файла станет равной нулю.

В классе fstream прототип функции open() следующий

  • filename – имя файла;
  • mode – параметр, который задает способ открытия файла. Как видно из значения параметра mode , файл открывается и для записи и для чтения.
3. Способы открытия файла. Функция openmode()

Способы открытия файла могут быть разными. Значения, которые описывают способы открытия файла, реализованы в функции openmode() . Ниже приведены значения, которые могут возвращаться функцией openmode() и объяснение к ним:

  • ios::app – записываемые данные дописываются в конец файла;
  • ios::ate – при открытии файла происходит поиск конца файла. Но запись в файл может осуществляться в любое место этого файла;
  • ios::binary – позволяет открыть файл в бинарном (двоичном) режиме. Существует два режима открытия файла: текстовый и бинарный. По умолчанию файлы открываются в текстовом режиме;
  • ios::in – файл открывается для ввода (происходит чтение из файла);
  • ios::out – файл открывается для вывода (происходит запись в файл);
  • ios::trunc – означает, что предыдущее содержимое существующего файла с таким самым именем будет удалено, а длина файла станет равной нулю. Если открыть поток вывода с помощью класса ofstream все содержимое файла с тем же именем стирается.

Способы открытия файла можно комбинировать между собою с помощью операции | (побитовое ИЛИ).

Пример. Способы открытия файла.

4. Функция is_open() . Проверка открытия файла. Пример

Функция is_open() предназначена для проверки, закрыт ли файл или нет. Эта функция реализована в классах ifstream , ofstream и fstream .

Функция имеет следующий прототип:

Функция возвращает true , если поток связан с открытым файлом. В другом случае функция возвращает false .

Пример. Демонстрируется определение открытия файла для объекта os класса ofstream . Если файл открыт, то этот файл закрывается.

5. Функция close() . Закрытие файла. Пример

Чтобы закрыть файл, нужно вызвать функцию close() .

Например. Далее продемонстрировано открытие и закрытие файла для потока ifstream

6. Примеры открытия/закрытия файла функцией open() . Проверка открытия файла функцией is_open()

Открыть файл можно одним из двух способов:

  • с помощью функции open() ;
  • с помощью конструктора класса (см. примеры ниже).

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

Корректность открытия файла можно реализовать одним из двух нижеследующих способов:

  • с помощью соответствующей проверки оператором условного перехода if ;
  • с помощью функции is_open() .

Пример 1. Открытие файла для вывода (записи).

Пример 2. Открытие файла для ввода (чтения)

Пример 3. Открытие файла для вывода (записи). Использование функции is_open() .

Пример 4. Открытие файла для ввода (чтения). Использование функции is_open() .

Читайте также: