注册 登录
编程论坛 C++教室

【头文件】自己写的头文件在编译的时候到底是起什么作用?

蚕头燕尾 发布于 2013-11-02 20:26, 3667 次点击
我一直是这样理解的:

头文件就像是一个要被用于复制的母本,凡是有包含它的地方就全部抄过去。

但是我今天碰到一个问题:

是头文件里自己定义一个class,如果我前面理解的是对的话,为什么这个头文件里依然要写

#include <iostream>
#include<string>
using namespace std;

这样的代码才能编译通过呢?

如果是这样的话,我在主函数所在文件里面已经包含了这样的三行,不是就相当于重复包含了么?

那么这个头文件(我知道它是不单独编译的),编译器到底在编译的时候对它做了什么?

31 回复
#2
未未来2013-11-02 22:43
同问,
#3
xiaodu0002013-11-02 23:23
有点不懂楼主的意思,我也是刚学c++,我的理解是头文件中包含不同的关键字,指令什么的,你写的程序用到的东西都会在头文件中,像楼主这样的情况,是不是看下程序有没有确实用到其他头文件的地方,或者两个头文件之间有什么关联.我是这样想的
新手意见,大神勿喷。
#4
peach54602013-11-03 09:26
不会重复包含的...

下面这个例子讲的是.h文件的作用...
http://programonkey.me/to-c-file-and-h-the-concepts-and-relations/

至于重复包含:
h文件一般的写法不是这样的么
#ifndef XXX
#define XXX
...
#enddef

如果你觉得会重复包含,那你再回头去翻课本吧...


[ 本帖最后由 peach5460 于 2013-11-3 09:31 编辑 ]
#5
TonyDeng2013-11-03 09:42
学语言的时候,把重点放在算法上,就总会问这种最基础的问题,入门又不是一天两天了。
#6
蚕头燕尾2013-11-03 09:55
我就是特别想明白,到底我们所说的“头文件就像是一个要被用于复制的母本,凡是有包含它的地方就全部抄过去。”这句话

是一种逻辑上的等价理解,还是说编译器实际上就是的的确确这么做的?

如果的确就是这么做的,为什么不会重复包含?明明就是头文件里已经写了这些语句了的,你在别的已经包含了这个头文件的文件里再写一遍,不是写重复了吗?

至于:
#ifndef XXX
#define XXX
...
#enddef
我知道这是常用的用于避免“无意间”重复定义了某个东西,比如说class


但是我现在的问题是:

我觉得头文件里没有必要去写
#include <iostream>
#include<string>
using namespace std;
这样的东西,或者说我觉得根本就是非常的不应该在头文件里写这些语句!因为客户程序员根本不知道你已经在头文件里包含了iostream这样的东西了呀,

他自然会在使用这个类的时候再包含一遍iostream这样的东西,如果项目很大的话,岂不是就像是明明有用的代码只有一万行,而我们却要编译三万行这样的?

我想为什么就不能养成一个好的编码习惯,让写出的代码尽可能的“没有废话”呢?

【但愿我表达清楚了】
#7
蚕头燕尾2013-11-03 09:58
而问题的关键就是,我很不明白,为什么如果   我不写   我上面提到的   我认为可以不写的   那些代码的话,会编译报错?

这就是我的问题。

#8
未未来2013-11-03 11:51
回复 6楼 蚕头燕尾
lz偏执狂,,,
#9
蚕头燕尾2013-11-03 12:06
               

这是一个很现实的问题好不好。。。
#10
peach54602013-11-03 17:43
以下是引用蚕头燕尾在2013-11-3 09:55:58的发言:

我就是特别想明白,到底我们所说的“头文件就像是一个要被用于复制的母本,凡是有包含它的地方就全部抄过去。”这句话

是一种逻辑上的等价理解,还是说编译器实际上就是的的确确这么做的?

如果的确就是这么做的,为什么不会重复包含?明明就是头文件里已经写了这些语句了的,你在别的已经包含了这个头文件的文件里再写一遍,不是写重复了吗?

至于:
#ifndef XXX
#define XXX
...
#enddef
我知道这是常用的用于避免“无意间”重复定义了某个东西,比如说class


但是我现在的问题是:

我觉得头文件里没有必要去写
#include
#include
using namespace std;
这样的东西,或者说我觉得根本就是非常的不应该在头文件里写这些语句!因为客户程序员根本不知道你已经在头文件里包含了iostream这样的东西了呀,

他自然会在使用这个类的时候再包含一遍iostream这样的东西,如果项目很大的话,岂不是就像是明明有用的代码只有一万行,而我们却要编译三万行这样的?

我想为什么就不能养成一个好的编码习惯,让写出的代码尽可能的“没有废话”呢?

【但愿我表达清楚了】

1,编译器确实是这么做的...
你问这个问题说明你看书看到include宏的时候不仔细...
include其实就是个文件拷贝替换...

