Вместо того, чтобы читать строку, а затем использовать substr
, чтобы извлечь интересующие вас фрагменты, я бы предпочел указать ожидаемый формат ввода чуть более прямо. В C я бы, наверное, сделал что-то вроде этого:
if (fscanf(infile, "START(%d,%d)", &startX, &startY) != 2)
// error in reading start point
if (fscanf(infile, "GOAL(%d,%d)", &goalX, &goalY) != 2)
// error reading goal point
В C ++, по крайней мере, мне кажется, что код, который мы действительно хотели бы написать, имеет следующий порядок:
input >> "START(">> startX >> "," >> startY >> ")\n";
input >> "GOAL(">> goalX >> "," >> goalY > ")\n";
Итак, по крайней мере, как я вижу, вопрос в том, можем ли (и если да, то как) мы можем это поддержать. Ответ: да, мы можем (вероятно, довольно очевидно - было бы довольно бессмысленно делать большую сборку, а потом сказать «извините, но мы не можем этого сделать»).
Для поддержки этого нам нужен экстрактор для константных строк. В простейшем случае это может выглядеть примерно так:
template <class charT>
std::basic_istream<charT> &operator>>(std::basic_istream<charT> &is, charT const *fmt) {
while (*fmt) {
if (*fmt != is.peek())
is.setstate(std::ios_base::failbit);
++fmt;
is.ignore(1);
}
return is;
}
Таким образом, это в основном просто смотрит на один символ за раз из входного потока, сравнивает его с текущим символом из строки формата. Если они совпадают, он просто извлекает этот символ из потока и переходит к следующему. Если они не совпадают, он устанавливает бит сбоя, чтобы сказать, что извлечение не удалось.
У этого есть один недостаток: поток имеет бит skipws
, и если он установлен, мы ожидаем пропустить пробел, прежде чем пытаться делать что-то еще. Это, вероятно, должно применяться здесь, поэтому что-то вроде infile >> "ignore"
будет соответствовать вводу типа «игнорировать».
Чтобы это исправить, мы могли бы добавить небольшой цикл примерно так:
while (std::isspace(is.peek()))
is.ignore(1);
... прежде чем пытаться сопоставить строку. Это имеет еще один недостаток: он всегда использует текущий глобальный locale
- но поток может быть наполнен собственным locale
, который должен применяться для чтения из этого потока. Таким образом, чтобы правильно пропустить пустое пространство, мы должны извлечь locale
потока, затем получить ctype
фасет из этого locale
и использовать его, чтобы решить, является ли что-то пустым пространством или нет. К сожалению, код для этого немного длиннее и сложнее, чем кому-либо, вероятно, нравится:
template <class charT>
std::basic_istream<charT> &operator>>(std::basic_istream<charT> &is, charT const *fmt) {
if (fmt == nullptr)
return is;
if (is.flags() & std::ios_base::skipws) {
std::locale const &loc = is.getloc();
if (std::has_facet<std::ctype<charT>>(loc)) {
auto const &ct = std::use_facet<std::ctype<charT>>(loc);
while (ct.is(std::ctype_base::blank, is.peek()))
is.ignore(1);
}
else
while (std::isspace(is.peek()))
is.ignore(1);
}
while (*fmt) {
if (*fmt != is.peek())
is.setstate(std::ios_base::failbit);
++fmt;
is.ignore(1);
}
return is;
}
По крайней мере, на данный момент я написал это, чтобы он извлекал locale
потока и использовал его ctype
фасет, если он есть, но если у него был locale
без фасета ctype (который является по крайней мере, теоретически это возможно) он использует std::isspace
, чтобы решить, является ли что-то пустым пространством. Вероятно, есть основания для спора о том, что в этот момент было бы лучше просто потерпеть неудачу, но я оставлю этот вопрос на следующий день.
Как только мы закончим, мы можем читать такие вещи, как хотели:
int main() {
std::istringstream b("START(0, 0)\nGOAL(1,2)");
int startX, startY;
b >> "START(" >> startX >> "," >> startY >> ")";
std::cout << "start_x: " << startX << ", start_y: " << startY << "\n";
int goalX, goalY;
b >> "GOAL(" >> goalX >> "," >> goalY >> ")";
std::cout << "goal_x: " << goalX << ", goal_y: " << goalY << "\n";
}
Обратите внимание, что здесь поведение немного отличается от scanf
и компании. В частности, при этом будет пропущен пробел перед началом указанной вами строки формата (если установлено skipws
), а затем попытается сопоставить каждый символ в строке, которую вы передаете буквально.
В отличие от этого, scanf
и компания рассматривают любые пробелы в строке формата как директиву, позволяющую пропустить серию всех последовательных пробелов во входных данных. Вы можете (конечно) изменить это так, чтобы он действовал как scanf
и компания, если хотите, но такое поведение удивляет довольно многих людей, поэтому я думаю, это, вероятно, лучше, чем оно есть.