第2.14章: A + B

English

基本的输入与输出

一个读取两个整数并打印它们之和的简单程序。它同时演示了用于输入的 scan 与用于输出的 println:


#include <fast_io.h>
#include <cstddef>

int main()
{
    try
    {
        using namespace ::fast_io::iomnp;
        ::std::size_t a, b;
        scan(a, b);
        println(a + b);
    }
    catch throws (::std::error e)
    {
        ::fast_io::perrln(e);
        return 1;
    }
}

就连这么小的程序也不能保证成功,而代码本身也表明了这一点:函数体放在 try 块中,catch throws 处理程序会报告失败 并返回非零退出码。下一节你将看到原因。

I/O 总是可能失败

这是本教程中第一个与外部世界打交道的程序,也正是从这一刻起,失败成为可能。 每一个 I/O 操作都可能失败。 不存在“安全”的 I/O 调用,也不存在某种配置能让你假定它一定成功。

输入随时都会失败。它失败的方式再普通不过:

运行上面的程序并喂给它垃圾数据:


$ echo "x 4" | ./aplusb
[parse](3)Invalid format
$ echo $?
1

输出同样会失败。这让人吃惊,因为在 iostream 的世界里从没有人检查打印结果。但写入是对 操作系统(或网络对端)的请求,而请求是会被拒绝的:

所以 println 并不是一条“只是打印”的语句。它是一个可能失败的操作, 而在 fast_io 中,每个可能失败的操作都会在它的类型中说明这一点。 试试看:


#include <fast_io.h>

using namespace ::fast_io::io;

// println can fail: it may have to write to a device
static_assert(!noexcept(println("Hello World")));

// scan can fail: it may have to read and parse
static_assert(!noexcept(scan(::fast_io::c_stdin(), ::std::declval<int&>())));

可能失败的调用会被声明为 throws。如果你调用了这样的函数,就必须 应对失败的可能性:要么用 try { } catch throws (::std::error e) { } 处理,要么把你自己的函数也标记为 throws,让失败传播给你的调用者。没有第三种选择 ——编译器不允许你忽视它。

本教程为何不为此使用传统的 C++ 异常。 fast_io 中的错误以 赫布异常 的形式报告,它们 是本教程中唯一的错误机制。要构建它们,需要 -fherbceptions -lherbceptions,以及我们的某个 GCC 或 LLVM 构建(MSVC 不支持赫布异常)。完整的规则——选项、 throw throws 语句、处理程序,以及每一个错误域——都在 第3.16章: 赫布异常 中。从这里开始, 每一个执行 I/O 的示例都会使用你在上面看到的同一种 try { } catch throws 形态。

什么是操纵器?

在 fast_io 中,操纵器(manipulator)是一个辅助对象,用来改变 输入或输出被解释或格式化的方式。不必依赖不安全的格式字符串, 操纵器把意图显式地表达在类型系统中。

操纵器定义在命名空间 fast_io::manipulators 中,为方便起见别名为 fast_io::mnp。相比传统的 stdio 或 iostream, 它们让输入输出更安全、更显式、更可移植。

例如:

十六进制操纵器

fast_io 提供了以十六进制读写数字的操纵器。 hex_get 以十六进制读取输入,而 hex 与 hexupper 分别以小写或大写十六进制打印输出:


#include <fast_io.h>
#include <cstddef>

int main()
{
    try
    {
        using namespace ::fast_io::iomnp;
        ::std::size_t a, b;
        scan(hex_get(a), hex_get(b));
        println(hex(a + b), " ", hexupper(a + b));
    }
    catch throws (::std::error e)
    {
        ::fast_io::perrln(e);
        return 1;
    }
}

两个十六进制数进,两个十六进制数出。坏输入仍然终止于处理程序,而不会造成 未定义行为。

N 进制操纵器

操纵器也支持 2 到 36 的任意进制。下面是使用 36 进制的例子:


#include <fast_io.h>
#include <cstddef>

int main()
{
    try
    {
        using namespace ::fast_io::iomnp;
        ::std::size_t a, b;
        scan(base_get<36>(a), base_get<36>(b));
        println(base<36>(a + b));
    }
    catch throws (::std::error e)
    {
        ::fast_io::perrln(e);
        return 1;
    }
}

关于操纵器

fast_io 定义了一组丰富的操纵器来控制输入输出的格式化。 为方便起见,命名空间 fast_io::manipulators 别名为 fast_io::mnp。

这些操纵器让你可以轻松处理不同的数字进制,而无需依赖不安全的格式字符串。

要点