2,为什么不会重复包含,因为有类似下面我写的那些防止重复的宏...
你说你知道这些宏是防止重复,我怎么感觉你不知道呢?

3,一般而言,尽量少在头文件里include东西,转而使用前置声明来声明相应的类型...
暂时不展开,呵呵...
你先自己领悟一下...
#11
peach54602013-11-03 17:44
以下是引用蚕头燕尾在2013-11-3 09:58:51的发言:

而问题的关键就是,我很不明白,为什么如果   我不写   我上面提到的   我认为可以不写的   那些代码的话,会编译报错?

这就是我的问题。


不写的确编译报错,你试一下就会知道...
为什么报错,你先想一下...
过几天我再告诉你...
或者有人会在我下次回答你问题前告诉你,呵呵
#12
TonyDeng2013-11-03 17:44
.h頭文件是預處理器做文本嵌入到代碼源文件中的,但正式編譯是編譯器在編譯時根據預處理語句按實際條件編譯的。
#13
蚕头燕尾2013-11-03 20:47
实际条件?这话是什么意思?

难道我在头文件里不写那些语句,就会被编译器认为是“不合法”的一种“实际条件”吗?

那这个编译器岂不是太不讲道理了哎……

#14
蚕头燕尾2013-11-04 05:32
bloody hell
#15
blueskiner2013-11-04 08:46
#ifxxxx #error #defing这些就是条件,预编译指令
#16
peach54602013-11-04 09:07
以下是引用蚕头燕尾在2013-11-3 20:47:14的发言:

实际条件?这话是什么意思?

难道我在头文件里不写那些语句,就会被编译器认为是“不合法”的一种“实际条件”吗?

那这个编译器岂不是太不讲道理了哎……



我觉着吧,应该这么断句

但正式編譯是
編譯器在編譯時
根據預處理語句
按實際(情况)
條件編譯的。
#17
blueskiner2013-11-04 14:14
只有本站会员才能查看附件,请 登录
#18
peach54602013-11-04 14:53
回复 17楼 blueskiner
不要发无谓的东西,下不为例
#19
蚕头燕尾2013-11-05 09:10
等我解决了这个问题,要自己来回答这个问题。

#20
peach54602013-11-05 10:08
以下是引用蚕头燕尾在2013-11-5 09:10:13的发言:

等我解决了这个问题,要自己来回答这个问题。


看来你还是一知半解...
多看书,打牢基础...
勿在浮砂筑高台...
#21
蚕头燕尾2013-11-05 13:53
勿在浮砂筑高台

好熟悉的一句话啊,侯俊杰的话?

#22
TonyDeng2013-11-05 14:28
还没想明白?与处理器把所有.h嵌入到代码文件中,如果最开始的.h已经声明了一个宏标识,那么后续嵌入的.h内容就被#ifndef废掉了,这样的设置不管嵌入多少次,都不会有影响,实际上等于一次。源文件文本巨大没关系的,编译和链接处理会把不需要的东西剔除,这就是编译型语言的优势——不要被源代码欺骗自己,你看到的与编译器看到的很可能是两回事。
#23
peach54602013-11-05 17:30
以下是引用蚕头燕尾在2013-11-5 13:53:41的发言:

勿在浮砂筑高台

好熟悉的一句话啊,侯俊杰的话?


嗯,侯杰<MFC深入检出>里面的话,我很喜欢
打好基础才能去玩技巧,要不然,总会摔跤的...
#24
peach54602013-11-05 17:33
以下是引用TonyDeng在2013-11-5 14:28:10的发言:

还没想明白?与处理器把所有.h嵌入到代码文件中,如果最开始的.h已经声明了一个宏标识,那么后续嵌入的.h内容就被#ifndef废掉了,这样的设置不管嵌入多少次,都不会有影响,实际上等于一次。源文件文本巨大没关系的,编译和链接处理会把不需要的东西剔除,这就是编译型语言的优势——不要被源代码欺骗自己,你看到的与编译器看到的很可能是两回事。

为楼主补充一下:
如果实在还是搞不懂的话...
去搜一下C++编译器相关的知识...如果你去看编译原理更好...

C++的编译首先经过预处理,词法分析,语法分析...(具体记不清了,我讨厌记概念)...
其中预处理,就是将宏展开,并且将可计算值进行计算,等等...
#25
蚕头燕尾2013-11-06 03:33
回复 24楼 peach5460
3你们说的这些我之前也是有所耳闻的,

关于那个宏定义防止重复定义的道理我想我是大概懂得的,因为自己写的代码就一直是遵循了这样的方法。

关于   编译器的预处理都做了些什么工作  我也了解过一些。

我的问题的关键不在这里,而是在后面:

为什么不在头文件里写我前面提到的那样几句代码就会报错。

