#include和using的区别

来源:互联网 发布:天津滨海网络广播电视 编辑:程序博客网 时间:2024/06/01 08:47

原博客地址为:http://blog.csdn.net/cyfcsd/article/details/51174394

这是一个C++里的老问题了,就像sizeof与strlen一样,困扰着很多初级程序员,所以估计看这篇文章的读者大都也是刚入门C++不久的新手,所以就不谈那些大道理了,而且那些文章已经不少了。

本文将从一个个程序试验入手,让大家对问题有个比较充分感性认识。也希望大家不要手懒,有条件还是跟着一步一步做的好。毕竟学程序,敲代码的过程谁也替不了你。

让我们先看一个简单的:(命名为main.cpp)

[cpp] view plain copy
  1. <span style="font-size:14px;">/* 
  2. * main.cpp 
  3. * 一个简单的小程序 
  4. */  
  5. int main()  
  6. {  
  7.     int a = 1;  
  8.     return 0;  
  9. }</span>  
这个程序什么也没做,只是定义了一个变量,然后就结束了。这东西当然没有实用意义,我们得用它做点儿什么才行,哪怕只是输出一句话呢!不过先不要着急,我们先来定义一个头文件和对应的源文件,为简便起见,让我们给它们起名叫1.h和1.cpp,内容如下:

[cpp] view plain copy
  1. <span style="font-size:14px;">/* 
  2. * 1.h 
  3. * 含有一个函数声明 
  4. */  
  5. #ifndef _1_H    /*头文件包含*/  
  6. #define _1_H  
  7.   
  8. void fun(); //声明一个函数  
  9.   
  10. #endif</span>  
[cpp] view plain copy
  1. <span style="font-size:14px;">/* 
  2. * 1.cpp 
  3. * 定义了一个函数 
  4. */  
  5.   
  6.   
  7. void fun(){  
  8.     int a;  
  9. }</span>  
然后将1.h包含到main.cpp中:

[cpp] view plain copy
  1. <span style="font-size:14px;">/* 
  2. * main.cpp 
  3. * 包含一个头文件 
  4. */  
  5. #include "1.h"  
  6. int main()  
  7. {  
  8.     int a = 1;  
  9.     fun();  
  10.     return 0;  
  11. }</span>  
常识着运行一下,没有错误。然后将main.cpp中的#include "1.h"注释掉,再运行一下,就不行了,提示fun()是未声明的标识符。这说明#include "1.h"的作用是将fun()的声明弄到了main.cpp中,实际上编译器做的事情就是将#include "1.h"这句话换成了1.h文件中的内容。完全是物理意义上的替换。

现在让我们回到输出的问题上,也就是cout了。但你发现直接写cout是会报错的,同样是未声明的标识符。这是因为cout和我们的fun()一样,是外来物种,并不是像int,return一样是C++语言的组成部分。所以必须要包含其头文件才可以使用,就像包含1.h一样。这也就是#include <iostream>的由来,让我们写上这句话,同时为了程序顺利运行,先把using namespace std语句也写上,一会儿再做解释。

[cpp] view plain copy
  1. <span style="font-size:14px;">/* 
  2. * main.cpp 
  3. * 加入<iostream>后 
  4. */  
  5. #include <iostream>  
  6. #include "1.h"  
  7.   
  8. using namespace std;  
  9.   
  10. int main()  
  11. {  
  12.     int a = 1;  
  13.     fun();  
  14.     cout<<a<<endl;  
  15.     return 0;  
  16. }</span>  
这样一运行就会把a输出了。到此为止,事情好像完美解决了,我们可以通过文件包含来组织我们的项目,还可以使用系统给我们提供的各种工具,比如用cout将信息输出至屏幕。

但实际开发中我们的项目文件是非常多的,它们可能是由不同的人编写出来的,并且相互之间形成复杂的包含层次。这时候就出现了同名问题,下面的代码又定义了一个头文件和对应的源文件:2.h和2.cpp。并且在里面定义了与1.h同名的函数fun():

[cpp] view plain copy
  1. <span style="font-size:14px;">/* 
  2. * 2.h 
  3. * 含有一个函数声明 
  4. */  
  5. #ifndef _2_H    /*头文件包含*/  
  6. #define _2_H  
  7.   
  8. void fun(); //声明一个与1.h中函数同名的函数  
  9.   
  10. #endif</span>  

[cpp] view plain copy
  1. <span style="font-size:14px;">/* 
  2. * 2.cpp 
  3. * 定义了一个和1.cpp中同名的函数 
  4. */  
  5.   
  6. void fun(){  
  7.     int a;  
  8. }</span>  

现在让我们把2.h也包含到main.cpp中

[cpp] view plain copy
  1. <span style="font-size:14px;">/* 
  2. * main.cpp 
  3. * 加入2.h后 
  4. */  
  5. #include <iostream>  
  6. #include "1.h"  
  7. #include "2.h"  
  8.   
  9.   
  10. using namespace std;  
  11.   
  12.   
  13. int main()  
  14. {  
  15.     int a = 1;  
  16.     //fun();  
  17.     cout<<a<<endl;  
  18.     return 0;  
  19. }  
  20. </span>  

