Как читать двузначные и однозначные числа в C ++ - PullRequest
0 голосов
/ 28 марта 2020

У меня проблема с тем, что я не могу заставить свою программу на C ++ читать двойные целые числа git. Моя идея состоит в том, чтобы прочитать его как строку, а затем каким-то образом разобрать его в отдельные целые числа и вставить их в массив, но я застрял при получении кода для правильного чтения цифр.

Пример вывода:

 i: 0 codeColumn 0

 i: 1 codeColumn 1

 i: 2 codeColumn 0 0

 i: 3 codeColumn 0

 i: 4 codeColumn 31 0

 i: 5 codeColumn 1

 i: 6 codeColumn 43 0

 i: 7 codeColumn 3

 i: 8 codeColumn 9 0

Таким образом, файл представляет собой строку триплетов, разделенных запятой:

0,1,0 0,0,31 0,0,18 0,0,8 0,11,0

Мой вопрос: как получить конечные нули (см. Выше) для перехода на новую строку? Я пытался использовать «char» и кучу операторов if, чтобы объединить однозначные числа в двузначные, но я чувствую, что это не совсем эффективно и не идеально. Есть идеи?

Мой код:

#include <iostream>     // Basic I/O
#include <string>       // string classes
#include <fstream>      // file stream classes
#include <sstream>
#include <vector>

int main()
{

    ifstream fCode;
    fCode.open("code.txt"); 
    vector<string> codeColumn;

    while (getline(fCode, codeLine, ',')) {
        codeColumn.push_back(codeLine);
    }

    for (size_t i = 0; i < codeColumn.size(); ++i) {

                cout << " i: " << i << " codeColumn " << codeColumn[i] << endl;

    }

    fCode.close();

}

Ответы [ 2 ]

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

Итак, я покажу вам 3 решения: от простого для понимания кода C -Style, затем более современный код C ++ с использованием библиотеки и итераторов std::algorithm и, наконец, объектно-ориентированное решение C ++.

Я также объясню вам, что std::getline может быть, но следует использовать , а не для разделения строк на токены.

Я понял из твоего вопроса, что тебе было трудно это понять. И я понимаю вашу озабоченность.

Но давайте начнем с простого решения. Я показываю код и затем объясняю его вам:

#include <iostream>
#include <fstream>
#include <string>

int main() {

    // Open the source text file, and check, if there was no failure
    if (std::ifstream fCode{ "r:\\code.txt" }; fCode) {

        size_t tripletCounter{ 0 };

        // Now, read all triplets from the file in a simple for loop
        for (std::string triplet{}; fCode >> triplet; ) {

            // Prepare output
            std::cout << "\ni:\t" << tripletCounter++ << "\tcodeColumn:\t";

            // Go through the triplet, search for comma, then output the parts
            for (size_t i{ 0U }, startpos{ 0U }; i <= triplet.size(); ++i) {

                // So, if there is a comma or the end of the string
                if ((triplet[i] == ',') || (i == (triplet.size()))) {

                    // Print substring
                    std::cout  << (triplet.substr(startpos, i - startpos)) << ' ';
                    startpos = i + 1;
                }
            }
        }
    }
    else {
        std::cerr << "\n*** Error, Could not open source file\n";
    }
    return 0;
}

Видите ли, нам нужно всего несколько строк простого для понимания кода, который удовлетворит ваши требования и даст желаемый результат.

Некоторые, возможно, новые функции:

Оператор if с инициализатором . Это доступно начиная с C ++ 17. Вы можете (в дополнение к условию) определить переменную и инициализировать ее. Итак, в

if (std::ifstream fCode{ "r:\\code.txt" }; fCode) {

мы сначала определяем переменную с именем "fCode" типа std::ifstream. Мы используем единый инициализатор "{}", чтобы инициализировать его именем входного файла.

Это вызовет конструктор для переменной "fCode" и откроет файл. (Это был этот конструктор). После закрывающего «}» оператора if переменная «fCode» выйдет из области видимости и будет вызван деструктор для std::ifstream. Это автоматически закроет файл.

Этот тип оператора if был введен, чтобы помочь предотвратить решение пространства имен. Переменная должна быть видна только в области видимости, где она используется. Без этого вам бы пришлось определить std::ifstream вне (до) if, и он был бы видим для внешнего контекста, и файл был бы закрыт в очень позднее время. Итак, пожалуйста, ознакомьтесь с этим.

Далее мы определяем "tripletCounter". Это необходимо для вывода. Другого использования нет.

