关于输入解析,IOStreams中缺少一些scanf()所具备的功能,其中之一是为数字类型设置字段宽度(另一个是匹配输入中的字符串)。假设您想继续使用格式化输入,处理它的一种方法是创建一个过滤流缓冲区,在给定数量的字符后注入一个空格字符。
另一种方法是编写自定义std::num_get facet,将其imbue()到当前流中,然后设置宽度即可。与注入空格不同,实际字符解析会观察流是否已达到末尾或超出允许的字符数。使用此facet的相应代码将设置自定义std::locale,但除此之外看起来像预期的那样:
int main() {
std::istringstream in("1234567890123456789");
std::locale loc(std::locale(), new width_num_get);
in.imbue(loc);
int w(0);
for (int value(0); in >> std::setw(++w) >> value; ) {
std::cout << "value=" << value << "\n";
}
}
这里是一个有些天真的实现相应的std::num_get<char>
facet,它只收集适当的数字(假设为10进制)然后调用std::stoi()
进行转换。它可以更加灵活和高效地完成,但你已经明白了大概:
#include <iostream>
#include <streambuf>
#include <sstream>
#include <locale>
#include <string>
#include <iomanip>
#include <cctype>
struct width_num_get
: std::num_get<char> {
auto do_get(iter_type it, iter_type end, std::ios_base& fmt,
std::ios_base::iostate& err, long& value) const
-> iter_type override {
int width(fmt.width(0)), count(0);
if (width == 0) {
width = -1;
}
std::string digits;
if (it != end && (*it == '-' || *it == '+')) {
digits.push_back(*it++);
++count;
}
while (it != end && count != width && std::isdigit(static_cast<unsigned char>(*it))) {
digits.push_back(*it);
++it;
++count;
}
try { value = std::stol(digits); }
catch (...) { err |= std::ios_base::failbit; }
return it;
}
};
第一种方法可以使用以下代码来读取不同宽度的整数(我使用不同的宽度来展示它可以灵活地设置):
int main() {
std::istringstream in("1234567890123456789");
int w(0);
for (int value(0); in >> fw(++w) >> value; ) {
std::cout << "value=" << value << "\n";
}
}
当然,整个魔法在于小小的
fw()
,它是一个自定义操纵器:如果当前使用的流缓冲区不是适当的类型,则安装过滤流缓冲区,并设置在该数量之后应注入空格。过滤流缓冲区读取单个字符并在相应数量的字符后简单地注入一个空格。代码可能类似于这样(目前完成后未进行清理 - 我将在下一步添加):
#include <iostream>
#include <streambuf>
#include <sstream>
class fieldbuf
: public std::streambuf {
std::streambuf* sbuf;
int width;
char buffer[1];
int underflow() {
if (this->width == 0) {
buffer[0] = ' ';
this->width = -1;
}
else {
int c = this->sbuf->snextc();
if (c == std::char_traits<char>::eof()) {
return c;
}
buffer[0] = std::char_traits<char>::to_char_type(c);
if (0 < this->width) {
--this->width;
}
}
this->setg(buffer, buffer, buffer + 1);
return std::char_traits<char>::to_int_type(buffer[0]);
}
public:
fieldbuf(std::streambuf* sbuf): sbuf(sbuf), width(-1) {}
void setwidth(int width) { this->width = width; }
};
struct fw {
int width;
fw(int width): width(width) {}
};
std::istream& operator>> (std::istream& in, fw const& width) {
fieldbuf* fbuf(dynamic_cast<fieldbuf*>(in.rdbuf()));
if (!fbuf) {
fbuf = new fieldbuf(in.rdbuf());
in.rdbuf(fbuf);
static int index = std::ios_base::xalloc();
in.pword(index) = fbuf;
in.register_callback([](std::ios_base::event ev, std::ios_base& stream, int index){
if (ev == std::ios_base::copyfmt_event) {
stream.pword(index) = 0;
}
else if (ev == std::ios_base::erase_event) {
delete static_cast<fieldbuf*>(stream.pword(index));
stream.pword(index) = 0;
}
}, index);
}
fbuf->setwidth(width.width);
return in;
}
std::setw
参考文档和std::num_get::get
参考文档(实际解析数字的函数)之后,似乎std::setw
操作符仅用于字符和字符串,数字解析不检查宽度。因此,如果您想一次只读取一个数字,则必须在该数字为数字时一次只读取一个字符。 - Some programmer dudestd::setw
操纵符不能用于数字。 - Some programmer dude