模拟扫描( "%1d" ) 用于C++ (std::cin)

Analog scanf("%1d") for C++ (std::cin)

本文关键字:扫描 cin std 用于 %1d 模拟 C++      更新时间:2023-10-16

我正在寻找一些模拟扫描("%1d",&序列)用于std::cin>>序列。

例如:

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 << " ";
}

如果你愿意,你可以这样做(sequence变量必须是char类型)

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

关于输入解析,不幸的是,IOStreams 中缺少许多功能,这些功能存在于scanf()中。为数值类型设置字段宽度就是其中之一(另一个是匹配输入中的字符串)。假设你想保持格式化的输入,处理它的一种方法是创建一个过滤流缓冲区,该缓冲区在给定数量的字符后注入一个空格字符。

另一种方法是编写自定义std::num_get<char>方面,将其imbue()到当前流中,然后仅设置宽度。实际的字符解析将观察是否到达流的末尾或超过允许的字符数,而不是注入空格。使用此方面的相应代码将设置自定义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>方面的有点幼稚的实现,它只是收集适当的数字(假设以 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;
}