tôi sẽ muốn cung cấp một cách tiếp cận hoàn toàn khác: Lấy chuỗi đầu vào của bạn, tự mình mã hóa và sau đó chuyển đổi các trường riêng lẻ bằng cách sử dụng boost::lexical_cast<T>
.
Lý do: Tôi lãng phí một buổi chiều về phân tích cú pháp một chuỗi có chứa 2 trường int và 2 trường đôi, được phân tách bằng dấu cách. Làm những điều sau đây:
int i, j;
double x, y;
std::istringstream ins{str};
ins >> i >> j >> x >> y;
// how to check errors???...
phân tích đầu vào đúng như
`"5 3 9.9e+01 5.5e+02"`
một cách chính xác, nhưng không phát hiện ra vấn đề với điều này:
`"5 9.6e+01 5.5e+02"`
gì xảy ra là i
sẽ được thiết lập đến 5 (OK), j
sẽ được đặt thành 9 (??), x
đến 6.0 (= 0,6e + 01), y
đến 550 (OK). Tôi đã khá ngạc nhiên khi thấy failbit
không được đặt ... (thông tin nền tảng: OS X 10.9, Apple Clang ++ 6.0, chế độ C++ 11). Tất nhiên bạn có thể nói bây giờ, "Nhưng chờ đợi, các tiêu chuẩn nói rằng nó nên như vậy", và bạn có thể đúng, nhưng biết rằng nó là một tính năng chứ không phải là một lỗi không làm giảm đau nếu bạn muốn để làm kiểm tra lỗi thích hợp mà không cần viết dặm mã.
OTOH, nếu bạn sử dụng "Marius" xuất sắc tokeniser function và tách str
trước trên khoảng trắng thì đột nhiên mọi thứ trở nên rất dễ dàng. Đây là một phiên bản sửa đổi nhỏ của tokeniser. Tôi viết lại nó để trả về một vectơ dây; bản gốc là một mẫu đặt các mã thông báo trong một vùng chứa với các phần tử có thể chuyển đổi thành chuỗi. (Đối với những người cần một cách tiếp cận chung chung như vậy xin vui lòng tham khảo link gốc ở trên.)
// \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;
}
và sau đó chỉ cần sử dụng nó như thế này (ParseError
là một số đối tượng ngoại lệ):
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};
}
Lưu ý: bạn có thể sử dụng Boost split hoặc tokenizer nếu bạn muốn, nhưng chúng chậm hơn so với tokeneller của Marius (ít nhất là trong môi trường của tôi).
Cập nhật: Thay vì boost::lexical_cast<T>
bạn có thể sử dụng các hàm C++ 11 "std::sto*
" (ví dụ: stoi
để chuyển đổi mã thông báo chuỗi thành int). Chúng ném hai loại ngoại lệ: std::invalid_argument
nếu chuyển đổi không thể được thực hiện và std::out_of_range
nếu giá trị được chuyển đổi không thể được biểu diễn. Bạn có thể nắm bắt chúng một cách riêng biệt hoặc cha mẹ của chúng std::runtime_error
. Các sửa đổi đối với mã ví dụ ở trên được để lại dưới dạng bài tập cho người đọc :-)
Không phải là câu trả lời, chỉ cần lưu ý: xem xét sử dụng biến tmp của loại 'T' khi bạn ghi đè' t' ngay cả trong trường hợp 'e.eof()' là sai. – Mene
Bạn có nhận được 'EOF' trả về nếu bạn làm' i.peek() ' –