C++
Потоки C ++
Поиск…
замечания
Конструктор по умолчанию std::istream_iterator
создает итератор, который представляет конец потока. Таким образом, std::copy(std::istream_iterator<int>(ifs), std::istream_iterator<int>(), ....
означает копирование с текущей позиции в ifs
до конца.
Строковые потоки
std::ostringstream
- это класс, объекты которого выглядят как поток вывода (то есть вы можете писать им через operator<<
), но на самом деле сохраняете результаты записи и предоставляете их в виде потока.
Рассмотрим следующий короткий код:
#include <sstream>
#include <string>
using namespace std;
int main()
{
ostringstream ss;
ss << "the answer to everything is " << 42;
const string result = ss.str();
}
Линия
ostringstream ss;
создает такой объект. Этот объект сначала обрабатывается как обычный поток:
ss << "the answer to everything is " << 42;
После этого, однако, полученный поток можно получить следующим образом:
const string result = ss.str();
( result
строки будет равен "the answer to everything is 42"
).
Это в основном полезно, когда у нас есть класс, для которого была определена сериализация потока, и для которого мы хотим получить строчную форму. Например, предположим, что у нас есть класс
class foo
{
// All sort of stuff here.
};
ostream &operator<<(ostream &os, const foo &f);
Чтобы получить строковое представление объекта foo
,
foo f;
мы могли бы использовать
ostringstream ss;
ss << f;
const string result = ss.str();
Тогда result
содержит строковое представление объекта foo
.
Чтение файла до конца
Чтение текстового файла по очереди
Правильный способ чтения текстового файла по очереди до конца обычно не ясен из документации ifstream
. Давайте рассмотрим некоторые распространенные ошибки, сделанные начинающими программистами на С ++, и правильный способ прочитать файл.
Строки без пробельных символов
Для простоты предположим, что каждая строка в файле не содержит пробельных символов.
ifstream
имеет operator bool()
, который возвращает true, когда поток не имеет ошибок и готов к чтению. Более того, ifstream::operator >>
возвращает ссылку на сам поток, поэтому мы можем читать и проверять EOF (а также на ошибки) в одной строке с очень элегантным синтаксисом:
std::ifstream ifs("1.txt");
std::string s;
while(ifs >> s) {
std::cout << s << std::endl;
}
Строки с пробельными символами
ifstream::operator >>
считывает поток до появления любого символа пробела, поэтому приведенный выше код будет печатать слова из строки на отдельных строках. Чтобы прочитать все до конца строки, используйте std::getline
вместо ifstream::operator >>
. getline
возвращает ссылку на поток, с которым он работал, поэтому доступен такой же синтаксис:
while(std::getline(ifs, s)) {
std::cout << s << std::endl;
}
Очевидно, std::getline
также следует использовать для чтения однострочного файла до конца.
Чтение файла в буфер сразу
Наконец, давайте прочитаем файл с самого начала и до конца, не останавливаясь ни на каком персонаже, включая пробелы и символы новой строки. Если мы знаем, что точный размер файла или верхняя граница длины допустимы, мы можем изменить размер строки, а затем читать:
s.resize(100);
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
s.begin());
В противном случае нам нужно вставить каждый символ в конец строки, поэтому std::back_inserter
- это то, что нам нужно:
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
std::back_inserter(s));
В качестве альтернативы, можно инициализировать коллекцию данными потока, используя конструктор с аргументами диапазона итератора:
std::vector v(std::istreambuf_iterator<char>(ifs),
std::istreambuf_iterator<char>());
Обратите внимание, что эти примеры также применимы, если ifs
открыт как двоичный файл:
std::ifstream ifs("1.txt", std::ios::binary);
Копирование потоков
Файл может быть скопирован в другой файл с потоками и итераторами:
std::ofstream ofs("out.file");
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
std::ostream_iterator<char>(ofs));
ofs.close();
или перенаправляется на любой другой тип потока с совместимым интерфейсом. Например, сетевой поток Boost.Asio:
boost::asio::ip::tcp::iostream stream;
stream.connect("example.com", "http");
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
std::ostream_iterator<char>(stream));
stream.close();
Массивы
Поскольку итераторы могут рассматриваться как обобщение указателей, контейнеры STL в приведенных выше примерах могут быть заменены на собственные массивы. Вот как разбирать числа в массиве:
int arr[100];
std::copy(std::istream_iterator<char>(ifs), std::istream_iterator<char>(), arr);
Остерегайтесь переполнения буфера, поскольку массивы не могут быть изменены «на лету» после их выделения. Например, если вышеприведенный код будет содержать файл, содержащий более 100 целых чисел, он попытается написать за пределами массива и выполнить неопределенное поведение.
Печать коллекций с помощью iostream
Основная печать
std::ostream_iterator
позволяет печатать содержимое контейнера STL в любой выходной поток без явных циклов. Второй аргумент конструктора std::ostream_iterator
устанавливает разделитель. Например, следующий код:
std::vector<int> v = {1,2,3,4};
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " ! "));
распечатает
1 ! 2 ! 3 ! 4 !
Неявный тип
std::ostream_iterator
позволяет std::ostream_iterator
тип содержимого контейнера. Например, давайте настроим std::cout
для печати значений с плавающей запятой с 3 цифрами после десятичной точки:
std::cout << std::setprecision(3);
std::fixed(std::cout);
и создайте экземпляр std::ostream_iterator
с float
, а содержащиеся значения останутся int
:
std::vector<int> v = {1,2,3,4};
std::copy(v.begin(), v.end(), std::ostream_iterator<float>(std::cout, " ! "));
поэтому приведенный выше код дает
1.000 ! 2.000 ! 3.000 ! 4.000 !
несмотря на то, что std::vector
имеет int
s.
Поколение и трансформация
std::generate
, std::generate_n
и std::transform
- это очень мощный инструмент для манипуляции данными на лету. Например, имея вектор:
std::vector<int> v = {1,2,3,4,8,16};
мы можем легко распечатать логическое значение оператора «x is even» для каждого элемента:
std::boolalpha(std::cout); // print booleans alphabetically
std::transform(v.begin(), v.end(), std::ostream_iterator<bool>(std::cout, " "),
[](int val) {
return (val % 2) == 0;
});
или напечатать квадрат элемента:
std::transform(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " "),
[](int val) {
return val * val;
});
Печать N случайных чисел, разделенных пробелами:
const int N = 10;
std::generate_n(std::ostream_iterator<int>(std::cout, " "), N, std::rand);
Массивы
Как и в разделе о чтении текстовых файлов, почти все эти соображения могут быть применены к встроенным массивам. Например, давайте напечатаем квадратные значения из собственного массива:
int v[] = {1,2,3,4,8,16};
std::transform(v, std::end(v), std::ostream_iterator<int>(std::cout, " "),
[](int val) {
return val * val;
});
Разбор файлов
Разбор файлов в контейнеры STL
istream_iterator
s очень полезны для чтения последовательностей чисел или других анализируемых данных в контейнеры STL без явных циклов кода.
Использование явного размера контейнера:
std::vector<int> v(100);
std::copy(std::istream_iterator<int>(ifs), std::istream_iterator<int>(),
v.begin());
или с вставкой итератора:
std::vector<int> v;
std::copy(std::istream_iterator<int>(ifs), std::istream_iterator<int>(),
std::back_inserter(v));
Обратите внимание, что числа во входном файле могут быть разделены на любое количество символов пробела и символов новой строки.
Разбор гетерогенных текстовых таблиц
Поскольку istream::operator>>
считывает текст до символа пробела, он может использоваться во while
условия для обработки сложных таблиц данных. Например, если у нас есть файл с двумя действительными числами, за которым следует строка (без пробелов) в каждой строке:
1.12 3.14 foo
2.1 2.2 barr
он может быть проанализирован следующим образом:
std::string s;
double a, b;
while(ifs >> a >> b >> s) {
std::cout << a << " " << b << " " << s << std::endl;
}
преобразование
Любая функция управления диапазоном может использоваться с диапазонами std::istream_iterator
. Одним из них является std::transform
, который позволяет обрабатывать данные «на лету». Например, давайте прочитаем целочисленные значения, умножим их на 3.14 и сохраним результат в контейнер с плавающей запятой:
std::vector<double> v(100);
std::transform(std::istream_iterator<int>(ifs), std::istream_iterator<int>(),
v.begin(),
[](int val) {
return val * 3.14;
});