第26.2章:print
1. 函数表面
print 家族刻意保持很小。每个函数都返回 void,并通过 herbception 报告失败。
名字之间的差别只有:目标、换行,以及这个调用是否允许返回。
| 函数 | 默认目标 | 换行 | 可用性 |
|---|---|---|---|
print(args...) |
C stdout |
否 | 始终 |
println(args...) |
C stdout |
是,\n |
始终 |
perr(args...) |
原生 stderr |
否 | 始终 |
perrln(args...) |
原生 stderr |
是,\n |
始终 |
panic(args...) |
原生 stderr |
否 | 始终;会终止 |
panicln(args...) |
原生 stderr |
是,\n |
始终;会终止 |
debug_print(args...) |
原生 stdout |
否 | 仅在未定义 NDEBUG 时 |
debug_println(args...) |
原生 stdout |
是 | 仅在未定义 NDEBUG 时 |
debug_perr(args...) |
原生 stderr |
否 | 仅在未定义 NDEBUG 时 |
debug_perrln(args...) |
原生 stderr |
是 | 仅在未定义 NDEBUG 时 |
每个函数也接受一个输出设备作为第一个参数:
print(device, args...)、println(device, args...)、
perr(device, args...) 等等。第一个参数不是输出设备时,使用表里的默认目标。
perr 写平台原生的标准错误,而不是 C 的 stderr。
它不带缓冲,每次调用就是一次系统调用,这正好提供诊断所需的原子性。
panic 和 panicln 是同样的目标,然后执行 trap:它们不返回。
2. 第一个参数算什么
如果第一个参数满足 fast_io 使用的 output-stream 或 I/O-stream 概念,它就是设备; 否则它被当作值打印,并使用默认目标。所以下面两种写法都成立:
#include <fast_io.h>
int main()
{
using namespace ::fast_io::io;
// Value first: default destination.
println("answer = ", 42);
// Device first: explicit destination.
::fast_io::obuf_file out("out.txt");
println(out, "answer = ", 42);
}
没有格式串,也没有每次调用的状态。设备只选一次,剩下的每个参数按自己的类型,
以及包在它外面的操纵器来格式化。这就是为什么 print 不需要 iostream
状态机也能组合:调用结束后没有东西需要恢复。
3. 格式化只作用于参数
操纵器是值,不是流标志。mnp::hex、mnp::fixed、
mnp::left 等等都只包住一个参数:
println("decimal: ", 255);
println("hex: ", ::fast_io::mnp::hex(255));
println("padded: ", ::fast_io::mnp::right(42, 8, '0'));
下一条语句不受影响。不存在忘记重置 std::hex 的等价问题,
也不存在操纵器泄漏到另一个流的等价问题。完整导出列表——包括进制、浮点、宽度、
指针、字符串、二进制、网络和 locale 操纵器——在
第26.1章。
自定义类型通过 print_reserve_define 或 print_define
加入同一条路径。print 函数不需要认识这个类型;它让类型的格式化器写进输出设备。
如果类型只能通过一段已有字节的 scatter 打印,就用 print_scatter_define。
4. 返回值与失败
print 家族没有返回值。失败以 herbception 抛出,所以通常的处理程序就是本书一直在用的那个:
#include <fast_io.h>
int main()
{
try
{
::fast_io::io::println(::fast_io::out(), "hello");
}
catch throws(::std::error e)
{
::fast_io::io::perrln(e);
return 1;
}
}
给一个没有格式化器的类型做格式化是编译期错误,不是运行期错误。 运行期失败来自设备层:文件已关闭、磁盘满、管道破裂,或者系统调用被中断。
5. 缓冲、刷新与同步
print 对带缓冲和不带缓冲的输出设备都成立。带缓冲设备把小写操作攒起来,
缓冲区满或对象析构时刷出;不带缓冲设备直接写透。这就是
第11.5章讲过的划分,
也是 print 家族不强迫每个调用者选择缓冲区大小的原因。
perr 故意不带缓冲。即使进程即将 trap,或者另一个线程还持有带缓冲的流,
诊断也必须到达。代价是每次调用一次系统调用;不要在热循环里用 perr。
线程安全取决于设备。互斥包装的设备(例如可加锁文件类型)会把调用串行化。
普通带缓冲文件不会因为函数叫 println 就变成线程安全的。
进程安全是更强的性质:只有设备不是带缓冲输出流时,print 家族才保证它。
6. 调试输出
debug_* 家族的存在,是为了让诊断打印从发布构建里消失。
定义 NDEBUG 后这些函数不可用;仍然引用它们的代码会编译失败,
而不是悄悄产生输出。它们的默认目标是原生标准流,不是 C 的流,
所以即使 C 运行时不在可用状态,它们仍然能工作。
debug_println("state = ", state);
不要把 debug_print 当日志 API 用。它是开发工具,可用性刻意绑定构建模式。
7. 什么场景用什么
| 需求 | 使用 |
|---|---|
| 普通程序输出 | print / println,目标为默认或显式文件/设备 |
| 崩溃后仍需保留的诊断 | perr / perrln |
| 不可能状态 | panic / panicln |
| 仅开发期的跟踪 | debug_print / debug_perr |
| 搬字节,不是搬值 | transmit(见 第11.5章) |
| locale 感知的数字或时间 | imbue(loc, out)(见 第26.1章) |
要点
- print 家族是
print、println、perr、perrln、panic、panicln以及debug_*变体。 print/println默认写 Cstdout;perr/perrln使用原生、不带缓冲的stderr。- 设备可以作为第一个参数;否则使用默认目标。
- 操纵器只影响一个参数;没有需要重置的流状态。
- 所有函数返回
void,设备失败以 herbception 报告。 panic会终止;debug_*在NDEBUG下消失。- 搬字节用
transmit,不要用 print 家族。