“师傅,能不能帮我过来看看?”
一听这个声音就知道,我那个“好”徒弟Young又用她那种极有“磁性”的声音在召唤我,一定是遇到了什么问题,搞不定了。
“别叫我师傅,这样感觉我很老一样,还是叫我Weily吧。”我一边说,一边走过去,一看,她开着一个IDE,似乎是在写程序。仔细一看,原来在调试最基本、最著名的那个Hello World啊,呵呵,我鼻梁上的眼镜可不是摆设用的,我也看出问题所在了。
“有什么问题啊?”我故意装作什么都不知道的样子。
“恩……我这个程序无法运行,我也不知道错在哪里,编译不通过,老是报错,而且,我这个程序是完全按照书上打的呀,应该没错的呀!可是就是……”
“喔?让我看看。”我装模作样地凑过去看显示屏,显示屏上的程序是这样的:
#include <iostream>
int main( )
{
cout << “Hello World!\n”;
return 0;
}
Young还把一本书拿到我面前,的确,那本书上的程序和屏幕上的一模一样。
“你看,我没打错吧?”Young一脸无辜的样子,难得看到她这样,这个表情看上去楚楚可怜,与平时调皮的Young截然不同。
“你别急,我知道了。先提醒你一点,就是别迷信权威和书本,任何人都有犯错的时候,这是一个优秀的程序员应该具有的素质。这个问题么,让我一点点来讲给你听。你先看看编译器报的错是什么错,记住,一个优秀的程序员能充分利用编译器的查错能力,并且,能从编译器给出的错误信息里判断出错误所在,所以,要学会读懂出错信息,这是我对你的最基本的要求。”
“喔!它报的错误是’cout’: undeclared identifier。这个我以前在学C的时候见过。就是当我的程序里使用了一个没有声明过的变量的时候,就会产生这种错误。”
“完全正确!你已经说出了错误的原因了。”
“啊?可是cout应该是在头文件iostream里面已经引入我这个程序了啊,那么就不应该没有声明过啊?”Young又一次陷入了迷茫。
“其实这个问题牵涉到C++里面的一个特性,就是关于名字空间(namespace)的作用。”
“namespace?……”
“让我们还是从你这个程序看起。cout是一个ostream对象,也就是一个输出流对象。它的确在iostream中定义了。但是,它的全称应该写成std::cout。这个std就是一个namespace,所有标准库里的东西都被封装在std这个namespace里。所以,编译器在这里就不能认出cout,因为它不在全局名字空间里。”
“那是不是我只要把cout改成std::cout就可以了啊?”
“恩,这样改动后就能用了。不过这个问题就像孔乙己的那个茴香豆的茴字的四种写法一样,这个问题的解决方法也有四种解决方法。”
“四种?那另外三种是什么?快告诉我啊!”看来我这个比喻引起了Young的兴趣。
“第一种,就是将std这个名字空间引入全局名字空间。这个方法只要在全局里,也就是任何一个函数之外,当然,最好放放在预处理之后,任何一个函数之前,这样,任何一个函数里都可以直接使用std这个namespace里面的东西,就像使用全局对象一样。例如你这个程序,就能改成这样。”
#include <iostream>
using namespace std; //引入std这个namespace
int main( )
{
cout << “Hello World!\n”;
return 0;
}
“这个程序就能通过编译,而且其他的标准库里的东西,也可以像这里的cout一样,直接使用。”
“喔!那么这个方法不是很方便吗?也不容易出错啊!”
“并不是这样。其实这么做了之后,std这个namespace就失去了它存在的意义了。C++里引入namespace的目的就是为了避免污染全局名字空间,简单地说,就是为了避免和减少命名冲突。一旦一个程序写大了,就很难避免重名,特别是多人合作的情况下。过去C中的解决方法只有靠人为的注意,并且加长名字,以避免重名。这样做会使得一些名字看上去没有意义或者难以理解,而程序员在写程序的时候,也受这个问题的限制,不能自由地命名自己使用的变量或者函数。而有了namespace就不存在这些问题了,这就是C++引入namespace这个概念所带来的便利。当然,这也使得C++里很多名字看上去特别长。”
“喔,原来如此!本来我还准备以后就一直这么用呢!”
“接下来我们讨论第二种解决方案。这个方案和前面一种类似,也是引入名字空间,只是,它只引入需要的一部分。就像这个程序,我们只需要cout这个对象,所以,我们只需将std中的cout引入就可以了。就像这样。”
#include <iostream>
using std::cout;
int main( )
{
cout << “Hello World!\n”;
return 0;
}
“喔,原来还可以这样啊!这样就可以避免了全部引入所带来的污染名字空间的问题了。那最后一种呢?”
“最后一种解决方法更简单,但是,我先要你注意,这个方法今天看过就算了,不用去记住它,更不要使用它。先让我们来看看用了这个方案的程序是什么样子的。”
#include <iostream.h> //注意这个头文件与前面所写的头文件的区别
int main( )
{
cout << “Hello World!\n”;
return 0;
}
“这个方法的程序看上去似乎最简短啊。对了,这个头文件的问题也是我一直想问你的问题。我记得原来C里面的那些标准库的头文件都是以.h结尾的啊,为什么C++的标准库的头文件都没有.h啊?”
“你能注意到这点很好,说明你在看书和写程序的时候还在用脑子思考,并且能注意到许多人往往会忽视的细节问题。其实有.h这个后缀的头文件和没有.h的头文件的最大的区别也在于namespace,有.h的头文件里的元素都是暴露在全局中的,也就是没有std这个namespace。而没有.h的头文件中的元素都是被封装在std这个namespace中的。其实C++标准委员会早就去掉了标准库里每个头文件的.h后缀,只是编译器的生产厂商为了向前兼容,为了能让在标准出台之前的程序都能用,于是仍然提供了.h的头文件。记住,这些有.h的头文件都不在C++标准之内,这也是我叮嘱你不要去使用它的原因。还有一点,就是原来C中的那些标准库头文件,其实也存在相应的C++版本的,它们的命名规则很简单,就是以c开头,后面跟上原来的文件名,但是都没有.h这个后缀。并且,其中的元素也都被封装在std中了。”
“喔,原来还有这么一个渊源啊!”
“是的。C++的发展过程中有很多类似的决策和选择,现在标准里定义的那些东西,都是专家们经过深思熟虑后才决定的,其中很多选择和决定都有着它的道理,这个你有兴趣的话可以去看看《The Design and Evolution of C++》这本书,它能告诉你C++为什么是现在这样的。其实因为C++有它的一套设计思想和设计哲学,标准库里的东西在设计上都符合这个思想,并且都保持着高效、安全的原则,所以,在你的程序中尽量地使用标准库已经提供的东西,这样你的代码将更为简短、易读,也更为安全可靠。记住这个原则。”
“恩,我记住了,并且以后会注意的。”
“好了,今天就讲这些吧,关于标准库的其他方面,以后我还会提起的。”
“好的,谢谢师傅!”
“和你说过多少遍了啊,别叫我师傅,我连女朋友都还没有呢,我怕越叫越老的。就像记住我和你说的那些原则一样,记住以后叫我Weily,别叫师傅!”
“遵命!师……”
“!”
“不是啦,我没叫你师傅,我只是说‘是’”Young还是依旧那么调皮。
“好啦,以后注意点。继续写你的程序,看你的书吧。”
“好的,Weily。今天多谢你了!”
“不用客气啦,谁叫Solmyr把你交给了我呢……”
说完,回到自己的位子上,看着桌上那杯已经凉掉了的红茶,哎……
本文地址:http://com.8s8s.com/it/it1829.htm