http://soft.zdnet.com.cn/software_zone/2008/0118/710903.shtml<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
从std::cout和endl说起
问题是这样的……
相信下面这个程序凡是会写C++程序的同仁都认得,估计学会的第一个C++程序就是它了吧:
//----------------------------------------------
// 水之真谛
// http://blog.csdn.net/FantasiaX
//----------------------------------------------
#include <iostream>
int main(int argc, char *argv[])
{
std::cout << "Hello, World." << std::endl;
return 0;
}
我会写一点C语言的程序,于是在写这个程序的时候就对很多东西“想当然”了。比如对于操作符“<<”,在心里一直是与C语言的printf()函数对应起来的——认为它就是封装进了ostream对象中的printf()函数。既然是这样,那么对于“endl”,自然就“想当然”地认为它是“n”了。
突然有一天,在Visual Studio弹出的代码自动完成窗口中发现,endl不是一个成员变量(如果它代表一个字符,那么理应是一个字符类型的成员变量)而是一个成员函数!大脑中立刻蹦出一个解释:或许endl函数的返回值是字符“n”吧?可是这个答案存活了不到一秒钟就被否定了——如果想让一个函数执行从而得到它的返回值,应该是调用这个函数,所以写法应该是“std::endl()”而不是“std::endl”。写成“std::endl”是将函数名放在这里,并不是在调用这个函数。哈~~脑子里的概念开始互相打架了~~
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />
因为问题是出在了endl上,所以一直在查endl的定义——结果除了发现MSDN里有个Bug之外,一无所获L
MSDN里是这样声名的:
template class<_Elem, _Tr>
basic_ostream<_Elem, _Tr>& endl( basic_ostream<_Elem, _Tr>& _Ostr );
红色标记的地方写错了:p
C++ ISO文档里是这样声名的:
template <class charT, class traits>
basic_ostream<charT,traits>& endl(basic_ostream<charT,traits>& os);
按MSDN里模板的“写法”根本编译不过去,呵呵。
不过,MSDN里的说明还是非常有用的——Terminates a line and flushes the buffer. 可是函数的功能是“结束一行并冲洗缓冲区”,如果想执行这个功能,应该是调用这个函数、应该写endl()而不是endl啊……看来问题又绕回去了。于是这事儿就放下了。
今天遇到高手Sidney,又问起了这个问题。Sidney是研究过这个问题的,虽然没有给出我答案,但他提到这么一句话——“<<”操作符是被重载过的,可以接收一个函数作为参数。正好前几天我在写《深入浅出话回调》的时候写过类似的程序,经Sidney一点拨,顿时感觉豁然开朗。很快问题的答案就找到了——
1. 先查看<iostream>的成员,找到一个全局对象cout
2. 查看cout对象,发现它是ostream的一个实例
3. 查看<ostream>文件说明中的“<<”操作符,有10个重载,但是没有可将函数作为参数的
4. 仔细想了想,会不会是从别处继承来的呢?(操作符其实就是简写了的函数,完全可以当函数来对待)
5. 查看MSDN,发现ostream是由类模板basic_ostream<char, char_traits<char> >生成的
6. 查看basic_ostream<char, char_traits<char> >的说明,发现它也具有“<<”操作符,并且有15个重载。
7. 其中的一个卸载形式是——
basic_ostream& operator << ( basic_ostream& (*_Pfn)(basic_ostream&) );
说明cout的<<操作符可以接受一个函数指针(函数的地址)作为参数。
这个重载正好与endl函数的声名相匹配,所以<<后面是可以跟着endl的,也就是说,cout对象的<<操作符接受到endl函数的地址后会在后台调用endl函数,而endl函数会结束当前行并冲洗buffer。
最后啰嗦一句——你可能会问:不是函数指针吗?为什么不写“std::cout<<&endl”而写“std::cout<<endl”呢?实际上,函数名本身就代表的是函数的地址,&endl与endl的值是一样的J
不信你试试下面的代码,结果与上面的一样:
//----------------------------------------------
// 水之真谛
// http://blog.csdn.net/FantasiaX
//----------------------------------------------
#include <iostream>
int main(int argc, char *argv[])
{
std::cout << "Hello, World." << &std::endl;
return 0;
}
文章评论(0条评论)
登录后参与讨论