C主要使用scanf/printf及其相关函数( fread, fwrite等 )
C++主要使用std::cin/std::cout流对象
int main() {
fprintf( stdout, "hello, " );
fprintf( stderr, "world!\n" );
}
实际输出是:
world!
hello,
另外标准库提供了一个fflush函数,它会将库缓存提交给操作系统;
它不仅仅是输出一个换行符,还执行了flush操作(也就是将库缓存数据提交给操作系统),这样做的优点是不会因为程序意外退出少打印数据,缺点是无法利用buffer,性能急剧下降。所以对于不那么重要的操作,尤其是OJ的一些输出换行,建议用’\n’而不要用std::endl。
a> 从编译器角度,C编译器生成的代码不会比C++编译器差,甚至可能略占优势(之前是这么讲,不知道现在情况如何)
b> 需要解析format字符串,先判断类型,然后才能做相应的处理,这会占用一定的时间
c> mingw版本下的cstdio可能会慢很多,msvcrt的IO不符合ANSI标准,所以mingw自己加入了一段wrapper,并且引入了__USE_MINGW_ANSI_STDIO宏作为开关,默认是关闭的,但是对于C++来讲还需要libstdc++库,在编译libstdc++的时候__USE_MINGW_ANSI_STDIO却是打开的。[1][2]
a> 默认情况下,C++为了在交杂使用cin/cout和scanf/printf不至于出错时,将C和C++输出输入流绑在了一起,导致每次同步都要额外消耗时间,这个可以通过std::ios::sync_with_stdio(false);关闭
b> 默认情况下,C++的cin上会绑定对应的输出流cout,每次对cin进行任何IO操作都会flush cout,可以通过std::cin.tie( 0 );解除这种绑定
c> 因为数据类型编译期间可以确定,所以节省了parse时间,较新版本的编译器已经可以体现出这个优势。
在gcc 4.9.1下测试如下代码(预先生成一个data文件,里面存储1000000个整数):
#ifdef _USE_STDIO
#include <stdio.h>
#endif
#ifdef _USE_CSTDIO
#include <cstdio>
#endif
#ifdef _USE_CIN
#include <stdio.h>
#include <iostream>
#endif
const int NUM = 1000000;
int main()
{
freopen( "data", "r", stdin );
int n;
for(int i = 0 ; i < NUM ; i++) {
#ifdef _USE_STDIO
scanf( "%d", &n );
#endif
#ifdef _USE_CSTDIO
scanf( "%d", &n );
#endif
#ifdef _USE_CIN
#ifdef _NO_SYNC
std::ios::sync_with_stdio( false );
#endif
#ifdef _NO_TIE
std::cin.tie( 0 );
#endif
std::cin >> n;
#endif
}
return 0;
}
使用如下命令行编译和运行
g++ test_cin.cc -D_USE_STDIO -O2 -o test_stdio
g++ test_cin.cc -D_USE_CSTDIO -O2 -o test_cstdio
g++ test_cin.cc -D_USE_CIN -O2 -o test_cin
g++ test_cin.cc -D_USE_CIN -D_NO_SYNC -O2 -o test_cin_nosync
g++ test_cin.cc -D_USE_CIN -D_NO_SYNC -D_NO_TIE -O2 -o test_cin_nosync_notie
time ./test_stdio
time ./test_cstdio
time ./test_cin
time ./test_cin_nosync
time ./test_cin_nosync_notie
测试结果(大于号表示速度快)是:
test_cin_nosync_notie > test_cin_nosync > test_stdio ≈ test_cstdio > test_cin
因为我的测试环境是linux,没有使用mingw,所以stdio.h和cstdio没区别。
当然速度更快的还是自己申请比较大的缓冲区,调用fread(间接调用系统api read)直接读一大块数据,然后自己手动解析,这样做可以减少系统调用次数,从而缩短时间,缺点是额外使用空间。
如果确定不会有并发问题,可以调用fread_unlocked,从而去掉同步过程,速度更快,这只有在fread被频繁调用时才能展现优势。
a> 在%后面的接*号
对于scanf来说,是按照相应格式读入,然后忽略;对于printf来说,会先把一个参数替换到*的位置,然后该处格式对应下一个参数。
scanf( "%*d%d", &a ); // 输入1 2,a == 2,因为1被忽略了
printf( "%0*.*f\n", 10, 4, 1.2 ); // 输出 00001.2000
b> 在%或*后面接m$,其中m是一个正整数
这表示使用第几个参数,参数列表索引从1开始,相当于用%m$对应参数代替%,用*m$对应的参数代替*
printf( "%2$*1$d", a, b ); // 等价于printf( "%*d", a, b ); a是第一个参数,b是第二个参数,%2$表示用第二个参数作为%,*1$表示用第一个参数做宽度控制
c> %n的使用
对于printf来说,可以将输出字符个数统计到参数中
printf( "%nhello%n\n", &a, &b ); // a == 0 b == 5
d> %[的使用
对于scanf来说,可以使用部分正则字符串功能,可以用’[’和’]’中间写一个字符串集合,遇到任何一个字符都匹配,用’^’表示不包括
scanf( "%*[ \n]%c", &c ); // 略过输入开头的空格和换行符,将第一个字符读入c中
因为sprintf没有对缓冲区大小做检查,很容易引起缓冲区溢出攻击,所以建议替换成snprintf
printf也因为使用格式符,一旦其可能引入用户输入,就会产生问题,例如你如果从某个地方得到用户输入字符串str,直接将其作为format串输出,printf( str );就会造成安全隐患,应该用printf( “%s”, str );这样的方法调用,更详细的分析可见[4]
参考
[1] http://www.zhihu.com/question/21016898
[2] http://stackoverflow.com/questions/17236352/mingw-w64-slow-sprintf-in-cstdio
[3] http://www.hankcs.com/program/cpp/cin-tie-with-sync_with_stdio-acceleration-input-and-output.html
[4] http://drops.wooyun.org/binary/6259
[5] http://blog.csdn.net/tianwailaibin/article/details/6709490