这时候我们根本不用调用fun()编译也不会通过(其实是链接错误,一会儿再细说,反正是不能运行),甚至将前面的#include "1.h"和#include "2.h"删去也不行!这说明我们所有cpp文件中的所有函数名和变量名都不可以重复,否则就会被编译器判定为重复定义。那该怎么办呢,把其中一个的名字改了?这当然也是一种办法,但就像刚才提到的,我们的项目可能有成百上千个文件,而且还不是一个人写的,要保证里面没有同名变量实在有点儿困难。

让我们回到问题本身,编译之所以无法通过的原因就是编译器把所有的cpp文件中的名字“一锅烩”了,里面有重复的就报错。那能否不让编译器把他们放到一起呢,而是这部分放一起,那部分放一起呢?这个时候就要说到命名空间了,命名空间,顾名思义就是给命名分空间的,这个命名空间里的名字是“一锅”,那个命名空间里的名字是另“一锅”。不同锅里的名字可以重复,这样问题不就解决了吗。让我们把代码修改一下:

[cpp] view plain copy
  1. <span style="font-size:14px;"><span style="font-size:18px;">/* 
  2. * 1.h 
  3. * 加入了命名空间的定义 
  4. */  
  5. namespace ns1{  
  6.     #ifndef _1_H    /*头文件包含*/  
  7.     #define _1_H  
  8.   
  9.     void fun(); //声明一个函数  
  10.   
  11.     #endif  
  12. }</span></span>  

[cpp] view plain copy
  1. <span style="font-size:14px;"><span style="font-size:18px;">/* 
  2. * 1.cpp 
  3. * 加入了命名空间的定义 
  4. */  
  5. namespace ns1{  
  6.     void fun(){  
  7.         int a;  
  8.     }  
  9. }</span></span>  

[cpp] view plain copy
  1. <span style="font-size:14px;"><span style="font-size:18px;">/* 
  2. * 2.h 
  3. * 加入了命名空间的定义 
  4. */  
  5. namespace ns2{  
  6.     #ifndef _2_H    /*头文件包含*/  
  7.     #define _2_H  
  8.   
  9.   
  10.     void fun(); //声明一个与1.h函数同名的函数  
  11.   
  12.   
  13.     #endif  
  14. }</span></span>  

[cpp] view plain copy
  1. <span style="font-size:14px;"><span style="font-size:18px;">/* 
  2. * 2.cpp 
  3. * 加入了命名空间的定义 
  4. */  
  5. namespace ns2{  
  6.     void fun(){  
  7.         int a;  
  8.     }  
  9. }</span></span>  
先不要修改main.cpp,这个时候程序可以运行了。这说明我们定义的ns1和ns2像是两口新锅,将两个锅里定义的名字分开了,编译器将它们分别处理了。这是个好消息,但当我们去掉main.cpp中的fun()的注释后发现新问题来了,编译器提示未定义标识符。

这里要打断一下,这时候#include的作用并没有消失,编译器还是把两个头文件里的东西复制到了main.cpp里,只不过他们都嵌套在namespace里了,即便是在一个文件里,也无法访问到里面的内容。

这说明不同锅里的变量是不能随便相互引用的,我们得想办法把一个锅里的东西倒进另一个锅里才行。注意这个时候其实是有三个锅,一个ns1,一个ns2,还有一个是我们主函数所在锅,就叫它主锅吧(其实是默认命名空间)。

我们现在要在主锅里引用ns1或者ns2里的东西,这里注意只能引用其中一个,否则就又回到原点了,两个一样的名字在一个锅里是不可以的。问题搞明白了,方法估计你已经想到了,那就是用using namespace ns1/ns2(注意只能选其中一个!)将ns1或者ns2锅里的东西倒过来就可以了。这个代码就不给出来了,自己加上试一下就好。

解决了这个问题那std的问题就迎刃而解了,std就是一个已经定义了很多名称的命名空间。我们要用里面的东西就必须把它倒进我们的锅里。

还有一个萦绕在很多程序员心中的问题是到底谁包含谁的问题。是ns1包含“1.h”还是相反呢?其实这还是没有真正理解二者作用的表现,既然说到了,就多说几句,#include的作用是帮助编译(这个编译是狭义上的编译,不包含链接)过程的,包含进来的各种声明是告诉编译器这个名字是合法的,可以在文件中出现的,这样编译器就将各个cpp文件编译成了多个目标模块。到此为止,两个fun()还相安无事的呆在自己的模块里。下面就是链接了,链接的过程是为每个名字找到对应的定义,也就是对应的内存地址,这个时候就出问题了,在我们的项目中出现了两个fun()的定义,那使用时该用哪个呢?这显然是个问题,所以就出现了一个连接错误,说fun()重复定义了,不知道用哪个了。而namespace就是这个时候出现的,它实现了一种为名字找定义的机制(using声明并不会复制什么代码到你的文件里!),即链接时只在同一个锅里找定义,其他锅里有没有不管。总而言之,#include管编译,namespace管链接,二者谁也替代不了谁!

就像sizeof和strlen一样,#include和using也一样,看似关系紧密,实则貌合神离。二者可以说是没有一毛钱的关系。每个东西有每个东西自己的作用,就是这么简单。

最后,本人也是正在通往C++的路上,能力还相当有限,文中有误人子弟的地方还请各位批评指正。

原创粉丝点击