C++中模拟scanf("%1d")的方法(使用std::cin)

7

我正在寻找一些类似于scanf("%1d", &sequence) 的方法,用于替代std::cin >> sequence。

例如:

for ( ; scanf("%1d", &sequence) == 1; ) {
    printf("%d ", sequence);
}
标准输入: 5341235
标准输出: 5 3 4 1 2 3 5 

C++ 的实现原理是什么?!

for ( ; std::cin >> *some_magic* sequence; ) {
    std::cout << sequence << " ";
}

@JoachimPileborg:你真的测试过了吗?文档没有显示流宽度被考虑用于此输入操作。 - Kerrek SB
1
@JoachimPileborg:是的,我也这样认为,但不幸的是它没有起作用 http://ideone.com/Z7K5HD - Ankur
在仔细阅读了std::setw参考文档std::num_get::get参考文档(实际解析数字的函数)之后,似乎std::setw操作符仅用于字符和字符串,数字解析不检查宽度。因此,如果您想一次只读取一个数字,则必须在该数字为数字时一次只读取一个字符。 - Some programmer dude
@KerrekSB 你是对的,std::setw 操纵符不能用于数字。 - Some programmer dude
2个回答

3

如果需要的话,您可以这样做(sequence变量必须char类型)。

for ( ; std::cin.read(&sequence,1); ) {
    sequence-='0';
    std::cout << sequence << " ";;
}

3
关于输入解析,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; } // should probably distinguish overflow
        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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接