Затем снова такой оператор if с initailizer. Сначала мы определяем пустой std::string «триплет», а затем используем оператор экстрактора для чтения текста до следующего пробела. Вот как работает «экстрактор» (>>). Мы используем все выражение в качестве условия, чтобы проверить, была ли извлечена ошибка, или мы достигли конца файла (или какой-либо другой ошибки). Это работает, потому что оператор извлечения возвращает поток, в котором он работал, поэтому ссылка на «fCode». И поток имеет перезаписанный логический оператор!, Чтобы проверить состояние потока. Пожалуйста, смотрите здесь .

Вы должны всегда и при каждой проверке IO-операции проверять, сработала она или нет.

Итак, далее мы разбиваем тройку (например, «0»). , 1,0 ") в его подстроки с очень легко для l oop. Мы go просматриваем все символы в строке и проверяем, является ли текущий символ запятой или концом строки. В этом случае мы выводим символы перед разделителем.

Очень просто и легко понять. std::getline здесь не нужен.


Итак, следующее решение, более продвинутое:

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
#include <regex>

std::regex re(",");

int main() {

    // Open the source text file, and check, if there was no failure
    if (std::ifstream fCode{ "r:\\code.txt" }; fCode) {

        size_t tripletCounter{ 0 };

        // Now, read all triplets from the file into a vector
        std::vector triplets(std::istream_iterator<std::string>(fCode), {});

        // Next, go through all triplets 
        for (const std::string &triplet : triplets) {

            // Prepare output
            std::cout << "\ni:\t" << tripletCounter++ << "\tcodeColumn:\t";

            // Split triplet into code column. All codes are in vector codeColums
            std::vector codeColumns(std::sregex_token_iterator(triplet.begin(), triplet.end(), re, -1), {});

            //Show codes
            for (const std::string& code : codeColumns) std::cout << code << ' ';
        }
    }
    else {
        std::cerr << "\n*** Error, Could not open source file\n";
    }
    return 0;
}

Начало то же самое. Но тогда:

// Now, read all triplets from the file into a vector
std::vector triplets(std::istream_iterator<std::string>(fCode), {});

Мм. Что это такое. Давайте начнем с std :: istream_iterator . Если вы прочтете связанное описание, то обнаружите, что оно будет вызывать оператор экстрактора >> для указанного типа. И поскольку это итератор, он будет вызывать его снова и снова, если итератор увеличивается. Хорошо, понятно, но тогда

