严格来说, 有 3 种风格.
- UNIX 底层读写库
- c 语言 stdio 标准库
- iostream 流
一般的工程中, 底层读写库封装程度太低, 需要自己处理缓存和很多通用的异常场景. 不适合.
网络编程中, 缓存会导致很多负面作用, 可以考虑用底层的读写库.
1. 格式化输出对比
1.1 格式化输出的可配置性
iostream 采用 manipulator 来格式化, 格式化信息写死在代码中, 难以实现可配置. -- 软件工程中绝对的硬伤.
stdio 采用的 %d %s 等已经发展成一个 DSL, 并在很多语言中通用. http://en.wikipedia.org/wiki/Printf_format_string#Programming_languages_with_printf
1.2 上下文无关
iostream 中, 通过改变流的状态来实现格式化输出, 而流的状态是全局的. 需要时刻小心,防止影响了后面的代码.
stdio 是上下文无关的.
1.3 缓冲区溢出
stdio 问题在于, 不是类型安全的, 而且存在缓冲区溢出的安全风险. 正确而安全的做法如下
#include <stdio.h> int main() { const int max = 80; char name[max]; char fmt[10]; sprintf(fmt, "%%%ds", max - 1); scanf(fmt, name); printf("%s\n", name); }
1.4 类型安全与 64 位兼容性
stdio 的标准库, 需要明确知道待输出数据的类型, 并选择合适的 %d, %ld.
格式化字符串与参数类型不匹配会造成难以发现的 bug.
如果考虑到同时在 32 和 64 位机器上支持正确打印, %d 的选取更加头疼.
《The Linux Programming Interface》的作者建议(3.6.2节)先统一转换为 long 类型再用 "%ld" 来打印;对于某些类型仍然需要特殊处理,比如 off_t 的类型可能是 long long。
http://google-styleguide.googlecode.com/svn/trunk/cppguide.html#64-bit_Portability
printf 无法像打印 int 那样用 printf 来直接打印 自定义对象。
1.5 性能
1. 即使只使用了解释程序的一个功能,也要全部装载。如上面的例子,我们要装载整个包,包括解释浮点数和字符串那部分程序段,无法减少程序的长度。 2. 虽然printf族函数已经优化得很好,但是,它是在运行期间进行解释,如果能在编译期间分析格式字符串里的变量,根据不同的类型调用各自的函数处理,运行会快得多,而且C++编译期间的类型检查会有助于我们发现错误.
在线 ACM/ICPC 判题网站上,如果一个简单的题目发生超时错误,那么把其中 iostream 的输入输出换成 stdio,有时就能过关。
3. 对于C++来说,printf不能被扩展是它最大的缺点。我们不能通过重载函数对它进行扩展,因为重载函数要有不同类型的参数,而printf族函数把类型信息隐藏在可变参数表和格式字符串中。
2. 线程安全
stdio 的函数是线程安全的,
而且 C 语言还提供了 flockfile(3)/funlockfile(3) 之类的函数来明确控制 FILE* 的加锁与解锁。
cout << a << b; 是两次函数调用,相当于 cout.operator<<(a).operator<<(b)。
两次调用中间可能会被打断进行上下文切换,造成输出内容不连续,插入了其他线程打印的字符。