Проверка неисправности символа новой строки, когда в качестве входных данных используются целые числа - PullRequest
0 голосов
/ 29 марта 2020

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

void averager(vector<double> &outvec)
{
    ifstream temp;
    vector<int> tempvec;
    double average;

    temp.open("temp.dat");

    int num;

    while (! temp.eof())
    {
        temp >> num;

        if (num != 9999)
            tempvec.push_back(num);

        else if (num == 9999)
        {
           average = accumulate(tempvec.begin(), tempvec.end(), 0.0) / tempvec.size();

           outvec.push_back(average);
           tempvec.clear();
        }
    }

    temp.close();

    return;
}

Я беру входные данные из временного файла, который содержит наборы целых чисел, в данном случае результаты тестов, разделенные пробелами и заканчивающиеся целым числом 9999 в качестве разделителя. Когда он попадает в разделитель, он останавливает усреднение целых чисел, помещает новое число в вектор outvec и снова начинает со следующей строки.

Я хотел бы использовать только символ '\n' в качестве разделителя, но не похоже, что я могу проверить, равна ли моя переменная num символу, предпочтительно символу новой строки. Мой метод введения странного целого числа в конце строки работает, но мне это не нравится.

Извините, если этот код ужасен или этот вопрос глуп; Я новичок, и я хочу, чтобы это был хороший код. Заранее спасибо!

Ответы [ 2 ]

0 голосов
/ 29 марта 2020

Прежде всего. Ансер дан, очень хороший и адекватный по уровню опыта и принятый. Так что все хорошо.

Я хотел бы дать дополнительный ответ, чтобы показать вам, как более современное решение 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 ++

0 голосов
/ 29 марта 2020

Для этого можно использовать функцию std::getline(). Он читает строку ввода в строку. С этой строкой вы можете затем проанализировать все целые числа.

Для анализа целых чисел вы можете использовать std::istringstream, который действует так же, как std::cin, но использует строку как источник вместо консоли.

#include <sstream>
// ...
std::string line;
while (std::getline(std::cin, line)) {
  std::istringstream sstream(line);
  int temp;
  while (sstream >> temp) {
     tempvec.push_back(temp);
  }
  average = accumulate(tempvec.begin(), tempvec.end(), 0.0) / tempvec.size();
  outvec.push_back(average);
  tempvec.clear();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...