Мы определяем триплеты переменных как std::vector и вызываем его конструктор с 2 аргументами. Этот конструктор является так называемым конструктором диапазона std::vector. Пожалуйста, смотрите описание для конструктор 5 . Ага, он получает итератор begin () и итератор end (). Ага, а что это за странный {} вместо "end ()" - итератор. Это инициализатор по умолчанию (см. здесь и здесь . И если мы посмотрим на описание std::istream_iterator, мы увидим, что по умолчанию это конечный итератор. Хорошо, понятно .

Я предполагаю, что вы знаете о диапазоне на основе, который будет следующим. Хорошо. Но теперь мы подошли к самому сложному моменту. Расщепление строки с разделителями. Люди используют std::getline. Но почему? Почему люди делают такие странные вещи?


Что люди ожидают от функции, когда они читают

getline?

Большинство людей сказали бы, Хм, я думаю, он прочитает полную строку откуда-то. И угадайте, что это было основным намерением для этой функции. Прочитайте строку из потока и поместите ее в строку.

Как видите, здесь std::getline имеет некоторые дополнительные функции.

И это привело к серьезному неправильному использованию этой функции для разделения std::string s на токены .

Разделение строк на токены - очень старая задача. В самом начале C существовала функция strtok, которая все еще существует даже в C ++. Пожалуйста, смотрите std::strtok.

Но из-за дополнительной функциональности std::getline он сильно использовался для токенизации строк. Если вы посмотрите на главный вопрос / ответ о том, как анализировать файл CSV (см. здесь ), то вы поймете, что я имею в виду.

Люди используют std::getline для чтения текстовая строка, строка из исходного потока, затем вставка ее в std::istringstream снова и использование std::getline с разделителем снова для разбора строки в токены.

Странно.

Поскольку на протяжении многих лет у нас есть специальная специальная функция для токенизации строк, специально разработанная специально для этой цели. Это

std::sregex_token_iterator

И поскольку у нас есть такая выделенная функция, мы должны просто использовать ее.

Эта вещь является итератором. Для итерации по строке, следовательно, имя функции начинается с s. Начальная часть определяет, с каким диапазоном ввода мы будем работать (begin (), end ()), затем есть std :: regex для того, что должно совпадать / или что не должно совпадать во входной строке. Тип стратегии сопоставления указан с последним параметром.

  • 0 -> дайте мне то, что я определил в регулярном выражении, и
  • -1 -> дайте мне то, что НЕ соответствует на основе регулярного выражения.

Мы можем использовать этот итератор для хранения токенов в std::vector. std::vector имеет конструктор диапазона, который принимает 2 итератора в качестве параметра и копирует данные между первым и вторым итератором в std :: vector. Оператор

std::vector tokens(std::sregex_token_iterator(s.begin(), s.end(), re, -1), {});

определяет переменную «tokens» как std :: vector и снова использует конструктор диапазона std :: vector. Обратите внимание: я использую C ++ 17 и могу определить std::vector без аргумента шаблона. Компилятор может вывести аргумент из заданных параметров функции. Эта функция называется CTAD («дедукция аргумента шаблона класса»). Я также использовал это для вектора выше.

Кроме того, вы можете видеть, что я не использую итератор end () явно.

Этот итератор будет построен из пустой фигурной скобки -Закрытый инициализатор по умолчанию с правильным типом, потому что он будет выведен таким же как тип первого аргумента из-за конструктора std::vector, требующего, как уже описано.

Вы можете прочитать любое число из жетонов в строке и положить его в std::vector

Но вы можете сделать еще больше. Вы можете подтвердить свои данные. Если вы используете 0 в качестве последнего параметра, вы определяете std::regex, который даже проверяет ваш ввод. И вы получаете только действительные токены.

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

Некоторые люди могут жаловаться на издержки функции Но сколько из них используют большие данные. И даже тогда подход, вероятно, будет тогда использовать string.find и string.substring или std::stringviews или что-то еще.

Так что, как-то продвинутый, но вы в конечном итоге выучите это.


А теперь мы будем использовать объектно-ориентированный подход. Как вы знаете, C ++ является объектно-ориентированным языком.

Мы можем поместить данные и методы, работающие с этими данными, в класс (struct). Функциональность заключена в капсулу. Только класс должен знать, как оперировать его данными. Sw, мы определим класс «Код». Содержит std::array, состоящий из 3 st::string с. и связанные функции. Для массива мы сделали typedef для облегчения написания. Функции, которые нам нужны, это ввод и вывод. Итак, мы перезапишем экстрактор и оператор вставки.

В этих операторах мы используем функции, как описано выше.

И в результате всей этой работы мы получаем элегантную основную функцию где вся работа выполняется в 3 строки кода.

Пожалуйста, смотрите:

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
#include <regex>
#include <array>
#include <algorithm>

using Triplet = std::array<std::string, 3>;
std::regex re(",");

struct Code {
    // Our Data
    Triplet triplet{};

    // Overwrite extractor operator for easier input
    friend std::istream& operator >> (std::istream& is, Code& c) {

        // Read a triplet with commans
        if (std::string s{}; is >> s) {

            // Copy the single columns of the triplet in to our internal Data structure
            std::copy(std::sregex_token_iterator(s.begin(), s.end(), re, -1), {}, c.triplet.begin());
        }
        return is;
    }
    // Overwrite inserter for easier output
    friend std::ostream& operator << (std::ostream& os, const Code& c) {
        return os << c.triplet[0] << ' ' << c.triplet[1] << ' ' << c.triplet[2];
    }
};

int main() {

    // Open the source text file, and check, if there was no failure
    if (std::ifstream fCode{ "r:\\code.txt" }; fCode) {


        // Now, read all triplets from the file, split it and put the Codes into a vector
        std::vector code(std::istream_iterator<Code>(fCode), {});

        // Show output
        for (size_t tripletCounter{ 0U }; tripletCounter < code.size(); tripletCounter++)
            std::cout << "\ni:\t" << tripletCounter << "\tcodeColumn:\t" << code[tripletCounter];
    }
    else {
        std::cerr << "\n*** Error, Could not open source file\n";
    }
    return 0;
}
0 голосов
/ 28 марта 2020
getline(fCode, codeLine, ',')

будет читать между запятыми, поэтому 0,1,0 0,0,31 будет разделяться в точности так, как вы видели.

0,1,0 0,0,31
 ^ ^   ^ ^

Собранные токены - это все, что находится между ^ s

У вас есть два разделителя, которые необходимо учитывать запятую и пробел. Самый простой способ обработать пространство - это тупой старый >>.

std::string triplet;
while (fCode >> triplet)
{
    // do stuff with triplet. Maybe something like      
    std::istringstream strm(triplet); // make a stream out of the triplet
    int a;
    int b; 
    int c;
    char sep1;
    char sep2;
    while (strm >> a >> sep1 >> b >> sep2 >> c // read all the tokens we want from triplet 
           && sep1 == sep2 == ',') // and the separators are commas. Triplet is valid
    {
       // do something with a, b, and c
    }
}

Документация для std::istringstream.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...