Файловый ввод
Теперь мы попытаемся прочитать содержимое файла, который создали в предыдущем примере
Обратите внимание, возвратит , если мы достигли конца файла (это удобно для определения «длины» содержимого файла). Например:
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib> // для использования функции exit()
int main()
{
using namespace std;
// ifstream используется для чтения содержимого файла.
// Попытаемся прочитать содержимое файла SomeText.txt
ifstream inf(«SomeText.txt»);
// Если мы не можем открыть этот файл для чтения его содержимого,
if (!inf)
{
// то выводим следующее сообщение об ошибке и выполняем функцию exit()
cerr << «Uh oh, SomeText.txt could not be opened for reading!» << endl;
exit(1);
}
// Пока есть данные, которые мы можем прочитать,
while (inf)
{
// то перемещаем эти данные в строку, которую затем выводим на экран
string strInput;
inf >> strInput;
cout << strInput << endl;
}
return 0;
// Когда inf выйдет из области видимости, то деструктор класса ifstream автоматически закроет наш файл
}
1 |
#include <iostream> intmain() { usingnamespacestd; // ifstream используется для чтения содержимого файла. // Попытаемся прочитать содержимое файла SomeText.txt ifstreaminf(«SomeText.txt»); // Если мы не можем открыть этот файл для чтения его содержимого, if(!inf) { // то выводим следующее сообщение об ошибке и выполняем функцию exit() cerr<<«Uh oh, SomeText.txt could not be opened for reading!»<<endl; exit(1); } // Пока есть данные, которые мы можем прочитать, while(inf) { // то перемещаем эти данные в строку, которую затем выводим на экран stringstrInput; inf>>strInput; cout<<strInput<<endl; } return; // Когда inf выйдет из области видимости, то деструктор класса ifstream автоматически закроет наш файл } |
Результат выполнения программы:
Хм, это не совсем то, что мы хотели. Как мы уже узнали на предыдущих уроках, оператор извлечения работает с «отформатированными данными», т.е. он игнорирует все пробелы, символы табуляции и символ новой строки. Чтобы прочитать всё содержимое как есть, без его разбивки на части (как в примере, приведенном выше), нам нужно использовать метод getline():
#include <iostream>
#include <fstream>
#include <string>
#include <cstdlib> // для использования функции exit()
int main()
{
using namespace std;
// ifstream используется для чтения содержимого файлов.
// Мы попытаемся прочитать содержимое файла SomeText.txt
ifstream inf(«SomeText.txt»);
// Если мы не можем открыть файл для чтения его содержимого,
if (!inf)
{
// то выводим следующее сообщение об ошибке и выполняем функцию exit()
cerr << «Uh oh, SomeText.txt could not be opened for reading!» << endl;
exit(1);
}
// Пока есть, что читать,
while (inf)
{
// то перемещаем то, что можем прочитать, в строку, а затем выводим эту строку на экран
string strInput;
getline(inf, strInput);
cout << strInput << endl;
}
return 0;
// Когда inf выйдет из области видимости, то деструктор класса ifstream автоматически закроет наш файл
}
1 |
#include <iostream> intmain() { usingnamespacestd; // ifstream используется для чтения содержимого файлов. // Мы попытаемся прочитать содержимое файла SomeText.txt ifstreaminf(«SomeText.txt»); // Если мы не можем открыть файл для чтения его содержимого, if(!inf) { // то выводим следующее сообщение об ошибке и выполняем функцию exit() cerr<<«Uh oh, SomeText.txt could not be opened for reading!»<<endl; exit(1); } // Пока есть, что читать, while(inf) { // то перемещаем то, что можем прочитать, в строку, а затем выводим эту строку на экран stringstrInput; getline(inf,strInput); cout<<strInput<<endl; } return; // Когда inf выйдет из области видимости, то деструктор класса ifstream автоматически закроет наш файл } |
Результат выполнения программы:
Строки в кавычках (C++ 14)
При вставке строки в поток можно легко получить ту же строку обратно, вызвав функцию-член. Однако если вы хотите использовать оператор извлечения для вставки потока в новую строку позже, возможно, вы получите непредвиденный результат, так как оператор по умолчанию будет останавливаться при обнаружении первого символа пробела.
Эту проблему можно устранить вручную, но чтобы сделать обход строки более удобным, C++ 14 добавляет манипулятор потока в . После вставки заключает строку с разделителем (по умолчанию используется двойная кавычка «»), а при извлечении обрабатывается поток для извлечения всех символов до тех пор, пока не будет найден конечный разделитель. Все вложенные кавычки экранируются с помощью escape-символа (по умолчанию — «\\»).
Разделители существуют только в объекте потока; они отсутствуют в извлеченной строке, но находятся в строке, возвращаемой функцией .
Обработка пробелов операциями вставки и извлечения не зависит от способа представления строки в коде, поэтому заключение оператора в кавычки будет полезно в любом случае, независимо от того, является входная строка необработанным строковым литералом или обычной строкой. Входная строка любого формата может содержать внедренные кавычки, разрывы строк, табуляции и т. д., и все они будут сохранены манипулятором.
Дополнительные сведения и полные примеры кода см. в разделе .
Когда использовать встраиваемые функции
Встроенные функции лучше использовать для небольших функций, например функций доступа к закрытым элементам данных. Основной целью таких функций метода доступа является возврат сведений о состоянии объектов. Короткие функции чувствительны к издержек вызовов функций. Более длинные функции тратят пропорционально меньше времени в вызове и возвращают последовательность и выделяют меньше от встраивания.
Класс можно определить следующим образом:
Предполагается, что обработка координат является относительно распространенной операцией в клиенте такого класса, указывая две функции доступа ( и в предыдущем примере), как правило, позволяет экономить издержки:
-
вызовы функций (включая передачу параметров и размещение адреса объекта в стеке);
-
сохранение кадра стека вызывающего объекта;
-
Настройка нового кадра стека
-
передачу возвращаемого значения;
-
Восстановление старого кадра стека
-
Возвращает
Определения типов
Объявление вводит имя, которое в пределах его области видимости преобразуется в синоним для типа, заданного в объявлении .
Объявления typedef можно использовать для создания более коротких или более понятных имен для типов, уже определенных в языке или объявленных пользователем. Имена typedef позволяют инкапсулировать детали реализации, которые могут измениться.
В отличие от объявлений, , и , объявления не предоставляют новые типы — они представляют новые имена для существующих типов.
Имена, объявленные с помощью , занимают то же пространство имен, что и другие идентификаторы (кроме меток операторов). Таким образом, в них не может использоваться тот же идентификатор, что и в объявленном ранее имени, за исключением случаев, когда они находятся в объявлении типа класса. Рассмотрим следующий пример.
Правила скрытия имен, относящиеся к другим идентификаторам, также управляют видимостью имен, объявленных с помощью . Поэтому следующий код допустим в C++:
При объявлении в локальной области идентификатора с тем же именем, что и имя typedef, или при объявлении члена структуры либо объединения в той же области или во внутренней области обязательно должен указываться спецификатор типа. Пример:
Чтобы повторно использовать имя для идентификатора, члена структуры или члена объединения, необходимо указать тип:
Недостаточно написать
поскольку воспринимается как часть типа, а не как заново объявляемый идентификатор. Это объявление недопустимо, как и
С помощью typedef можно объявить любой тип, включая типы указателей, функций и массивов. Имя typedef для типа указателя на структуру или объединение можно объявить до определения типа структуры или объединения, если только определение находится в той же области видимости, что и объявление.
Примеры
Использование объявлений состоит в том, чтобы сделать объявления более однородными и компактными. Пример:
Чтобы использовать для указания фундаментальных и производных типов в одном объявлении, можно разделить деклараторы запятыми. Пример:
В следующем примере задан тип для функции, не возвращающей никакого значения и принимающей два аргумента int.
После оператора выше объявление
будет эквивалентно следующему:
часто объединяется с для объявления и именования определяемых пользователем типов:
Повторное объявление определений типов
Объявление можно использовать для повторного объявления того же имени для ссылки на один и тот же тип. Пример:
Программа Prog. CPP включает два файла заголовка, оба из которых содержат объявления для имени . Если в обеих объявлениях указывается один и тот же тип, такое повторное объявление допустимо.
Невозможно переопределить имя, которое ранее было объявлено как другой тип. Таким образом, если file2. H содержит
компилятор выдает ошибку из-за попытки повторного объявления имени как имени другого типа. Это правило распространяется также на конструкции, подобные следующим:
определения типов в C++ и C
Использование спецификатора с типами классов в основном поддерживается из-за объявления неименованных структур в объявлениях в ANSI C . Например, многие программисты C используют следующий код.
Преимущество такого объявления заключает в том, что можно выполнять объявления
вместо
В C++ разница между именами и реальными типами (объявленными с ключевыми словами,, и) более Разна. Хотя методика C объявления структуры без имени в инструкции по-прежнему работает, она не предоставляет преимуществ для нотаций, как это делается в c.
В предыдущем примере объявляется класс с именем с использованием синтаксиса неименованного класса . считается именем класса, однако к именам, предоставленным таким образом, применяются следующие ограничения.
-
Имя (синоним) не может использоваться после префикса, или.
-
Имя не может использоваться в качестве имени конструктора или деструктора в объявлении класса.
Таким образом, этот синтаксис не предоставляет механизм наследования, создания или удаления.
Одновременное чтение и запись в файл с помощью fstream
Класс (почти) способен одновременно читать содержимое файла и записывать данные в него! Нюанс заключается в том, что вы не можете произвольно переключаться между чтением и записью файла. Как только начнется чтение или запись файла, то единственным способом переключиться между чтением или записью будет выполнение операции, которая изменит текущее положение файлового указателя (например, поиск данных). Если вы не хотите перемещать файловый указатель (потому что он уже находится в нужном месте), то вы можете просто выполнить поиск текущих данных (на которые указывает файловый указатель):
// Предположим, что iofile является объектом класса fstream
iofile.seekg(iofile.tellg(), ios::beg); // перемещаемся к текущей позиции файлового указателя
1 |
// Предположим, что iofile является объектом класса fstream iofile.seekg(iofile.tellg(),ios::beg);// перемещаемся к текущей позиции файлового указателя |
Теперь давайте напишем программу, которая откроет файл, прочитает его содержимое и заменит все найденные гласные буквы на символ :
#include <iostream>
#include <fstream>
#include <cstdlib> // для использования функции exit()
int main()
{
using namespace std;
// Мы должны указать как in, так и out, поскольку используем fstream
fstream iofile(«SomeText.txt», ios::in | ios::out);
// Если мы не можем открыть iofile,
if (!iofile)
{
// то выводим сообщение об ошибке и выполняем функцию exit()
cerr << «Uh oh, SomeText.txt could not be opened!» << endl;
exit(1);
}
char chChar;
// Пока есть данные для обработки
while (iofile.get(chChar))
{
switch (chChar)
{
// Если мы нашли гласную букву,
case ‘a’:
case ‘e’:
case ‘i’:
case ‘o’:
case ‘u’:
case ‘A’:
case ‘E’:
case ‘I’:
case ‘O’:
case ‘U’:
// то перемещаемся на один символ назад относительно текущего местоположения файлового указателя
iofile.seekg(-1, ios::cur);
// Поскольку мы выполнили операцию поиска, то теперь можем переключиться на запись данных в файл.
// Заменим найденную гласную букву символом #
iofile << ‘#’;
// Теперь нам нужно вернуться назад в режим чтения файла.
// Выполняем функцию seekg() к текущей позиции
iofile.seekg(iofile.tellg(), ios::beg);
break;
}
}
return 0;
}
1 |
#include <iostream> intmain() { usingnamespacestd; // Мы должны указать как in, так и out, поскольку используем fstream fstream iofile(«SomeText.txt»,ios::in|ios::out); // Если мы не можем открыть iofile, if(!iofile) { // то выводим сообщение об ошибке и выполняем функцию exit() cerr<<«Uh oh, SomeText.txt could not be opened!»<<endl; exit(1); } charchChar; // Пока есть данные для обработки while(iofile.get(chChar)) { switch(chChar) { // Если мы нашли гласную букву, case’a’ case’e’ case’i’ case’o’ case’u’ case’A’ case’E’ case’I’ case’O’ case’U’ // то перемещаемся на один символ назад относительно текущего местоположения файлового указателя iofile.seekg(-1,ios::cur); // Поскольку мы выполнили операцию поиска, то теперь можем переключиться на запись данных в файл. // Заменим найденную гласную букву символом # iofile<<‘#’; // Теперь нам нужно вернуться назад в режим чтения файла. // Выполняем функцию seekg() к текущей позиции iofile.seekg(iofile.tellg(),ios::beg); break; } } return; } |
Результат выполнения программы (содержимое файла SomeText.txt):
Другие полезные методы классов файлового ввода/вывода в языке C++:
remove() — удаляет файл;
is_open() — возвращает , если поток в данный момент открыт, и — если закрыт.
std::cout
Библиотека содержит несколько предопределенных переменных, которые мы можем использовать. Одной из наиболее полезных является , которая позволяет нам отправлять данные в консоль для печати в виде текста. означает «character output» (вывод символов).
Вспомним нашу программу Hello world:
В эту программу мы включили , чтобы у нас был доступ к . Внутри нашей функции мы используем вместе с оператором вставки () для отправки текста «Hello world!» в консоль для печати.
может печатать не только текст, но и числа:
Это дает результат:
Его также можно использовать для вывода значений переменных:
Это дает результат:
Чтобы напечатать несколько элементов в одной строке, для объединения (связывания) нескольких частей выводимых данных, оператор вставки () можно использовать несколько раз в одном выражении. Например:
Эта программа печатает:
Вот еще один пример, в котором мы печатаем и текст, и значение переменной в одном выражении:
Эта программа печатает:
Явные преобразования (приведения)
С помощью операции приведения можно указать компилятору преобразовать значение одного типа в другой тип. В некоторых случаях компилятор вызовет ошибку, если эти два типа полностью не связаны, но в других случаях не вызывает ошибку, даже если операция не является строго типизированной
Используйте приведение с осторожностью, так как любое преобразование из одного типа в другой является потенциальным источником ошибок программы. Однако иногда требуется выполнить приведения, а не все приведения являются опасными
Одно эффективное использование приведения заключается в том, что в коде выполняется понижающие преобразования и известно, что преобразование не приводит к созданию неверных результатов в программе. Фактически, это говорит компилятору о том, что вы делаете, а также о том, что вы выполняете предупреждения. Другой способ заключается в приведении из класса указателя на класс, производный от указатель на базовый. Другой способ — приведение к переменной постоянной, чтобы передать ее в функцию, для которой требуется аргумент, не являющийся константой. Большинство этих операций приведения к некоторым рискам требует определенного риска.
В программировании в стиле C для всех типов приведений используется один и тот же оператор приведения в стиле C.
Оператор приведения в стиле C идентичен оператору call () и, следовательно, инконспикуаус в коде и легко пропускаться. Оба являются некорректными, так как они трудно распознать на взгляде или найти, и они достаточно разнороды для вызова любой комбинации , и . Понять, что такое приведение старого стиля, действительно может быть трудно и подвержено ошибкам. По всем этим причинам, если требуется приведение, рекомендуется использовать один из следующих операторов приведения в C++, который в некоторых случаях значительно более строго типизирован, и что явно упрощает намерение программирования:
-
, для приведений, которые проверяются только во время компиляции. Возвращает ошибку, если компилятор обнаруживает, что вы пытаетесь выполнить приведение типов, которые полностью несовместимы. Его также можно использовать для приведения между указателями на базовые и производные указатели, но компилятор не всегда может определить, будут ли такие преобразования небезопасны во время выполнения.
Для получения дополнительной информации см. .
-
, для безопасного выполнения приведения указателя на Base к типу, который проверяется средой. Объект является более безопасным , чем для типов, но проверка среды выполнения требует некоторых дополнительных издержек.
Для получения дополнительной информации см. .
-
, для приведения параметра -rvalue характеристики переменной или для преобразования непеременной в значение . Приведение незавершенного использования — rvalue характеристики с помощью этого оператора, как и при использовании приведения в стиле C, за исключением того, что у вас меньше вероятность того, что приведение выполняется случайно. Иногда необходимо выполнить приведение rvalue характеристики переменной, например, чтобы передать переменную в функцию, которая принимает не параметр. В приведенном ниже примере показано, как это сделать.
Для получения дополнительной информации см. .
-
, для приведения между несвязанными типами, такими как тип указателя и .
Примечание
Этот оператор приведения не используется так часто, как другие, и не гарантирует перенос в другие компиляторы.
В следующем примере показано отличие от .
Дополнительные сведения см. в разделе оператор.
std::endl
Как вы думаете, что напечатает следующая программа?
Результат может вас удивить:
Отдельные выражения вывода не приводят к отдельным выводимым строкам в консоли.
Если мы хотим выводить в консоль отдельные выводимые строки, нам нужно указать консоли, когда необходимо переместить курсор на следующую строку.
Один из способов сделать это – использовать . При выводе с помощью , выводит в консоль символ новой строки (заставляя курсор переместиться в начало следующей строки). В этом контексте означает «end line» (конец строки).
Например:
Это печатает:
Совет
В приведенной выше программе второй технически не нужен, так как программа сразу же после этого завершается. Однако он служит двум полезным целям: во-первых, он помогает указать, что строка вывода является «законченной мыслью». Во-вторых, если мы позже захотим добавить дополнительные выражения вывода, нам не нужно будет изменять существующий код. Мы можем просто добавить новые выражения.
Математические функции
Математические функции хранятся в стандартной библиотеке math.h. Аргументы большинства математических функций имеют тип double. Возвращаемое значение также имеет тип double.
Углы в тригонометрических функциях задаются в радианах.
Основные математические функции стандартной библиотеки.
Функция | Описание |
int abs(int x) | Модуль целого числа x |
double acos(double x) | Арккосинус x |
double asin(double x) | Арксинус x |
double atan(double x) | Арктангенс x |
double cos(double x) | Косинус x |
double cosh(double x) | Косинус гиперболический x |
double exp(double x) | Экспонента x |
double fabs(double x) | Модуль вещественного числа |
double fmod(double x, double y) | Остаток от деления x/y |
double log(double x) | Натуральный логарифм x |
double log10(double x) | Десятичный логарифм x |
double pow(double x, double y) | x в степени y |
double sin(double x) | Синус x |
double sinh(double x) | Синус гиперболический x |
double sqrt(double x) | Квадратный корень x |
double tan(double x) | Тангенс x |
double tanh(double x) | Тангенс гиперболический x |
Язык Си
std::cin
– еще одна предопределенная переменная, определенная в библиотеке . В то время как выводит данные в консоль с помощью оператора вставки (), (что означает «character input», «ввод символов») считывает ввод с клавиатуры с помощью оператора извлечения (). Для дальнейшего использования входные данные должны быть сохранены в переменной.
Попробуйте скомпилировать эту программу и запустить ее самостоятельно. Когда вы запустите программу, строка 6 напечатает «Enter a number: ». Когда код дойдет до строки 10, ваша программа будет ждать, пока вы введете данные. После того, как вы введете число (и нажмете клавишу Enter), введенное вами число будет присвоено переменной . Наконец, в строке 11 программа напечатает «You entered », а затем число, которое вы только что ввели.
Например (я ввел 4):
Это простой способ получить от пользователя ввод с клавиатуры, и в будущем мы будем использовать его во многих наших примерах
Обратите внимание, что вам не нужно использовать ‘\n’ при принятии входных данных, так как пользователю нужно будет нажать клавишу Enter, чтобы его входные данне были принят, а это переместит курсор на следующую строку
Если ваш экран закрывается сразу после ввода числа, обратитесь к уроку «0.8 – Несколько основных проблем C++» для решения этой проблем.
Лучшая практика
Существуют споры о том, нужно ли инициализировать переменную непосредственно перед тем, как передать ей значение, предоставленное пользователем, через другой источник (например, ), поскольку значение, предоставленное пользователем, просто перезапишет значение инициализации. В соответствии с нашей предыдущей рекомендацией о том, что переменные всегда следует инициализировать, лучше всего сначала инициализировать переменную.
Мы обсудим, как обрабатывает недопустимые входные данные в следующем уроке (7.16 – и обработка недопустимых входных данных).
Для продвинутых читателей
Библиотека ввода/вывода C++ не позволяет принимать ввод с клавиатуры без нажатия пользователем клавиши Enter. Но если вам это нужно, то вам придется использовать стороннюю библиотеку. Для консольных приложений мы рекомендуем библиотеку pdcurses. Многие графические пользовательские библиотеки имеют для этого свои собственные функции.
Файловый вывод
Для записи в файл используется класс . Например:
#include <iostream>
#include <fstream>
#include <cstdlib> // для использования функции exit()
int main()
{
using namespace std;
// Класс ofstream используется для записи данных в файл.
// Создаем файл SomeText.txt
ofstream outf(«SomeText.txt»);
// Если мы не можем открыть этот файл для записи данных,
if (!outf)
{
// то выводим сообщение об ошибке и выполняем функцию exit()
cerr << «Uh oh, SomeText.txt could not be opened for writing!» << endl;
exit(1);
}
// Записываем в файл следующие две строки
outf << «See line #1!» << endl;
outf << «See line #2!» << endl;
return 0;
// Когда outf выйдет из области видимости, то деструктор класса ofstream автоматически закроет наш файл
}
1 |
#include <iostream> intmain() { usingnamespacestd; // Класс ofstream используется для записи данных в файл. // Создаем файл SomeText.txt ofstreamoutf(«SomeText.txt»); // Если мы не можем открыть этот файл для записи данных, if(!outf) { // то выводим сообщение об ошибке и выполняем функцию exit() cerr<<«Uh oh, SomeText.txt could not be opened for writing!»<<endl; exit(1); } // Записываем в файл следующие две строки outf<<«See line #1!»<<endl; outf<<«See line #2!»<<endl; return; // Когда outf выйдет из области видимости, то деструктор класса ofstream автоматически закроет наш файл } |
Если вы загляните в каталог вашего проекта (ПКМ по вкладке с названием вашего файла .cpp в Visual Studio > «Открыть содержащую папку»), то увидите файл с именем SomeText.txt, в котором находятся следующие строки:
Обратите внимание, мы также можем использовать метод put() для записи одного символа в файл
Буферизованный вывод
Вывод в языке C++ может быть буферизован. Это означает, что всё, что выводится в файловый поток, не может сразу же быть записанным на диск (в конкретный файл). Это сделано, в первую очередь, по соображениям производительности. Когда данные буфера записываются на диск, то это называется очисткой буфера. Одним из способов очистки буфера является закрытие файла. В таком случае всё содержимое буфера будет перемещено на диск, а затем файл будет закрыт.
Буферизация вывода обычно не является проблемой, но при определенных обстоятельствах она может вызвать проблемы у неосторожных новичков. Например, когда в буфере хранятся данные, а программа преждевременно завершает свое выполнение (либо в результате сбоя, либо путем вызова функции exit()). В таких случаях деструкторы классов файлового ввода/вывода не выполняются, файлы никогда не закрываются, буферы не очищаются и наши данные теряются навсегда. Вот почему хорошей идеей является явное закрытие всех открытых файлов перед вызовом функции exit().
Также буфер можно очистить вручную, используя метод ostream::flush() или отправив std::flush в выходной поток. Любой из этих способов может быть полезен для обеспечения немедленной записи содержимого буфера на диск в случае сбоя программы.
Интересный нюанс: Поскольку также очищает выходной поток, то его чрезмерное использование (приводящее к ненужным очисткам буфера) может повлиять на производительность программы (так как очистка буфера в некоторых случаях может быть затратной операцией). По этой причине программисты, которые заботятся о производительности своего кода, часто используют вместо для вставки символа новой строки в выходной поток, дабы избежать ненужной очистки буфера.