`
yidongkaifa
  • 浏览: 4064027 次
文章分类
社区版块
存档分类
最新评论

C++ ostream与printf比较

 
阅读更多

这两天与一位网友就C++流与printf函数的问题吵了两天,有点儿火药味儿(http://www.cppblog.com/converse/archive/2010/07/06/119427.html),其实我是对他一个大标题“C++的流设计很糟糕”,是比较生气的,这么多年还没有谁敢对C++的标准库如此出言不逊。我还使用百度跟GOOGLE验证了这一点,把“C++ 流 糟糕”三个关键词放在一起,GOOGLE与百度出奇一致的结果就是头条就是上面链接那博文了。
C++标准库,可以说为了保证其通用性和效率,真是绞尽脑汁、C++特性无所不用其极,而且还专门为了设计容器增加了模板的支持。C++的流当然也不是十全是美,这也跟C++的特性本身有关,还谈不上“糟糕”,更不能说“很糟糕”!
原因在于
ostream这种方式使得ostream语句何时结束,对于日志、网络等一些自定义输出操作,就不知道何时应该实施真实的操作。另一个问题就是不能保证该语句的原子操作。其实这些问题可以通过一定的手段来解决。
1) 结束符。可以通过一个特殊的字符来解决,如标准库中的std::ends,其实就是一个字符"/0",而对于二进制流,这样恐怕是不行的,可以通过下面的方法来处理。

#include <iostream><br>#include <sstream><br>#include <strstream><br><br>using namespace std;<br><br>struct streamend { };<br><br>class LoggerStream : public stringstream //public std::ostrstream <br>{<br>public:<br>	static streamend end;<br>	~LoggerStream( ) {	DoPrint();	}<br><br>private:<br>	void DoPrint( void )<br>	{<br>		coutstr();	// real string<br>		this-&gt;str("");<br>	}<br>private:<br>	friend ostream&amp; operator};<br><br>streamend LoggerStream::end;<br><br>ostream&amp; operator{<br>#if 1         // 两种方法都可以,后者要求对RTTI的支持<br>	ostream* pos = &amp;os;<br>	LoggerStream* pls = dynamic_cast<loggerstream>(pos);<br>	if( pls != NULL)<br>		pls-&gt;DoPrint();<br>#else<br>	if( typeid(os) == typeid(LoggerStream))<br>		((LoggerStream*)(&amp;os))-&gt;DoPrint();
#endif<br>	return os;<br>}<br>#define LOG( content ) lstream<br>int main() <br>{<br>	LoggerStream lstream; <br><br><br>	lstream 	lstream <br><br>	LOG( "str1"	stringstream ss;<br>	ss	return 0;<br>}</loggerstream></strstream></sstream></iostream>

2)多线程支持。其实也可以通过扩展LOG宏来处理,假设为自定义类型增加互斥锁,分别使用Lock( ), Unlock( )方法进行上锁与解锁,这样可以将宏修改为

#define LOG( content ) do{ /
lstream.Lock(); /
lstream lstream.Unlock( ); /
}while(0)

既然说到printf与流,不妨也比较一下两个的优缺点。
1. 先说printf的优点,也就这一点了,那就是代码简洁,格式化方便,可以在格式化字符串里一次性将输出格式化。而ostream则需要一段一段地拆分,显得比较烦锁,特别是自定义输出类型的格式时,如格式化输出浮点的小数位数、十六进制输出等,用ostream更烦锁。
2.ostream类型安全,而printf则不能保证类型安全。
2.1)printf容易产生输出格式字符串错误。
int i = –1;
std::coutprintf(“%u”, i):
使用printf的输出结果将是错误的,虽然“那谁”网友也说到GCC中对__attribute__的扩展,可以检测printf的格式化字符串,但对于%u仍然无可奈何,而且__attribute__移植性不好,其它的编译器不支持该特性。虽然一开始写的时候可以保证格式字符串的一致性,但谁能保证在一个大系统中,哪天unsigned变量不会被修改成signed,这样输出将不再正确。

2.2)printf类型错误时会造成程序崩溃。因为在64位主机上指针为8字节,而在32位系统中指针为4字节,如果使用格式符不当,会导致地址非法访问导致程序崩溃。而且,当printf提供的参数少于格式符时也会导致指针的非法访问,导致程序崩溃(GCC -Wall会给出警告)
2.3) printf与string混用容易出错。printf是C的API,如果使用%s直接输出string变量,将有可能导致程序崩溃(VC做了非标准的处理,可以正确输出)。
3.printf需要记很多格式字符,而使用ostream则不需要。
4.当printf后跟的参数很多时,很容易将参数的顺序搞错,而使用ostream则不容易出现这种情况。
5.使用sprintf等类似函数时需要自己处理缓冲区,处理不当容易产生缓冲区溢出,导致不可预知的错误。
6. 效率。效率应该差不多,虽然cout调用函数次数比较多,但它不用解析格式字符串,效率应该并不多。在VC2010上测试即差别比较大,printf约是cout效率的10位以上,而在Cygwin下使用gcc 4.3.4测试则相差不多,printf效率约为cout的1.2倍左右,看来应该是VC库的效率问题。下面是测试代码:

		int repeat = 2000;
long long ll = 1;
long l = 2;
short s=4;
char c = 'c';
float f = 5.5f;
double df = 6.6;

clock_t start = clock();

for(int i=0; i<repeat></repeat> cout }
clock_t end = clock();
clock_t elapse_cout = end - start;

start = clock();
for(int i=0; i<repeat></repeat> printf("ll=%lld, l=%ld, i=%d, s=%d, c=%c, f=%f, df=%lf/n"
,ll, l, i, s, c, f, df);
}
end = clock();
clock_t elapse_printf = end - start;

cout

“那谁”网友一再不承认这是“解决”方案,认为是对问题的“规避”措施;一再强调说是为了说明C++流设计有缺陷,极其推崇GCC的__attribute__检查制机制,但事实证明它并不能解决问题,对于"%u”输出格式不能保证数据正确个输出,而且不能给出警告,所以很容易导致输出数据的错误。如果想彻底解决问题,通过C++标准,严格定义格式输出,并在编译期进行强类型检查,这但样做不太合适,至少不适合定义到编译器的标准中去,因为printf只是一个库函数一样,语言不可能因为某个函数制定标准。 boost::format和fastformat也都是通过格式字符串来解析,无论功能如何强大,都不可能在编译器层面解决类型检查的问题,输出错误也就很难避免。

流操作符只是一个普通的运算符,它不可能预测未来如何使用它,不可能有办法在语法上强制用户多调用它一次,不仅C++做不到,任何一种语言也都不可能做到。如果要彻底解决格式化的问题,但是可以通过RTTI来实验,这就得改变C++的变参数机制,参数列表不能使用指针。C#和java使用数组可以很好地解决这个问题,因为所有的对象都可以通过object引用来传递,保持了原对象的类型信息,而在C/C++中是不可能的。如果哪天C++标准也定义一种所有类型的超父类型object的话,这个问题就迎刃而解了。

ostream毕竟是新生事物,也凝聚了很多人的心血,牺牲了一点效率,更好地保证软件的正确性,减小由于程序员的疏忽而产生的错误,特别是严格的类型检查,保证软件输出的正确,而printf的弱类型很难保证输出的正确。鉴于软件的最基本需求----正确性,建议使用ostream替代printf。那一点点的性能差距,早已经被今天强大的硬件给弥补了。

分享到:
评论

相关推荐

    ostream用法解释

    C++的ostream类的使用方法,很详细的。。

    C语言头文件 OSTREAM.H

    C语言头文件 OSTREAM.HC语言头文件 OSTREAM.HC语言头文件 OSTREAM.HC语言头文件 OSTREAM.HC语言头文件 OSTREAM.HC语言头文件 OSTREAM.HC语言头文件 OSTREAM.HC语言头文件 OSTREAM.HC语言头文件 OSTREAM.HC语言头文件...

    流类库与输入输出(ostream,ofstream,ostringstream)

    三个重要的输出流: – ostream – ofstream – ostringstream

    ostream头文件(in Dev-cpp)

    c++文件输出类

    日志模块(c/c++),简单、快捷,就一头文件就实现了日志模块无缝接入

    一个C/C++日志记录模块,它是对开源log4c进行的封装和修正的基础上,将所有的代码都集成到一个.h头文件中。...日志输出格式方面,提供了C语言传统的字符printf格式输出,同时也为C++提供了ostream方式的流式输出..

    C++文件操作 C++ 文件操作

    ofstream: 写操作(输出)的文件类 (由ostream引申而来) ifstream: 读操作(输入)的文件类(由istream引申而来) fstream: 可同时读写操作的文件类 (由iostream引申而来) 打开文件(Open a file) 对这些类的一个...

    C++中的模拟class string类的代码 cpp

    //一个C++初学者的string类,恳请朋友们多多指点 //特殊功能:- 取负数运算符代表将字符串翻转 函数头: class string{ friend int len(string &); friend const string & operator+(const string &s1,const ...

    C++ 大整数运算库(附源码)

    用于运算、输出大整数的C++库,使用简便,即下即用,已重载各类运算符,支持ostream(cout)输出和字符串输出、字符串构造、最大公约数和最小公倍数计算。 具体用法、函数说明可以在文件夹中的README.txt中找到

    C语言头文件 OSTREAM

    C语言头文件 OSTREAMC语言头文件 OSTREAMC语言头文件 OSTREAMC语言头文件 OSTREAMC语言头文件 OSTREAMC语言头文件 OSTREAMC语言头文件 OSTREAMC语言头文件 OSTREAMC语言头文件 OSTREAMC语言头文件 OSTREAMC语言...

    C++如何通过ostringstream实现任意类型转string

    再使用整型转string的时候感觉有点棘手,因为itoa不是标准C里面的,而且即便是有itoa,其他类型转string不是很方便。后来去网上找了一下,发现有一个好方法

    C和C++头文件对比一览

    正是因为这样标准程序库中class的名称和函数名与第三方提供的程序库中的class名或是函数名发生名字冲突的可能性大大增大。为了避免这个问题的发生,标准委员会决定将标准程序库中每一样东西都放在namespace std中。...

    本人精心收集,c++头文件一览

    ostream&gt; //基本输出流 #include &lt;queue&gt; //STL 队列容器 #include &lt;set&gt; //STL 集合容器 #include &lt;sstream&gt; //基于字符串的流 #include &lt;stack&gt; //STL 堆栈容器  #include &lt;...

    C++矩阵运算的实现

    C++控制台程序,利用指针进行矩阵的加、减、乘运算 头文件代码如下: #ifndef MATRIX_H #define MATRIX_H #include using namespace std; class Matrix { public: Matrix(int zRow = 0, int zLine = 0, double *...

    面向对象与C++试题.doc

    1、关于C++与C语言关系的描述中,( )是错误的。 A.C语言是C++语言的一个子集 B.C语言与C++语言是兼容的 C.C++语言对C语言进行了一些改进 D.C++语言和C语言都是面向对象的 2、已知:int m=10; 下列表示引用的...

    Google C++ Style Guide(Google C++编程规范)高清PDF

    Other C++ Features Reference Arguments Function Overloading Default Arguments Variable-Length Arrays and alloca() Friends Exceptions Run-Time Type Information (RTTI) Casting Streams Preincrement and ...

    C++标准库stl

    这些函数在C++中不常用 &lt;csignal&gt; 为中断处理提供C样式支持 C2 支持流输入/输出的头文件 头文件 描述 &lt; iostream&gt; 支持标准流cin、cout、cerr和clog的输入和输出,它还支持多字节字符标准流wcin、wcout、wcerr和...

    c++标准IO实例示范

    c++标准IO示范c++标准IO示范c++标准IO示范c++标准IO示范实例讲解各个流的基本用法 c++标准IO示范c++标准IO示范c++标准IO示范c++标准IO示范实例讲解各个流的基本用法 c++标准IO示范c++标准IO示范c++标准IO示范c++标准...

    C++ 小型复数计算器

    CComplex CComplex::operator++() //重载运算符"++",实部与虚部均加1 { Real++; Image++; return *this; } CComplex CComplex::operator--() //重载运算符"--",实部与虚部均减1 { Real--; Image--; return *this; ...

    C++自编String类代码

    friend ostream& operator(ostream&s,const String&a) { s; return s; } friend istream& operator&gt;&gt;(istream&i,const String&a) { i&gt;&gt;a.str; return i; } ~String(); private: int len; char *str...

    md5算法的c++实现

    看到一个不错的c++实现的md5算法 class MD5 { public: typedef unsigned int size_type; // must be 32bit MD5(); MD5(const std::string& text); void update(const unsigned char *buf, size_type length); ...

Global site tag (gtag.js) - Google Analytics