Прежде всего. Ансер дан, очень хороший и адекватный по уровню опыта и принятый. Так что все хорошо.
Я хотел бы дать дополнительный ответ, чтобы показать вам, как более современное решение C ++ с использованием C ++ 17 и как оно будет выглядеть. Алгоритм базового c такой же, как в ответе на запрос.
Сначала, пожалуйста, посмотрите код:
std::vector<double> averager(const std::string& fileName) {
// The resulting data. A vector with all average values per line
std::vector<double> result{};
// Open the file and check, if it could be opened.
if (std::ifstream sourceFileStream(fileName); sourceFileStream) {
// Now read all lines of the file in a simple for loop
for (std::string line{}; std::getline(sourceFileStream, line); ) {
// Put the just read line into an std::istringstream for easier extraction
std::istringstream iss{line};
// Construct a std::vector value using its range constructor and get all ints in the iss
std::vector values(std::istream_iterator<int>(iss), {});
// Calculate the average and store it in our result vector
result.emplace_back(std::accumulate(values.begin(), values.end(), 0.0) / values.size());
}
}
else {
std::cerr << "\n*** Error: Could not open source file '" << fileName << "'\n";
}
return result;
}
const std::string fileName{"r:\\temp.dat"};
int main() {
// Read all averages from file
std::vector avr{ averager(fileName) };
// Show the result to the user
std::copy(avr.begin(), avr.end(), std::ostream_iterator<double>(std::cout, "\n"));
return 0;
}
Хорошо, тогда давайте посмотрим на него. Сначала мы видим много комментариев. Очень важно писать комментарии. Это поможет вам понять, что вы делаете. Сейчас и позже. Это даже предотвратит ошибки. И качество кода увеличивается. Качество кода без комментариев - НОЛЬ.
Далее вы увидите, что я изменил сигнатуру функции. Я return
результат. Раньше люди думали, что возвращать большие или сложные данные нехорошо. Но в C ++ теперь у нас есть RVO (оптимизация возвращаемого значения) и копия elision.
С RVO и Copy elision вы можете и должны возвращать «по значению». Даже для супер больших объектов. Пожалуйста, посмотрите также здесь и здесь
Далее, "if-Statement".
у нас здесь if с инициализатором . Это доступно начиная с C ++ 17. Вы можете (в дополнение к условию) определить переменную и инициализировать ее. Итак, в
if (std::ifstream sourceFileStream(fileName); sourceFileStream) {
мы сначала определяем переменную с именем «sourceFileStream» типа std::ifstream
. Мы используем единый инициализатор "{}", чтобы инициализировать его именем входного файла.
Это вызовет конструктор для переменной «sourceFileStream» и откроет файл. (Это был конструктор). После закрытия "}" оператора if переменная sourceFileStream выйдет из области видимости и будет вызван деструктор для std::ifstream
. Это автоматически закроет файл.
Этот тип оператора if был введен для предотвращения загрязнения пространства имен. Переменная должна быть видна только в области видимости, где она используется. Без этого вам бы пришлось определить std::ifstream
вне (до) if, и он был бы видим для внешнего контекста, и файл был бы закрыт в очень позднее время. Итак, пожалуйста, ознакомьтесь с этим.
Следующее и аналогичное утверждение while. Мы не объявляем переменную в области видимости out, затем запускаем время l oop, где мы используем объявленную переменную только во внутренней области видимости while
. Возможно, вы слышали, что for
и while
могут быть заменены из-за идентичной функциональности. Но с for
у вас есть возможность иметь инициализатор в качестве первого элемента. Следовательно, по этой причине использование for
является более рекомендуемым решением на данный момент.
Так что l oop будет выглядеть как
// Now read all lines of the file in a simple for loop
for (std::string line{}; std::getline(sourceFileStream, line); ) {
Это полностью то же самое, что и
// Now read all lines of the file in a simple for loop
std::string line{};
while (std::getline(sourceFileStream, line)) {
, но без загрязнения пространства имен и for
- это только одна строка.
Давайте более подробно рассмотрим условие-условие for
-этажа ( или пока). Обычно мы ожидаем некоторого логического условия или логического значения или логического результата функции. Здесь это работает, потому что std::getline
возвращает поток, с которым работал, поэтому ссылка на "". И поток имеет перезаписанный логический оператор!, Чтобы проверить состояние потока. Пожалуйста, смотрите здесь . Таким образом, если возникает ошибка (или «конец файла»), условие будет ложным.
Вы должны всегда и для каждой проверки операций ввода-вывода, работала она или нет.
Итак, с for
мы читаем исходный файл построчно. И каждую строку мы поместим в std :: istringstream , чтобы иметь возможность извлекать значения из него, как при помощи стандартного оператора ">>" (экстрактор).
Следующая строка это:
// Construct a std::vector "value" using its range constructor and get all ints in the iss
std::vector values(std::istream_iterator<int>(iss), {});
Мм. Что это такое. Давайте начнем с std :: istream_iterator . Если вы прочтете связанное описание, то обнаружите, что оно будет вызывать оператор экстрактора >> для указанного типа. И поскольку это итератор, он будет вызывать его снова и снова, если итератор увеличивается. Ок, понятно, но потом. , ,
Мы определяем значения переменной как std::vector
и вызываем ее конструктор с 2 аргументами. Этот конструктор является так называемым конструктором диапазона std::vector
. Пожалуйста, смотрите описание для конструктор (5) . Ага, он получает итератор begin () и итератор end (). Хорошо, но что это за странный {} вместо "end ()" - итератор. Это инициализатор по умолчанию (см. здесь и здесь . И если мы посмотрим на описание std::istream_iterator
, мы увидим, что по умолчанию используется конечный итератор.
Пожалуйста, обратите внимание: поскольку мы используем C ++ 17, мы можем определить std::vector
для «значений» без аргумента шаблона, поэтому не std::vector<int> values
, а просто std::vector values
, без <int>
. Компилятор может вывести аргумент из заданных параметров функции. Эта функция называется CTAD («дедукция аргумента шаблона класса»). Следовательно, мы будем использовать ее позже в функции main.
Ага, вот как это работает .
Последнее важное утверждение функции:
// Calculate the average and store it in our result vector
result.emplace_back(std::accumulate(values.begin(), values.end(), 0.0) / values.size());
Сначала мы не создаем временное значение, а затем копируем его данные в вектор. Используем std::veoctor
s * Функция 1096 *, для создания на месте значения. Это сохраняет ненужные операции копирования.
В конце мы возвращаем результат и все для функции.
В основном мы де Изобразите переменную avr (снова используя CTAD) и инициализируйте ее результатом нашей функции. Все данные будут считаны и рассчитаны с помощью этой простой строки.
И, наконец, что не менее важно, мы copy
все данные передадим в `` `std :: cout````, используя std: : ostream_iterator .
Итак, я надеюсь, что смогу немного объяснить вам, что вы могли бы сделать с современным C ++