Роль std::ws (пробел) при чтении данных

Данные, сохраненные в моем файле (пробелы добавлены как в начале, так и в конце специально для этого теста):

1 2 3

Загрузка данных с помощью приведенного ниже кода с использованием или без "std::ws" не вызывает никакой разницы. Поэтому меня смущает роль "std::ws", поскольку я видел код, использующий его. Может кто-нибудь объяснить немного? Спасибо!

void main ()
{
ifstream inf; 
inf.open ("test.txt"); 

double x=0, y=0, z=0;
string line;

getline(inf, line);
istringstream iss(line);
//Using "std::ws" here does NOT cause any difference
if (!(iss >> std::ws >> x >> y >> z >> std::ws))
{
    cout << "Format error in the line" << endl;
}
else
{
    cout << x << y << z << endl;
}
iss.str(std::string ());
iss.clear();

cin.get();

}

person H. Wang    schedule 03.09.2015    source источник
comment
Пробелы удаляются в начале извлечения по умолчанию. Если у вас включен std::ios_base::noskipws или вы используете неформатированный ввод, то std::ws может быть полезен.   -  person 0x499602D2    schedule 03.09.2015
comment
@ 0x499602D2 Флаг называется skipws; noskipws — это функция-манипулятор.   -  person Potatoswatter    schedule 03.09.2015
comment
@ 0x499602D2: основное использование не столько при отключении std::ios_base::skipws, сколько при неформатированном вводе, который не пропускает начальные пробелы.   -  person Dietmar Kühl    schedule 03.09.2015


Ответы (3)


Основное использование std::ws — это переключение между форматированным и неформатированным вводом:

  • форматированный ввод, то есть обычные операторы ввода, использующие значение `in >>, пропускают начальные пробелы и останавливаются всякий раз, когда формат заполняется
  • неформатированный ввод, например, std::getline(in, value) не пропускает начальные пробелы

Например, при чтении age и fullname у вас может возникнуть соблазн прочитать это так:

 int         age(0);
 std::string fullname;
 if (std::cin >> age && std::getline(std::cin, fullname)) { // BEWARE: this is NOT a Good Idea!
     std::cout << "age=" << age << "  fullname='" << fullname << "'\n";
 }

Однако, если бы я ввел эту информацию, используя

47
Dietmar Kühl

Это напечатало бы что-то вроде этого

age=47 fullname=''

Проблема в том, что новая строка после 47 все еще присутствует и сразу заполняет запрос std::getine(). В результате вы предпочитаете использовать этот оператор для чтения данных

if (std::cin >> age && std::getline(std::cin >> std::ws, fullname)) {
    ...
}

Использование std::cin >> std::ws пропускает пробелы, в частности новую строку, и продолжает чтение там, где вводится фактическое содержимое.

person Dietmar Kühl    schedule 03.09.2015
comment
Это не похоже на допустимый ввод. Поля должны быть разделены известным символом, символом новой строки или табуляции, и вы переходите к следующему полю, используя ignore. - person Potatoswatter; 03.09.2015
comment
@Potatoswatter: что недопустимо? Код и ввод как есть работают полностью так, как ожидалось. Если вам так удобнее, предположим, что условие if() записывается как if (std::cout << "Enter your age: " && std::cin >> age && std::cout << "Enter your full name: " && std::getine(std::cin >> std::ws, fullname)) { ... } - person Dietmar Kühl; 03.09.2015
comment
Я имею в виду, что это странная программа, которой все равно, появляется ли элемент ввода на новой строке или нет. По сути, вы используете >> ws, чтобы убедиться, что новая строка обрабатывается так же, как табуляция или пробел. Мне кажется подозрительным. - person Potatoswatter; 03.09.2015
comment
Если ввод 47 Dietmar Kühl, то >> ws по-прежнему необходимо для удаления начального пробела. Это может быть более наглядным примером. - person Potatoswatter; 03.09.2015
comment
@Potatoswatter: в противном случае я бы счел более подозрительным пропускать символы! Например, если вы считаете, что будете использовать std::cin.ignore(), я начну с ввода " \n", а если вы затем ignore() до конца строки, я введу "47 Dietmar Kühl" дальше! Пропуск только пробела, в том числе, по общему признанию, столько новых строк, сколько введено до непробельного, кажется лучшим вариантом, когда данные потенциально вводятся вручную. Конечно, если файл отформатирован определенным образом, например, с использованием JSON или CSV, ни один из этих подходов не применим. - person Dietmar Kühl; 03.09.2015
comment
@Potatoswatter: кроме того, форматированный ввод также рассматривает пробелы, табуляции и новую строку как пробелы, т. Е. Вы можете вводить столько пробелов и новой строки, сколько хотите, например, int читается (при условии, что std::ios_base::skipws установлено). - person Dietmar Kühl; 03.09.2015
comment
Да, я больше думал о CSV. Существует континуум от гибкого ввода командной строки до строгих форматов сетевых сообщений. Но попытка приспособить хитрых пользователей — это скользкий путь. Кстати, под skipws ваш пример " \n47 Dietmar Kühl" допустим. Однако обычно программа, ориентированная на строки, использует getline для извлечения, повторной буферизации и проверки того, что символы новой строки не использовались в качестве обычных разделителей полей. - person Potatoswatter; 03.09.2015

По умолчанию установлен бит skipws потока, а пробелы автоматически пропускаются перед каждым вводом. Если вы отключите его с помощью iss >> std::noskipws, вам понадобится iss >> std::ws позже.

Также бывают случаи, когда пробелы не пропускаются автоматически. Например, чтобы определить конец ввода, вы можете использовать if ( ( iss >> std::ws ).eof() ).

person Potatoswatter    schedule 03.09.2015
comment
Благодарю вас! Это очень полезно! - person H. Wang; 03.09.2015

skipws и noskipws являются липкими, но ws НЕ является липким, поэтому, если вы хотите пропустить пробелы с помощью ws, вы должны использовать его перед каждым оператором>>.

Также обратите внимание, что skipws и noskipws применяются только к операции форматированного ввода, выполняемой с помощью оператора>> в потоке. но ws применяется как к операции форматированного ввода (с использованием оператора>> ), так и к операции неформатированного ввода (например, get, put, putback , .... )

#include <iostream>
#include <sstream>
#include <string>
using namespace std;

int main ()
{
    char c;
    istringstream input( "     1test      2test       " );

    input >> skipws;
    c = input.peek();
    //skipws doesn't skip whitespaces in unformatted input
    cout << "After using skipws c = " << c << endl;

    input >> ws;
    c = input.peek();
    cout << "After using ws c = " <<  c << endl;
}

Выход:

After using skipws c =  
After using ws c = 1
person Hani Shams    schedule 27.02.2018