基于前文所述的理解,写上那些代码的话要通过编译器的预处理进行“排除重复”【这里我不想讨论具体是采用了什么样的技术或者原理进行了“排除重复”,只是表达了这样一个意

思:在编译器未进行“排除重复”工作之前,代码确实是有重复的部分存在】

而我的疑问是:

如果我不在头文件里写那几行代码,是不是也应该是“不缺代码”的,只是编译器不用进行“排除重复”的工作了而已,我认为这样做也不应该是错误的。就是我们的代码没有重复的部

分了而已。且不论这样做是否有益,我所强调的是:这样做不该是错误的做法!

而实际情况是:

这样做却是错误的!要想不报错,就得在头文件里写上那几行代码,这与之前的理解完全相矛盾。甚至可以推测“编译器根本就没有进行排除重复工作,因为在它看来,在头文件里有那

几行代码是没有形成重复的”,而这一点与我之前的理解相违背。

【我的这个提问,目的不是为了问怎样去避免代码出现重复定义,也不是为了知道编译器是如何处理或者避免重复代码,我想知道的是:为什么我觉得会出现重复代码的情况下,我删除一部分的重复代码会出错,或者说,为什么我认为会出现重复代码的情况下,编译器却认为并没有重复代码出现。所以我质疑我之前的概念:头文件不单独编译,只是遇到包含语句的时候全盘复制过去。。。因为这个观点在这个情况下是解释不通的】

#26
蚕头燕尾2013-11-06 03:44
我想我这个人可能是有代码洁癖吧,一直奉承“用什么写什么”的观点

比如在一些小的练手的程序里,我更倾向于写using std::cout; using std::cin;   using std::endl;    而不是using namespace std;

比如:有一次一个朋友就是用了后者方式,结果定义了一个friend函数,函数名为distance,自然是报错的了

因为后者包含了using std::distance

这些错误往往都是莫名其妙,需要一定的经验才能排错,我费了将近十分钟才搞明白原因,从此对一些看起来“很大”的语句的使用都很小心。

或许我的这个问题有点就像前面某楼说的“偏执狂”,但我是真心的想知道这是为什么……

#27
peach54602013-11-06 08:13
哦,我一直以为你是不知道头文件的作用...
看来我理解错了一部分...你还是知道一点的...
但是我还是要说你不理解头文件的作用...
#28
peach54602013-11-06 08:18
首先,你要搞清楚,编译的时候,不可能是把所有文件合并到main的文件里面去,组成一个超级庞大的文件再进行编译
C++的编译模型是分别编译,再链接...
那么分别编译的时候你就要保证编译的通过...

不可能像你在1L写的...我主工程包含过了,其他工程就不需要了...
#29
peach54602013-11-06 08:27
只有本站会员才能查看附件,请 登录

你看到的是文件...
但是编译器看到的是编译单元...
你必须保证编译单元内可编译有效...

而不是你在最开始说的,我主项目文件包含过了,其他的都不用包含...
#30
蚕头燕尾2013-11-06 13:20
哈,我想斑竹终于明白我的问题了……

虽然我还不太理解这里的“编译单元”的概念【之前没听过这个词】

但是我知道这样一点:头文件不能编译【其实对此我也有个小疑问:如果我想写一个头文件给别人用,注意,就一个头文件,没有其他的代码了,而且我又不想开源,我该怎么办?】

正因为头文件的“不单独编译”,所以我觉得头文件里可以随便写,写什么都行,反正它就是一个要用于复制的“母本”呗,只要你把它放到你#include的地方能够前后文合理就ok了呗。

可事实并非如此~!!!!头文件不是爱写什么就写什么的!!!这是为什么呢?

这就是我为什么要问:头文件究竟是起了什么作用?究竟是不是我理解的“复制母本”的作用!!!

我还没有学编译原理,对编译器的一些行为不是很理解~~我想我应该为此查一下编译原理

~~~~还是谢谢斑竹啦~~~~

#31
peach54602013-11-06 14:04
回复 30楼 蚕头燕尾
难得在我的版块看到这么有营养的帖子,当然要好好养啊,养肥了再杀...
不过话说...你熟悉了C++的编译模型,这个问题就不用我跟你讲了,呵呵...
#32
TonyDeng2013-11-09 13:41
頭文件中用到了需要事先聲明的變量或標識符,就需要#include相應的頭。編譯器怎麼知道你會不會在別的模塊中鏈入相應的頭,它只知道當前需要什麼就要求你寫什麼。再說一次,經過預處理的源代碼文檔,可能是你無法想像的龐大文檔,編譯過程有多次掃描和篩選處理過程(在DOS時代就有3趟,MSC的編譯比TC/BC慢就是把每一趟的文件輸出到磁盤中,所以編譯後文件夾中殘留的文件數量很多,而後者是直接在內存中處理,文件夾很乾淨,用戶看不到,就以為編譯是一次生成最終目標文件的)。
1