Итак, ответ дан. Это хорошо, правильно. Он одобрен и принят. Очень хорошо.
В качестве дополнительной информации я покажу решение C ++, использующее более современные языковые элементы. Это решение пропускает промежуточный файл (может быть сгенерирован однострочным), а также не требует использования символа «X» (также может быть добавлено очень просто).
Используя STL, мы можем прийти с решением, от исходного исходного файла до конечного файла назначения в net 5 строках кода.
См.:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <regex>
#include <iterator>
#include <algorithm>
std::regex reFloat{ R"([-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?)" };
using SVector = std::vector<std::string>;
using SVectorIter = SVector::iterator;
int main() {
// Open source file and check, if it coud be opened
if (std::ifstream sourceFileStream{ "r:\\dummyValues.dat" }; sourceFileStream) {
// Open destination file and check, if it could be opened
if (std::ofstream finalFileStream("r:\\benchmarks_final.dat"); finalFileStream) {
// Algorithm start ----------------------------------------------------------
// Define a string variable and initialize it with the contents of the file
std::string completeFile(std::istreambuf_iterator<char>(sourceFileStream), {});
// Define vector and initialize it with all float values from the file
SVector values(std::sregex_token_iterator(completeFile.begin(), completeFile.end(), reFloat), {});
// Iterate over the vector and find the next value equal to first-value
for (SVectorIter svi{ values.begin() }; (svi = std::find(svi, values.end(), values[0])) != values.end(); ++svi) {
// Copy 16 value to the final file
std::copy_n(svi, std::min(16, std::distance(svi, values.end())), std::ostream_iterator<std::string>(finalFileStream, " "));
finalFileStream << '\n';
}
// Algorithm end ----------------------------------------------------------
}
else {
std::cerr << "\n*** Error: Could not open final file\n";
}
}
else {
std::cerr << "\n*** Error: Could not open source file\n";
}
}
Я думаю, что нет, но если вам нужно Если вы заинтересованы в том, чтобы понять это, я вам объясню. Пожалуйста, спрашивайте, я рад помочь.
РЕДАКТИРОВАТЬ Вас интересуют дальнейшие объяснения. Это нормально, но я, конечно, не могу дать здесь полное руководство.
Тогда давайте начнем с некоторых важных функций, которые мы здесь используем. Так называемый constructor
. Вы уже должны это знать. Это специальная функция класса, которая в основном используется для «инициализации» данных класса. Если у вас есть такой класс, как
struct MyClass {
int a;
// Constructor. Very simplified
MyClass(int value) { a = value; }
};
И если вы хотите определить переменную этого типа (создать экземпляр этого типа), вы можете написать:
MyClass test(3);
This вызовет конструктор и присвоит 3 члену класса a
. Вы уже должны в принципе это знать. Если вы будете искать классы в Справочнике по C ++ , вы всегда найдете конструктор для определенных классов.
Хорошо, тогда давайте начнем. Во-первых, мы хотим открыть файл для ввода. Для этого мы будем использовать класс STL std::ifstream
. И мы будем использовать один из его конструкторов . Пожалуйста, посмотрите ссылку. Конструктор 2 принимает имя файла как const char*
и открывает файл. Неявно вызывает open (см. Описание в ссылке). Деструктор, который будет вызван автоматически, когда переменная выпадет из области видимости (После следующего совпадения }
. Деструктор автоматически закроет файл для вас. Это очень хорошо и позволяет избежать ошибок.
Итак, вы можете написать:
// Some previous code here
// . . .
{
// Some previous code here
// . . .
// Open the file
std::ifstream sourceFileStream{ "r:\\dummyValues.dat" };
// more code
// . . .
} // Now the scope is closed. The Variable "sourceFileStream" will fall out of scope
// and the file will be close automatically.
Cool.
Теперь очень важная рекомендация. Вы всегда должны проверять, была ли операция ввода-вывода успешной. Как это сделать. Для этого вы должен знать, что std::stream
имеет своего рода состояние ошибки. И вы можете и должны это проверить. Вы можете прочитать об этих «состояниях» здесь . Если вы открываете файл, и он не работает . Будет установлен failbit
. Это вы можете прочитать здесь . Итак, вы можете проверить бит отказа с помощью оператора:
if (sourceFileStream.rdstate() == std::ios_base::failbit)
Но это как-то сложно. Итак, разработчики классов IO-потока определили для класса оператор bool . И, как вы можете прочитать в описании, он проверяет, нет ли в потоке ошибок. Для оператора if
требуется логическое значение состояние. И я Если вы напишете
if (sourceFileStream) {
, то возвращается логический оператор этой переменной и показывает, есть ли ошибка. Это очень полезно.
Затем, в C ++ 17, был введен новый синтаксис для оператора if . Теперь вы можете определить и инициализировать переменную в операторе if. Например,
if (int inner = 0; x < y) { . . . }
По-прежнему условие x < y
. Кроме того, будет определена переменная "внутренняя", и преимущество состоит в том, что эта переменная видна только во внутренней области действия оператора if
. После закрывающей скобки }
блока if переменная исчезнет.
И теперь мы можем собрать все части вместе:
if (std::ifstream sourceFileStream{ "r:\\dummyValues.dat" }; sourceFileStream) {
Первая часть этого if
оператора определяет переменную «sourceFileStream» и вызывает ее конструктор с именем файла. Итак, он открывает файл. Вторая часть if statement
- это условие, вызывающее оператор bool в «sourceFileStream», чтобы проверить, можно ли открыть файл. И хорошо то, что переменная "sourceFileStream" видна только в блоке if
. Нет необходимости, чтобы он был доступен за пределами этого блока, потому что, если бы файл не мог быть открыт, что бы вы с ним сделали. И, кроме того. После того, как блок if
завершится символом "}", файл будет автоматически закрыт, потому что переменная выпадает из области видимости, что вызовет деструктор.
Вау, это было большим объяснением для одного строка кода.
Следующая строка:
std::string completeFile(std::istreambuf_iterator<char>(sourceFileStream), {});
Ух, ох. Что это такое?
Очевидно, мы определяем переменную с именем «completeFile» с типом данных std::string
. Хорошо, это все еще понятно. А затем у нас есть (...), поэтому вызываем конструктор std::string
s. Пожалуйста, прочтите здесь о конструкторе номер 6. Он говорит:
Создает строку с содержимым диапазона [first, last).
Таким образом, требуется iterator
для первого элемента диапазона и итератор после последнего элемента диапазона. Затем он копирует все символы из этого диапазона в переменную «completeFile». Таким образом, он заполнит std::string
символами, заданными двумя итераторами.
Итак, nect part: std::istreambuf_iterator<char>(sourceFileStream)
. Это итератор begin
диапазона. Пожалуйста, прочтите здесь , что
std :: istreambuf_iterator - это однопроходный итератор ввода, который считывает последовательные символы из объекта std :: basic_streambuf, для которого он был создан.
Итак, эта штука читает последовательные символы из нашего "sourceFileStream". Он будет читать символ после символа из исходного файла и копирует их в нашу строковую переменную. Это происходит до итератора end
, поэтому до {}
. Хм, что это? Это пустой инициализатор по умолчанию в фигурных скобках, и он просто вызывает конструктор по умолчанию. И если вы посмотрите описание конструктора std::istreambuf_iterator
s номер 1 , то увидите, что это будет «итератор конца потока». И при этом все символы из исходного файла, от первого до последнего символа, будут прочитаны и скопированы в нашу std::string
переменную "completeFile".
Круто, правда? Одновременное определение доступного файла и чтение всего исходного файла.
Следующая строка:
std::vector<std::string> values(std::sregex_token_iterator(completeFile.begin(), completeFile.end(), reFloat), {});
Это может быть слишком сложно для этого здесь, и поэтому я могу объяснить только основы . Итак, сначала мы определим переменную с именем «values» типа std::vector`````of
std :: strings . So, we want to store many
std :: strings in a
std :: vector````. Это понятно. И угадайте, что, мы инициализируем эту переменную, используя ее конструктор. И мы снова будем использовать конструктор диапазона, здесь номер конструктора 5 . И это
Создает контейнер с содержимым диапазона [first, last).
То же, что и std::string
выше. Он копирует все строки, на которые указывают итераторы, от первой до последней в наш вектор. Итератор begin
- это std :: sregex_token_iterator . Я не могу объяснить здесь теорию regex
, но просто сказал, что она будет перебирать шаблон похожих элементов, определенных регулярным выражением на «метаязыке», и возвращать их, чтобы их можно было скопировать в наш вектор.
И это шаблон значения с плавающей запятой. Если вы посмотрите на значение с плавающей запятой, вы можете обнаружить закономерность. Он состоит из необязательных знаков + -, затем нескольких цифр, может быть, точки и других цифр. Это всегда одно и то же. И этот шаблон мы описываем с помощью регулярного выражения [-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?
. Вы можете скопировать и вставить это в здесь и поиграть с ним. Тогда вы сможете получить первое понимание.
Вернуться к итератору. Таким образом, он вернет только те строки, которые соответствуют шаблонам. Таким образом, он вернет только строки с плавающей запятой. Он не вернет никаких других символов. Только те, которые вам нужны.
И так же, как и выше, он начинается с первого шаблона в данной строке и заканчивается на {}. Как и выше, конструктор по умолчанию № 1"Создает итератор конца последовательности".
Если вас интересует больше, прокомментируйте, а я объясню остальное