Я хотел бы предложить совершенно другой подход: возьмите свою входную строку, разметьте ее самостоятельно, а затем преобразуйте отдельные поля с помощью boost::lexical_cast<T>
.
Причина: я потратил день на разбор строки, содержащей 2 поля int и 2 поля double, разделенные пробелами. Делаем следующее:
int i, j;
double x, y;
std::istringstream ins{str};
ins >> i >> j >> x >> y;
// how to check errors???...
анализирует правильный ввод, например
`"5 3 9.9e+01 5.5e+02"`
правильно, но не обнаруживает проблему с этим:
`"5 9.6e+01 5.5e+02"`
Что происходит, так это то, что i
будет установлено на 5 (ОК), j
будет установлено на 9 (??), x
на 6,0 (=0,6e+01), y
на 550 (ОК). Я был очень удивлен, увидев, что failbit
не установлен... (информация о платформе: OS X 10.9, Apple Clang++ 6.0, режим C++11).
Конечно, сейчас вы можете сказать: «Но подождите, в стандарте указано, что так и должно быть», и вы можете быть правы, но знание того, что это фича, а не баг, не уменьшает боли, если вы хотите сделать правильную ошибку. проверка без написания миль кода.
OTOH, если вы используете отличную функцию токенизатора "Marius" и разделите str
сначала на пробел, потом вдруг все станет очень просто. Вот немного модифицированная версия токенизатора. Я переписал его, чтобы вернуть вектор строк; оригинал — это шаблон, который помещает токены в контейнер с элементами, преобразуемыми в строки. (Для тех, кому нужен такой общий подход, обратитесь к исходной ссылке выше.)
// \param str: the input string to be tokenized
// \param delimiters: string of delimiter characters
// \param trimEmpty: if true then empty tokens will be trimmed
// \return a vector of strings containing the tokens
std::vector<std::string> tokenizer(
const std::string& str,
const std::string& delimiters = " ",
const bool trimEmpty = false
) {
std::vector<std::string> tokens;
std::string::size_type pos, lastPos = 0;
const char* strdata = str.data();
while(true) {
pos = str.find_first_of(delimiters, lastPos);
if(pos == std::string::npos) {
// no more delimiters
pos = str.length();
if(pos != lastPos || !trimEmpty) {
tokens.emplace_back(strdata + lastPos, pos - lastPos);
}
break;
} else {
if(pos != lastPos || !trimEmpty) {
tokens.emplace_back(strdata + lastPos, pos - lastPos);
}
}
lastPos = pos + 1;
}
return tokens;
}
а затем просто используйте его так (ParseError
- это какой-то объект исключения):
std::vector<std::string> tokens = tokenizer(str, " \t", true);
if (tokens.size() < 4)
throw ParseError{"Too few fields in " + str};
try {
unsigned int i{ boost::lexical_cast<unsigned int>(tokens[0]) },
j{ boost::lexical_cast<unsigned int>(tokens[1]) };
double x{ boost::lexical_cast<double>(tokens[2]) },
y{ boost::lexical_cast<double>(tokens[3]) };
// print or process i, j, x, y ...
} catch(const boost::bad_lexical_cast& error) {
throw ParseError{"Could not parse " + str};
}
Примечание: вы можете использовать Буст-разделение или токенизатор если хотите, но они были медленнее, чем токенизатор Мариуса (по крайней мере, в моей среде).
Обновление: вместо boost::lexical_cast<T>
вы можете использовать функции С++ 11 "std::sto*
" (например, stoi
для преобразования строкового токена в int). Они вызывают два типа исключений: std::invalid_argument
, если преобразование невозможно выполнить, и std::out_of_range
, если преобразованное значение не может быть представлено. Вы можете поймать их по отдельности или их родителя std::runtime_error
. Изменения в приведенном выше примере кода оставлены читателю в качестве упражнения :-)
person
Laryx Decidua
schedule
10.04.2015
T
, поскольку вы переопределяетеt
даже в случае, еслиe.eof()
ложно. - person Mene   schedule 07.11.2012EOF
, если вы сделаетеi.peek()
- person lc.   schedule 07.11.2012