Как извлечь смешанный формат с помощью istringstream

Почему моя программа не выводит:

10
1.546
,Apple 1

вместо

10
1
<empty space>

вот моя программа:

#include <iostream>
#include <string>
#include <sstream>

using namespace std;

int main () {
    string str = "10,1.546,Apple 1";
    istringstream stream (str);
    int a;
    double b;
    string c, dummy;
    stream >> a >> dummy >> b >> dummy >> c;
    cout << a << endl;
    cout << b << endl;
    cout << c << endl;
    return 0;
}

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


person Sunil Kundal    schedule 16.02.2014    source источник
comment
char dummy исправит это (второй съедает ввод)   -  person    schedule 16.02.2014
comment
@DieterLücking string dummy; д'ой. Я смотрел на код как идиот и не видел его :)   -  person jrok    schedule 16.02.2014
comment
@DieterLücking да, он улучшился до вывода 10 и 1,546, но там, где мне нужно Apple 1, и я ничего не получаю, теперь я получаю Apple, но все еще не Apple 1. Любые идеи?   -  person Sunil Kundal    schedule 16.02.2014
comment
@SunilKundal Извлечение останавливается на месте между Apple и 1. Вам нужно использовать std::getline() (конечно, после очистки новой строки).   -  person 0x499602D2    schedule 16.02.2014


Ответы (4)


В IOStreams строки (имеются в виду как C-строки, так и строки C++) практически не требуют форматирования. Любые и все символы извлекаются в строку только до тех пор, пока не будет найден пробельный символ или пока не будет перехвачен конец потока. В вашем примере вы используете строку, предназначенную для заполнения запятых между важными данными, но результат, который вы испытываете, является результатом поведения, которое я только что объяснил: строка dummy не просто поглощает запятую, но также остальная часть последовательности символов до следующего символа пробела.

Чтобы избежать этого, вы можете использовать char для фиктивной переменной, в которой есть место только для одного символа. И если вы хотите поместить Apple 1 в строку, вам понадобится неформатированное извлечение, потому что форматированное извлечение operator>>() читает только до пробела. Подходящей функцией для использования здесь является std::getline():

string c;
char dummy;

if ((stream >> a >> dummy >> b >> dummy) &&
     std::getline(stream >> std::ws, s))
//   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
{

}

Очистка новой строки после форматированного извлечения также необходима, поэтому я использовал std::ws для очистки начального пробела. Я также использую оператор if для содержания извлечения, чтобы определить, удалось оно или нет.


Любой более плавный способ сделать это очень помог бы мне.

Вы можете установить классификацию символа запятой как символ пробела, используя фасет std::ctype<char> языкового стандарта, встроенного в поток. Это сделает ненужным использование фиктивной переменной. Вот пример:

namespace detail
{
    enum options { add, remove };

    class ctype : public std::ctype<char>
    {
    private:
        static mask* get_table(const std::string& ws, options opt)
        {
            static std::vector<mask> table(classic_table(),
                                           classic_table() + table_size);
            for (char c : ws)
            {
                if (opt == add)
                    table[c] |= space;
                else if (opt == remove)
                    table[c] &= ~space;
            }
            return &table[0];
        }
    public:
        ctype(const std::string& ws, options opt)
            : std::ctype<char>(get_table(ws, opt)) { }
    };
}

class adjustws_impl
{
public:
    adjustws_impl(const std::string& ws, detail::options opt) :
        m_ws(ws),
        m_opt(opt)
    { }

    friend std::istream& operator>>(std::istream& is,
                                    const adjustws_impl& manip)
    {
        const detail::ctype* facet(new detail::ctype(manip.m_ws, manip.m_opt));

        if (!std::has_facet<detail::ctype>(is.getloc())
        {
            is.imbue(std::locale(is.getloc(), facet));
        } else
            delete facet;

        return is;
    }
private:
    std::string m_ws;
    detail::options m_opt;
};

adjustws_impl setws(const std::string& ws)
{
    return adjustws_impl(ws, detail::add);
}

adjustws_impl unsetws(const std::string& ws)
{
    return adjustws_impl(ws, detail::remove);
}

int main()
{
    std::istringstream iss("10,1.546,Apple 1");
    int a; double b; std::string c;

    iss >> setws(","); // set comma to a whitespace character

    if ((iss >> a >> b) && std::getline(iss >> std::ws, c))
    {
        // ...
    }

    iss >> unsetws(","); // remove the whitespace classification
} 
person 0x499602D2    schedule 16.02.2014

Позвольте мне предложить следующее.

Я не считаю это «гладким», так как диалог cin / cout не является «гладким», имхо.

Но я думаю, что это может быть ближе к тому, что вы хотите.

 int main (int, char**)
 {
    // always initialize your variables 
    // to value you would not expect from input        
    int            a = -99;
    double         b = 0.0;
    std::string    c("");
    char comma1 = 'Z';
    char comma2 = 'z';

    std::string str = "10,1.546,Apple 1";
    std::istringstream ss(str);

    ss >> a >> comma1 >> b >> comma2;

    // the last parameter has the default delimiter in it
    (void)getline(ss, c, '\n');  // to get past this default delimiter, 
                                 // specify a different delimiter

    std::cout << std::endl;
    std::cout << a << "   '" << comma1 <<  "'   " << std::endl;
    std::cout << b << "   '" << comma2 <<  "'   " << std::endl;
    std::cout << c << std::endl;

    return 0;
 }

Результаты: (и, конечно же, вам не нужно ничего делать с запятыми.)

10 ','
1,546 ','
Яблоко 1

person 2785528    schedule 16.02.2014
comment
Мне нравится этот подход.. и проще. Спасибо Дуглас. - person Sunil Kundal; 18.02.2014

Вы должны сделать следующие изменения:

string str = "10  1.546 Apple 1";

А также

 stream >> a >> b >> dummy >> c;

В вашем примере манекен получил бы строку ",1.546,Apple" . Поскольку до тех пор, пока не встретится нечисловой символ, он передается в переменную a. После этого все добавляется к фиктивному (строка), пока не будет достигнут разделитель по умолчанию (пробел).

person Nipun Talukdar    schedule 16.02.2014
comment
Вы должны объяснить, почему. - person jrok; 16.02.2014

Мне удалось немного изменить свой код. Еще не реализовал метод 0x499602D2, но вот что у меня сработало.

#include <iostream>
#include <string>
#include <cstdlib>
#include <sstream>

using namespace std;

int main () {
    string str = "10,1.546,Apple 1";
    istringstream stream (str);
    int a;
    double b;
    string c;
    string token;
    while (getline (stream, token, ',')) {
        if (token.find (".") == string::npos && token.find (" ") == string::npos) {
            a = atoi (token.c_str ());
        } else if (token.find (".") != string::npos) {
            b = atof (token.c_str ());
        } else {
            c = string (token);
        }
    }
    cout << a << endl;
    cout << b << endl;
    cout << c << endl;
    return 0;
}
person Sunil Kundal    schedule 16.02.2014