C++头文件与源文件
C++基础之头文件和源文件的关系
C++基础之头文件和源文件的关系今天找了个解析xml的开源C++项目tinyxml,按照网上的说法去编译,但是一直编译不通过,“无法打开头文件tinyxml.h”,但是明明我在工程底下有了这个文件,对于我这种初学C++的人来说我并不知道头文件和源文件到底什么关系(不像java的类文件),不过最终还是解决了这个问题。
一、源文件如何根据#include来关联头文件1,系统自带的头文件用尖括号括起来,这样编译器会在系统文件目录下查找。
#include2,用户自定义的文件用双引号括起来,编译器首先会在用户目录下查找,然后在到C++安装目录(比如VC中可以指定和修改库文件查找路径,Unix和Linux中可以通过环境变量来设定)中查找,最后在系统文件中查找。
#include“"xxx.h”(我一直以为””和<>没什么区别,但是tinyxml.h是非系统下的都文件,所以要用””)二、头文件如何来关联源文件这个问题实际上是说,已知头文件“a.h”声明了一系列函数,“b.cpp”中实现了这些函数,那么如果我想在“c.cpp”中使用“a.h”中声明的这些在“b.cpp”中实现的函数,通常都是在“c.cpp”中使用#include“a.h”,那么c.cpp是怎样找到b.cpp中的实现呢?其实.cpp和.h文件名称没有任何直接关系,很多编译器都可以接受其他扩展名。
比如偶现在看到偶们公司的源代码,.cpp文件由.cc文件替代了。
在Turbo C中,采用命令行方式进行编译,命令行参数为文件的名称,默认的是.cpp和.h,但是也可以自定义为.xxx等等。
谭浩强老师的《C程序设计》一书中提到,编译器预处理时,要对#include命令进行“文件包含处理”:将file2.c的全部内容复制到#include“file2.c”处。
这也正说明了,为什么很多编译器并不care到底这个文件的后缀名是什么----因为#include预处理就是完成了一个“复制并插入代码”的工作。
C语言中为什么要头文件和源文件分开写
C语言中为什么要头文件和源文件分开写对c&c++程序来说,基本上来说都是要把源文件和头文件分别编写。
一般都是代表一个基本功能的源文件引用相应的头文件。
一个相关功能的模块可能有若干对源文件和头文件组成。
这是基于组件编程的核心。
在我看来,他的好处是巨大的,是java不可比拟的,也是不可复制的:c语言中头文件中一般定义了函数的声明、结构体的定义、宏定义。
(常量和全局变量最好放到源文件中)1) 从业务扩展性上看:头文件中放函数的声明,函数由源文件实现,这就是将面向接口编程:接口和实现分开,这在面对业务变更频繁的需求中技术实现的好处是显而易见的--只要定义出良好地、扩展性高的接口,实现是可以很方便的更换。
2) 从程序架构上看:代码在在大型程序中需要分成不同的模块,单一模块中又可能分为不同的业务功能单元,他们间有很多相互的调用。
头文件中的方法声明、结构体定义、宏就都可以充当这部分的模块与模块间、业务功能单位间的接口调用。
模块与模块间,功能单元与功能单元间都是面向接口的调用,耦合性低,这正是基于组件编程的核心思想。
3) 从某些技术角度实现上看:头文件可通过宏定义来保证类定义、结构体定义、宏定义的唯一性。
确实很方便,不容易出错。
在用makefile编译程序时,各个功能单元单独编译,构成中间文件.最终这些中间文件链接成可执行程序,在这些中间文件中重复引用同一头文件是不可避免的。
但如果头文件有保护性编程就可以很容易保证类、结构体、宏定义的唯一性----最终链接成可执行程序时,可执行程序中代码区中只会有唯一的类、结构体、宏的定义,其他都因为不满足唯一性保护失效。
这样的实现是不是很‘优雅’:)))? 我不知道你是否是这样认为,至于我,我是这样认为,haha~。
C++中源文件和头文件的区别
C++的源代码文件分为两类:头文件(Header file)和源文件(Source code file)。
头文件用于存放对类型定义、函数声明、全局变量声明等实体的声明,作为对外接口;而源程序文件存放类型的实现、函数体、全局变量定义.C++的源代码文件分为两类:头文件(Header file)和源文件(Source code file)。
头文件用于存放对类型定义、函数声明、全局变量声明等实体的声明,作为对外接口;而源程序文件存放类型的实现、函数体、全局变量定义。
对于商业C++程序库,一般把头文件随二进制的库文件发布,而源代码保留。
一般情况下头文件常以.h或.hpp作为扩展名,而实现文件常以.cpp或.cc为扩展名。
头文件一般不直接编译,一个源文件代表一个“编译单元”。
在在编译一个源文件时,如果引用的类型、函数或其它实体不在本编译单元内,可以通过引用头文件将其它编译单元内实现的实体引入到本编译单元。
而从本质上讲,这些源代码文件都是纯文本文件,可以使用任何一款文本编译器进行源代码的编辑,并没有本质的区别,这些头文与实现文件的扩展名只是一种习惯。
而C++的标准库的头文件则不使用扩展名,例如string、 iostream、cstdio 等头文件。
对与源文件也一样,你完全可以使用.inl或.cplusplus作为文件的扩展名。
事实上,在一些C++的项目中.inl被用作源代码文件的扩展名,保存内联函数,直接包含在源文件中,如ACE(the Adaptive Communication Environment, /~schmidt/ACE.html)等。
gcc默认支持的C++源文件扩展名有.cc、.cp、.cpp、.cxx、.c++、.CPP、.C(注意后两项是大写,在Unix/Linux 上的文件名是区分大小写的)。
例如在gcc中你可以这样编译一个扩展名为.cplusplus的C++程序:g++ -x c++ demo.cplusplus虽然文件名对程序没有任何影响,但.cpp和.cc这些扩展名是编译器默认支持的,使用这些扩展名您就不需要手动添加编译选项支持您使用的扩展名,如gcc 中的-x选项。
CC++中的源文件与头文件的区别
CC++中的源⽂件与头⽂件的区别问题提出的背景:最近在⾃⼰动⼿,⽤C来实现各类经典算法,还搬到了Github上,但是有⼀个问题⽐较困扰我,就是这些可以复⽤的,作为⼯具⽅法的算法,究竟应该放在头⽂件还是源⽂件⾥?⼀般的、通⽤的准则到底是什么呢?或者说头⽂件与源⽂件的作⽤究竟是什么?在编译连接等过程中,编译器会对他们有怎样的区别对待呢?⼀、实现究竟放在哪⾥?⾸先回答第⼀个问题,⼀般来说,什么时候需要把实现放在头⽂件⾥,什么时候⼜需要把实现放在源⽂件⾥?(此部分参考:)不把实现放在头⽂件中,往往是出于以下⼏种顾虑:1、暴露了实现细节2、头⽂件被包含到不同的源⽂件中,会导致链接冲突3、头⽂件被包含到不同的源⽂件中,会导致有多份实现被编译出来,增⼤可执⾏体的体积如果有顾虑 1 ,那很显然应该在第⼀时间抛弃完全在头⽂件中实现的念头。
⾄于顾虑 2和3 的,我们举例如下。
例如有以下头⽂件 c_function.h:int integer_add(const int a, const int b){return a + b;}如果在同⼀⼯程中,有 a.c 和 b.c 两个(或两个以上)源⽂件包含了此头⽂件,则在链接时期就会发⽣冲突,因为在两个源⽂件编译得到的⽬标⽂件中都有⼀份 integer_add 的函数实现,导致链接器不知道对于调⽤了此函数的调⽤者,应该使⽤哪⼀个副本。
解决冲突办法有两个,⼀个是加上 inline ,另⼀个是加上 static 。
使⽤这两个关键字的任意⼀个来修饰 integer_add 函数,然⽽本质却⼤不相同。
如果使⽤ inline ,则意味着编译器会在调⽤此函数的地⽅把函数的⽬标代码直接插⼊,⽽不是放置⼀个真正的函数调⽤,实际作⽤就是这个函数事实上已经不再存在,⽽是像宏⼀样被就地展开了。
使⽤ inline 的副作⽤,⾸先在于⽏庸置疑地,代码的体积变⼤了;其次则是,这个关键字严格算起来并不是 C 语⾔的关键字,使⽤它多少会带来⼀些移植性⽅⾯的风险。
关于头文件和源文件的分别
关于头⽂件和源⽂件的分别关于头⽂件和源⽂件的分别⾸先,我们可以将所有东西都放在⼀个.cpp⽂件内.然后编译器就将这个.cpp编译成.obj,obj是什么东西?就是编译单元了.⼀个程序,可以由⼀个编译单元组成,也可以有多个编译单元组成. 如果你不想让你的源代码变得很难阅读的话,就请使⽤多个编译单元吧.(⼀个函数不能放到两个编译单元⾥⾯,但两个以上就可以分别放在⼀个单元,也就是cpp⾥⾯)那么就是⼀个.cpp对应⼀个.obj,然后将所有的obj链接起来(通过⼀个叫链接器的程序),组成⼀个.exe,也就是程序了.如果⼀个.cpp要⽤到另⼀个.cpp定义的函数怎么办? 只需在这个.cpp种写上他的函数声明就可以了.其余⼯作由链接器帮你完成,你可以随便调⽤该函数.链接器将所有的obj连接起来,但是如果碰巧有相同的函数或外部变量怎么办?他如何识别?⼀般来说是不能允许在同⼀个程序中,出现两个⼀样的函数名或外部变量名.但是只得庆幸的是,c++可以通过⼀种叫做链接属性的关键字来限定,你这个函数是属于整个程序公⽤的,还是只是在⼀个编译单元obj⾥⾯使⽤的.这些关键字就是extern 和 static; extern是外部链接的意思,也就是除了这个单元,外部的单元也是能够访问这个函数的.static 是内部链接,⾃属于⾃⼰单元.说了这么久,还没有说.h的作⽤呢?其实没有.h也能很好的⼯作,但是当你发现⼀个外部链接的函数或外部变量,需要许多份声明,因为c++这种语⾔,在使⽤函数和变量的时候,必须将他声明,为何要声明?声明之后才知道他的规格,才能更好的发现不和规格的部分.你别妄想⼀个编译单元,会⾃动从另⼀个编译单元那⾥得到什么信息,知道你是如何定义这个函数的.所以说,只要使⽤到该函数的单元,就必须写⼀份声明在那个.cpp⾥⾯,这样是不是很⿇烦,⽽且,如果要修改,就必须⼀个⼀个修改.这真让⼈受不了..h就是为了解决这个问题⽽诞⽣,他包含了这些公共的东西.然后所有需要使⽤该函数的.cpp,只需要⽤#include包含进去便可.以后需要修改,也只是修改⼀份内容.请注意不要滥⽤.h,.h⾥⾯不要写代码,.h不是.cpp的仓库,什么都塞到⾥⾯.如果在⾥⾯写代码,当其他.cpp包含他的时候,就会出现重复定义的情况,⽐如将函数func(){printf};放到头⽂件a.h,⾥⾯还有⼀些a.cpp需要的声明等;然后你发现b.cpp需要⽤到a.cpp⾥⾯的⼀个函数,就很⾼兴的将a.h包含进来.注意,#include并不是什么申请指令,他就是将指定的⽂件的内容,原封不动的拷贝进来.这时候实际上a.cpp和b.cpp都有⼀个func()函数的定义.如果这个函数是内部链接static的话,还好,浪费了⼀倍空间;如果是extern,外部链接(这个是默认情况),那么根据在同⼀个程序内不可出现同名函数的要求,连接器会毫不留情给你⼀个连接错误!。
C++中头文件(.h)和源文件(.cpp)都应该写些什么
C++中头⽂件(.h)和源⽂件(.cpp)都应该写些什么头⽂件(.h):写类的声明(包括类⾥⾯的成员和⽅法的声明)、函数原型、#define常数等,但⼀般来说不写出具体的实现。
在写头⽂件时需要注意,在开头和结尾处必须按照如下样式加上预编译语句(如下):#ifndef CIRCLE_H#define CIRCLE_H//你的代码写在这⾥#endif这样做是为了防⽌重复编译,不这样做就有可能出错。
⾄于CIRCLE_H这个名字实际上是⽆所谓的,你叫什么都⾏,只要符合规范都⾏。
原则上来说,⾮常建议把它写成这种形式,因为⽐较容易和头⽂件的名字对应。
源⽂件(.cpp):源⽂件主要写实现头⽂件中已经声明的那些函数的具体代码。
需要注意的是,开头必须#include⼀下实现的头⽂件,以及要⽤到的头⽂件。
那么当你需要⽤到⾃⼰写的头⽂件中的类时,只需要#include进来就⾏了。
下⾯举个最简单的例⼦来描述⼀下,咱就求个圆⾯积。
第1步,建⽴⼀个空⼯程(以在VS2003环境下为例)。
第2步,在头⽂件的⽂件夹⾥新建⼀个名为Circle.h的头⽂件,它的内容如下:#ifndef CIRCLE_H#define CIRCLE_Hclass Circle{private:double r;//半径public:Circle();//构造函数Circle(double R);//构造函数double Area();//求⾯积函数};#endif注意到开头结尾的预编译语句。
在头⽂件⾥,并不写出函数的具体实现。
第3步,要给出Circle类的具体实现,因此,在源⽂件夹⾥新建⼀个Circle.cpp的⽂件,它的内容如下:#include "Circle.h"Circle::Circle(){this->r=5.0;}Circle::Circle(double R){this->r=R;}double Circle:: Area(){return 3.14*r*r;}需要注意的是:开头处包含了Circle.h,事实上,只要此cpp⽂件⽤到的⽂件,都要包含进来!这个⽂件的名字其实不⼀定要叫Circle.cpp,但⾮常建议cpp⽂件与头⽂件相对应。
C语言中的头文件与原文件
C语言中的头文件与原文件简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程:1.预处理阶段2.词法与语法分析阶段3.编译阶段,首先编译成纯汇编语句,再将之汇编成跟CPU相关的二进制码,生成各个目标文件(.obj文件)4.连接阶段,将各个目标文件中的各段代码进行绝对地址定位,生成跟特定平台相关的可执行文件,当然,最后还可以用objcopy生成纯二进制码,也就是去掉了文件格式信息。
(生成.exe 文件)编译器在编译时是以C文件为单位进行的,也就是说如果你的项目中一个C文件都没有,那么你的项目将无法编译,连接器是以目标文件为单位,它将一个或多个目标文件进行函数与变量的重定位,生成最终的可执行文件,在PC上的程序开发,一般都有一个main函数,这是各个编译器的约定,当然,你如果自己写连接器脚本的话,可以不用main函数作为程序入口(main.c文件目标文件可执行文件)有了这些基础知识,再言归正传,为了生成一个最终的可执行文件,就需要一些目标文件,也就是需要C文件,而这些C文件中又需要一个main函数作为可执行程序的入口,那么我们就从一个C文件入手,假定这个C文件内容如下:#include#include"mytest.h"intmain(intargc,charargv){test=25;printf("test.................%d/n", test);}头文件内容如下:inttest;现在以这个例子来讲解编译器的工作:1.预处理阶段:编译器以C 文件作为一个单元,首先读这个C文件,发现第一句与第二句是包含一个头文件,就会在所有搜索路径中寻找这两个文件,找到之后,就会将相应头文件中再去处理宏,变量,函数声明,嵌套的头文件包含等,检测依赖关系,进行宏替换,看是否有重复定义与声明的情况发生,最后将那些文件中所有的东东全部扫描进这个当前的C文件中,形成一个中间“C文件”2.编译阶段,在上一步中相当于将那个头文件中的test变量扫描进了一个中间C文件,那么test变量就变成了这个文件中的一个全局变量,此时就将所有这个中间C文件的所有变量,函数分配空间,将各个函数编译成二进制码,按照特定目标文件格式生成目标文件,在这种格式的目标文件中进行各个全局变量,函数的符号描述,将这些二进制码按照一定的标准组织成一个目标文件3.连接阶段,将上一步成生的各个目标文件,根据一些参数,连接生成最终的可执行文件,主要的工作就是重定位各个目标文件的函数,变量等,相当于将个目标文件中的二进制码按一定的规范合到一个文件中再回到C文件与头文件各写什么内容的话题上:理论上来说C文件与头文件里的内容,只要是C语言所支持的,无论写什么都可以的,比如你在头文件中写函数体,只要在任何一个C文件包含此头文件就可以将这个函数编译成目标文件的一部分(编译是以C文件为单位的,如果不在任何C文件中包含此头文件的话,这段代码就形同虚设),你可以在C文件中进行函数声明,变量声明,结构体声明,这也不成问题那为何一定要分成头文件与C文件呢?又为何一般都在头件中进行函数,变量声明,宏声明,结构体声明呢?而在C 文件中去进行变量定义,函数实现呢??原因如下:1.如果在头文件中实现一个函数体,那么如果在多个C文件中引用它,而且又同时编译多个C文件,将其生成的目标文件连接成一个可执行文件,在每个引用此头文件的C文件所生成的目标文件中,都有一份这个函数的代码,如果这段函数又没有定义成局部函数,那么在连接时,就会发现多个相同的函数,就会报错2.如果在头文件中定义全局变量,并且将此全局变量赋初值,那么在多个引用此头文件的C文件中同样存在相同变量名的拷贝,关键是此变量被赋了初值,所以编译器就会将此变量放入DATA段,最终在连接阶段,会在DATA段中存在多个相同的变量,它无法将这些变量统一成一个变量,也就是仅为此变量分配一个空间,而不是多份空间,假定这个变量在头文件没有赋初值,编译器就会将之放入BSS段,连接器会对BSS段的多个同名变量仅分配一个存储空间3.如果在C文件中声明宏,结构体,函数等,那么我要在另一个C文件中引用相应的宏,结构体,就必须再做一次重复的工作,如果我改了一个C文件中的一个声明,那么又忘了改其它C文件中的声明,这不就出了大问题了,程序的逻辑就变成了你不可想象的了,如果把这些公共的东东放在一个头文件中,想用它的C文件就只需要引用一个就OK了这样岂不方便,要改某个声明的时候,只需要动一下头文件就行了4.在头文件中声明结构体,函数等,当你需要将你的代码封装成一个库,让别人来用你的代码,你又不想公布源码,那么人家如何利用你的库呢?也就是如何利用你的库中的各个函数呢??一种方法是公布源码,别人想怎么用就怎么用,另一种是提供头文件,别人从头文件中看你的函数原型,这样人家才知道如何调用你写的函数,就如同你调用printf函数一样,里面的参数是怎样的??你是怎么知道的??还不是看人家的头文件中的相关声明啊当然这些东东都成了C标准,就算不看人家的头文件,你一样可以知道怎么使用c语言中.c和.h文件的困惑?本质上没有任何区别。
3、C编程的各种源码文件
3、C编程的各种源码⽂件1、C语⾔模块化编程中的头⽂件 实际开发中⼀般是将函数和变量的声明放到头⽂件,再在当前源⽂件中 #include 进来。
如果变量的值是固定的,最好使⽤宏来代替。
.c和.h⽂件都是源⽂件,除了后缀不⼀样便于区分外和管理外,其他的都是相同的,在.c中编写的代码同样也可以写在.h中,包括函数定义、变量定义、预处理等。
但是,.h 和 .c 在项⽬中承担的⾓⾊不⼀样:.c ⽂件主要负责实现,也就是定义函数和变量;.h ⽂件主要负责声明(包括变量声明和函数声明)、宏定义、类型定义等。
这些不是C语法规定的内容,⽽是约定成俗的规范,或者说是长期形成的事实标准。
根据这份规范,头⽂件可以包含如下的内容:可以声明函数,但不可以定义函数。
可以声明变量,但不可以定义变量。
可以定义宏,包括带参的宏和不带参的宏。
结构体的定义、⾃定义数据类型⼀般也放在头⽂件中。
在项⽬开发中,我们可以将⼀组相关的变量和函数定义在⼀个 .c ⽂件中,并⽤⼀个同名的 .h ⽂件(头⽂件)进⾏声明,其他模块如果需要使⽤某个变量或函数,那么引⼊这个头⽂件就可以。
这样做的另外⼀个好处是可以保护版权,我们在发布相关模块之前,可以将它们都编译成⽬标⽂件,或者打包成静态库,只要向⽤户提供头⽂件,⽤户就可以将这些模块链接到⾃⼰的程序中。
2、C语⾔标准库以及标准头⽂件 源⽂件通过编译可以⽣成⽬标⽂件(例如 GCC 下的 .o 和 Visual Studio 下的 .obj),并提供⼀个头⽂件向外暴露接⼝,除了保护版权,还可以将散乱的⽂件打包,便于发布和使⽤。
实际上我们⼀般不直接向⽤户提供⽬标⽂件,⽽是将多个相关的⽬标⽂件打包成⼀个静态链接库(Static Link Library),例如 Linux 下的 .a 和 Windows 下的 .lib。
打包静态库的过程很容易理解,就是将多个⽬标⽂件捆绑在⼀起形成⼀个新的⽂件,然后再加上⼀些索引,⽅便链接器找到,这和压缩⽂件的过程⾮常类似。
头文件与源文件
头⽂件与源⽂件以下内容全部是个⼈总结,如果有错误请指正!在初学C++的时候,我总是彷徨于不恰当使⽤头⽂件、声明、定义、源⽂件⽽导致的各种Link错误。
今天我想通过⼀些简单的测试,得到⼀些概括的,⼀般性的结论。
(因为我没有学习过C++的编译器,所以我所以这些结论仅仅是⼀些根据⼀些现象的猜想)实验环境:集成开发环境(Visual Studio 2017),并没有直接使⽤过g++的shell命令。
1. 在Visual Studio 2017 环境下,项⽬中的⽂件会被赋予不同的类型每个类型有各⾃的作⽤。
1.1 头⽂件 头⽂件不会参与Link过程,所以头⽂件中如果存在语法错误,是不会被发现的。
e.g:在头⽂件⽂件夹下新建⽂件:输⼊“wrong code”。
运⾏源.cpp,发现正常运⾏。
1.2 源⽂件 源⽂件既可以被 #include 导⼊(因为头⽂件源⽂件类型的区分是VS的限定,但对于⼀个编译器来说任何⽂件都是没有区别的,只有输⼊的参数不同),但导⼊之后同样会编译⾃⾝之后Link。
e.g:在源⽂件⽂件夹下新建⽂件a.h: 定义函数 a, 在源.cpp 中include该⽂件,并使⽤a函数。
系统将出现符号重定义的错误:因为 VS认为a.h是源⽂件故编译了他。
⽽源.cpp中include的部分也被编译,故出现了两个相同的符号。
2. C++ 的 #include 对⽂件的名字(后缀)不敏感2.1 可以使⽤⽆ .h 后缀的⽂件作为 #include 的⽬标 这个是⼀个不需要实验的结论,因为常⽤的#include <iostream>便是⼀个很好的例⼦。
2.2 如果把⼀个正常的 .h ⽂件改成 .cpp 后缀,效果是⼀样的正常情况:// 源.cpp#include "A"#include <iostream>int main() {A a;//int main2();std::cout << "在main⾥:" << a.value << std::endl;//std::cout << "在main⾥:" << a.value << " " << a.functionA() << std::endl;//std::cout << "在main2⾥:";//main2();getwchar();return1;}// A#pragma onceclass A{public:int value = 100;//A();//~A();int functionA();private:};正常输出当 #include 的对象改为 .cpp 后缀// 源.cpp#include "A.cpp"#include <iostream>int main() {A a;//int main2();std::cout << "在main⾥:" << a.value << std::endl;//std::cout << "在main⾥:" << a.value << " " << a.functionA() << std::endl;//std::cout << "在main2⾥:";//main2();getwchar();return1;}// A.cpp#pragma onceclass A{public:int value = 100;//A();//~A();int functionA();private:};正常输出3. #include 是的本质是将 #include⽂件的全体代码替换到当前位置3.1 (猜想)因为 #include 带 “#” 所以是预处理过程,其过程将先与其他语法检查步骤使⽤ #include 语句// 源.cpp#include "testInclude.h"#include <iostream>int main() {std::cout << functionToTestInclude();getwchar();return1;}// testInclude.h#pragma onceint functionToTestInclude() { return1; }正常输出直接将 #include 对象替换掉 #include 语句// 源.cppint functionToTestInclude() { return1; }#include <iostream>int main() {std::cout << functionToTestInclude();getwchar();return1;}正常输出结论:1. 替换后效果⼀样。
VS2008教程
for(;;) {
pos1 = pos2 = 0;
if((pos1 = tmpstr.find_first_not_of(delimiters, pos2))
== string::npos)
break;
if((pos2 = tmpstr.find_first_of(delimiters, pos1))
}
return fields.size();
}
函数声明可以放在任何一个调用它的函数之前,而且在调用一个函数之前必须在调用者函数之前定义或声明被调函数。函数的定义只能有一次,如果调用者与被调用者不在同一编译单元,只能在调用者之前添加函数的声明。函数定义只能有一次,函数声明可以有无限次(理论上),这也是头文件的作用,将一批函数的声明放入一个头文件中,在任何需要这些函数声明的地方引用该头文件,以便于维护。
1.1.1.4. 小结
从理论上讲,声明与定义的区别就是:定义描述了内部内容,而声明不表露内部内容,只说明对外接口。例如,类的定义包含了内部成员的声明,而类的声明不包含任何类的内部细节;函数的定义包含了函数体,而函数声明只包括函数的签名;变量的定义可以包含初始化,而变量的声明不可以包含初始化。
class B { public: A* CreateA( void ) const; }
类的定义只给出了类包含了哪些数据(成员变量)和接口(成员函数),但并没有给出实现,程序的实现应该放在原代码文件中。如例程[2-1]中的Point类定义在Point.hpp头文件中,相应的源代码文件Point.cpp的内容如例程[2-4]所示。
// 例程2-2: 类的声明
class Point;
类的说明与实现都可以放在头文件中,因为上层代码需要使用Point的类必须知道当前工程已经定义了这个类。但应该使用定义还是声明呢?使用声明可以的地方使用定义都是可以的,但是,过多得使用定义会使项目编译时间加长,减慢编译速度,细节可参见(@see effective series,item 34)。
头文件和源文件
int number1 = f1(); int number2 = f2(number1); } /* end of main.cpp */ 这 样,便是一个完整的程序了。需要注意的是,.h 文件不用写在编译器的命令之后,但它必须要在编译器 找得到的地方(比如跟 main.cpp 在一个目录下)。 main.cpp 和 math.cpp 都可以分别通过编译,生成 main.o 和 math.o,然后再把这两个目标文件进行链接,程序就可以运行了。 三、#include #include 是一个来自 C 语言的宏命令,它在编译器进行编译之前,即在预编译的时候就会起作用。 #include 的作用是把它后面所写的那个文件的内容,提的是,它本身是没有其它任何作用与副功能的,它的作用就是把每一个它出现的地方,替换成它后 面所写的那个文件的 内容。简单的文本替换,别无其他。因此,main.cpp 文件中的第一句(#include "math.h"),在编译之前就会被替换成 math.h 文件的内容。即在编译过程将要开始的时候,main.cpp 的 内容已经发生了改变:
所以,应该记住的一点就是,.h 头文件中,只能存在变量或者函数的声明, 而不要放定义。即,只能在头 文件中写形如:extern int a;和 void f();的句子。这些才是声明。如果写上 int a;或者 void f() {}这样 的句子,那么一旦这个头文件被两个或两个以上的.cpp 文件包含的话,编译器会立马报错。(关于 extern, 前面有讨论过,这里不再讨论定义跟 声明的区别了。) 但是,这个规则是有三个例外的。 一,头文件中可以写 const 对象的定义。因为全局的 const 对象默 认是没有 extern 的声明的,所以它只 在当前文件中有效。把这样的对象写进头文件中,即使它被包含到其他多个.cpp 文件中,这个对象也都只 在包含它的 那个文件中有效,对其他文件来说是不可见的,所以便不会导致多重定义。同时,因为这些.cpp 文件中的该对象都是从一个头文件中包含进去的,这样也就保证 了这些.cpp 文件中的这个 const 对象的 值是相同的,可谓一举两得。同理,static 对象的定义也可以放进头文件。 二,头文件中可 以写内联函数(inline)的定义。因为 inline 函数是需要编译器在遇到它的地方根据它的 定义把它内联展开的,而并非是普通函数那样可以先声明再链 接的(内联函数不会链接),所以编译器就需 要在编译时看到内联函数的完整定义才行。如果内联函数像普通函数一样只能定义一次的话,这事儿就难 办了。因为在 一个文件中还好,我可以把内联函数的定义写在最开始,这样可以保证后面使用的时候都可 以见到定义;但是,如果我在其他的文件中还使用到了这个函数那怎么办 呢?这几乎没什么太好的解决办 法,因此 C++规定,内联函数可以在程序中定义多次,只要内联函数在一个.cpp 文件中只出现一次,并且 在所有的.cpp 文 件中,这个内联函数的定义是一样的,就能通过编译。那么显然,把内联函数的定义放进 一个头文件中是非常明智的做法。 三,头文件中可以写类 (class)的定义。因为在程序中创建一个类的对象时,编译器只有在这个类的定义 完全可见的情况下,才能知道这个类的对象应该如何布局,所以,关于类的 定义的要求,跟内联函数是基 本一样的。所以把类的定义放进头文件,在使用到这个类的.cpp 文件中去包含这个头文件,是一个很好的 做法。在这里,值得一提 的是,类的定义中包含着数据成员和函数成员。数据成员是要等到具体的对象被 创建时才会被定义(分配空间),但函数成员却是需要在一开始就被定义的,这也就 是我们通常所说的类的 实现。一般,我们的做法是,把类的定义放在头文件中,而把函数成员的实现代码放在一个.cpp 文件中。 这是可以的,也是很好的办法。 不过,还有另一种办法。那就是直接把函数成员的实现代码也写进类定义 里面。在 C++的类中,如果函数成员在类的定义体中被定义,那么编译器会视这个函数为 内联的。因此, 把函数成员的定义写进类定义体,一起放进头文件中,是合法的。注意一下,如果把函数成员的定义写在类 定义的头文件中,而没有写进类定义中, 这是不合法的,因为这个函数成员此时就不是内联的了。一旦头 文件被两个或两个以上的.cpp 文件包含,这个函数成员就被重定义了。 五、头文件中的保护措施 考 虑一下,如果头文件中只包含声明语句的话,它被同一个.cpp 文件包含再多次都没问题——因为声明语 句的出现是不受限制的。然而,上面讨论到的头文件中的 三个例外也是头文件很常用的一个用处。那么, 一旦一个头文件中出现了上面三个例外中的任何一个,它再被一个.cpp 包含多次的话,问题就大了。因为 这三个 例外中的语法元素虽然“可以定义在多个源文件中”,但是“在一个源文件中只能出现一次”。设想一 下,如果 a.h 中含有类 A 的定义,b.h 中含有类 B 的定 义,由于类 B 的定义依赖了类 A,所以 b.h 中也 #include 了 a.h。现在有一个源文件,它同时用到了类 A 和类 B,于是程序员在这个源文件中既把 a.h 包 含进来了,也把 b.h 包含进来了。这时,问题就来了:类 A 的定义在这个源文件中出现了两次!于是整个 程序就不能通过编译了。你也许会认为这是程序 员的失误——他应该知道 b.h 包含了 a.h——但事实上他 不应该知道。 使用"#define"配合条件编译可以很好地解决这个问题。在一 个头文件中,通过#define 定义一个名字, 并且通过条件编译#ifndef...#endif 使得编译器可以根据这个名字是否被定义,再决定要不要继 续编译该 头文中后续的内容。这个方法虽然简单,但是写头文件时一定记得写进去。
c语言中头文件和源文件解析 编译原理
c语言中头文件和源文件解析编译原理头文件和源文件是C语言中常见的两种文件类型,它们在编译原理中扮演着重要的角色。
本文将对头文件和源文件进行解析,从编译原理的角度探讨它们的作用和使用方法。
一、头文件的概念和作用头文件是一种特殊的文件,它通常以.h作为文件扩展名,用于存放函数声明、宏定义、结构体声明等内容。
头文件的作用主要有以下几个方面:1.1 提供接口声明头文件中包含了函数的声明,通过包含头文件可以让源文件知道这些函数的存在,并且能够正确地调用这些函数。
这种方式可以提高代码的可读性和可维护性,使得不同的源文件可以共享同一个函数的实现。
1.2 定义常量和宏头文件中可以定义常量和宏,这些常量和宏可以被多个源文件引用和使用。
通过在头文件中定义常量和宏,可以提高代码的可重用性和可维护性,避免了在多个源文件中重复定义常量和宏的问题。
1.3 声明结构体和类型头文件中可以声明结构体和类型,这些结构体和类型可以被多个源文件引用和使用。
通过在头文件中声明结构体和类型,可以提高代码的可读性和可维护性,避免了在多个源文件中重复声明结构体和类型的问题。
二、源文件的概念和作用源文件是C语言程序的主要组成部分,它通常以.c作为文件扩展名,包含了具体的函数实现和全局变量定义等内容。
源文件的作用主要有以下几个方面:2.1 实现函数的定义源文件中包含了函数的具体实现,通过编译和链接的过程,可以将函数的定义和函数的调用联系起来。
源文件中的函数实现可以直接访问和修改全局变量,同时也可以调用其他源文件中的函数。
2.2 定义全局变量源文件中可以定义全局变量,这些全局变量可以被多个函数访问和修改。
全局变量在程序的整个执行过程中都是存在的,它们的作用域不限于某个函数,可以在不同的函数之间共享数据。
2.3 包含头文件源文件可以通过包含头文件来使用头文件中定义的函数、常量、宏、结构体和类型等。
通过包含头文件,源文件可以获取到头文件中的声明信息,从而可以正确地使用其中定义的内容。
头文件与源文件在c语言中应用简单示例
一、概述C语言作为一种被广泛使用的程序设计语言,其核心概念之一就是头文件(Header File)和源文件(Source File)。
头文件和源文件在C 语言中的应用非常普遍,它们的合理使用对于提高代码的可读性、可维护性和可重用性,起着非常重要的作用。
本文将从头文件和源文件的概念入手,通过简单的示例帮助读者更加深入的理解并应用头文件与源文件在C语言中的重要性和用法。
二、头文件与源文件概念与作用1. 头文件(Header File)是一种特殊的文本文件,它以“.h”为扩展名,用来包含要被其他文件引用的声明和定义,通常包含函数原型、宏定义、数据结构等内容。
当程序需要使用某些外部的函数或数据结构时,可以通过#include指令引用相应的头文件。
2. 源文件(Source File)是包含C语言源代码的文件,通常以“.c”为扩展名。
源文件包含了程序的实际代码,其中定义了各种函数、变量、数据结构等。
源文件中也可以通过#include指令引用头文件,以便在源文件中使用头文件中声明的函数和数据结构。
三、头文件的编写与应用1. 定义头文件的格式头文件通常包括以下内容:- 头文件保护宏(Header Guard):用来防止头文件被多次引用的问题,通常采用#ifndef、#define和#endif三个宏来实现;- 函数原型:声明函数的名称、返回类型和参数列表,以便在源文件中使用;- 宏定义:定义一些常量和宏,方便程序中的代码使用。
2. 编写简单的头文件示例以一个简单的数学计算为例,定义一个头文件math.h包含两个函数的声明:```c#ifndef MATH_H#define MATH_Hint add(int a, int b);int subtract(int a, int b);#endif```3. 应用头文件在源文件中使用这个头文件:```c#include "math.h"int m本人n() {int result1 = add(10, 5);int result2 = subtract(10, 5);// other code...return 0;}```四、源文件的编写与应用1. 定义源文件的格式源文件中包括了实际的函数定义和全局变量定义,以及程序的入口函数m本人n()定义。
C源文件要包含自己的头文件
引言:我们经常在c工程中发现,源文件中要包含自己的头文件。
一直以来,都不知道为什么这样做。
现在,我知道了。
以前的认知:我认为,.c文件没有必要包含自己的.h文件。
.h文件包含.c文件中定义的函数和全局变量的声明,.h文件就是.c文件提供的对外接口文件。
既然.h文件就是.c文件提供的对外接口文件,那么.c文件就没必要包含自己的.h文件了(.h文件是对外提供用的,对内又何必再包含进来呢)。
鉴于这样的理解,我对于工程中.c源文件包含自己的.h头文件很是不理解,不知道为什么要这样做。
现在对此的理解:但是现在,我知道为什么要源文件包含自己的头文件了。
如下,一段书中的原话:“如果希望让编译器检查声明的一致性, 一定要把全局声明放到头文件中。
特别是, 永远不要把外部函数的原型(也就是函数声明)放到.c 文件中: 通常它与定义的一致性不能得到检查, 而矛盾的原型(也就是函数声明)比不用还糟糕。
”注意:外部函数的原型,就是外部函数的声明。
对这段话的理解:为什么:“永远不要把外部函数的原型放到.c 文件中”这个外部函数A指的是B.c文件之外定义的函数,B.c文件中需要使用外部函数A,就需要先对外部函数A声明(对外部函数的声明就是外部函数原型)。
对这个外部函数A的声明,不能放在B.c文件里面来实现。
以实例说明:①假若工程中有2个源文件a.c和b.c;a.c的头文件为a.h,b.c的头文件为b.h。
②a.c中定义了一个函数sum。
③b.c要引用sum 这个函数。
做法是:在b.c中声明sum这个函数。
然后b.c就可以使用sum函数了。
这样的做法就是把外部函数sum的声明放到了b.c中来。
然而,这样的做法很不妥。
不妥的原因:sum是在a.c中定义的,而声明确是在b.c中,sum函数的定义和声明不是在同一个文件中的。
定义和声明不在同一个文件中,编译的时候,编译器就不能对定义和声明的一致性进行检查。
这样,如果sum的定义和声明不一致,编译器就无法检查出来(定义和声明不在同一个文件中),那么编译的时候不会报错,但是程序运行的时候就可能会出错。
C语言头文件源文件
C语⾔头⽂件源⽂件C语⾔头⽂件源⽂件1、头⽂件与源⽂件头⽂件⽤于声明接⼝函数,格式如下如创建test.h#ifndef _TEST_H_#define _TEST_H_/*接⼝函数的申明*/#endif#ifndef _TEST_H_#define _TEST_Hint sum(int x, int y);void swap(int *x, int *y);int max(int x, int y);#endif源⽂件⽤于接⼝函数的实现,源⽂件中只写接⼝函数的实现不能写main()函数#include <stdio.h>#include "test.h"int sum(int x, int y){return x+y;}void swap(int *x, int *y){int tmp;tmp = *x;*x = *y;*y = tmp;}int max(int x, int y){return (x>y)? x : y;}2、⽤户⽂件头⽂件和源⽂件⼀般是标准库⽂件或者⾃定义的库⽂件,⽤户⽂件则是我们⾃⼰写的⽂件,我们需要在⽤户⽂件中使⽤库⽂件或函数,就要包含所需的头⽂件#include <stdio.h>#include "test.h"int main(){int a = 1, b = 2;swap(&a, &b);printf("sum(%d,%d)=%d\n", a, b, sum(a, b));printf("a=%d, b=%d\n", a, b);printf("max(%d,%d)=%d\n", a, b, max(a, b));return0;}3、多⽂件编译当我们使⽤的时候,如果只编译main.c(gcc main.c)就会报错原因是在test.h中找不到函数的实现,所以在编译时要将源⽂件test.c和main.c⼀起编译(gcc main.c test.c),这样就不会报错4、makefile和shell脚本当我们包含的头⽂件特别多,在编译时就要编译很多源⽂件(gcc main.c test1.c test2.c test3.c test4.c ... testn.c),这样就会⾮常长,所以我们可以将命令⾏写到脚本⾥⾯进⾏批处理(1)shell脚本创建⼀个build.sh的脚本⽂件,然后将需要编译的命令⾏写到脚本⽂件⾥,编译时输⼊命令 sh build.sh就完成了编译(2)makefile(待续。
在源文件(.c)和头文件(.h)中声明和定义的区别——C语言
在源⽂件(.c)和头⽂件(.h)中声明和定义的区别——C语⾔最近在看多⽂件编程的时候遇到的⼀个问题,本来以为理解了声明和定义的区别(然⽽并没有····),也算是重新认识了⼀次声明和定义,下⾯上代码声明和定义:有分配空间的叫定义,没分配空间的叫声明定义:表⽰创建变量或分配存储单元声明:说明变量的性质,但并不分配存储单元情形⼀:在源⽂件(.c)中(函数内)int a;//定义extern int a;//声明第⼀个int a是定义,分配了存储空间(其实包括了声明和定义两个步骤,可以叫"定义性声明"),第⼆个 extern int a是声明(也叫"引⽤性声明"),没有分配存储空间关于extern的使⽤,感兴趣的读者可以看⼀下另⼀⽚随笔:声明可以多次,定义只能有⼀次意思是在源⽂件(.c)中可以多次声明同⼀个变量,例如在编译阶段是不会报错的情形⼆:在源⽂件(.c)中(函数外)编译结果:这⾥的int a;放在函数外,没有出现重定义的错误,并不意味着int a就不是定义了,int a仍然是定义,编译阶段没有报错了原因在于全局变量没有赋初值,没赋初值的全局变量,那么在⽬标⽂件中是不会为这个全局变量分配空间的,它被放在BSS段(如果是赋初值的全局变量,那么放在data段),BSS段在⽬标⽂件中是没有的(这个就是编译阶段不会报错的原因),意味着没赋初值的全局变量不占磁盘空间,当程序加载运⾏时,就会为bss段中的数据分配内存进⾏初始化了(起到了节省磁盘空间的作⽤),上⾯说了BSS段的数据不占磁盘空间,那么它的⼤⼩和符号存在哪呢? bss段占据的⼤⼩存放在ELF⽂件格式中的段表(Section Table)中,段表存放了各个段的各种信息,⽐如段的名字、段的类型、段在elf⽂件中的偏移、段的⼤⼩等信息(感谢园友:指出随笔中的错误)(对内存映像感兴趣的读者可以看下这两篇⽂章:、)情形三:在头⽂件(.h)中在头⽂件中的声明和定义和在源⽂件中函数外的声明和定义相同,下⾯为测试结果修改头⽂件后从上⾯可以看出int a在头⽂件中仍然是定义,只不过和全局变量定义相同,只要没有赋初值,编译阶段就不会报错。
源文件与头文件
14.1源文件和头文件和别的一些语言不同,C,C++的代码文件有“头文件”和“代码文件”之分。
二者合起来我们称为单元(Unit)文件。
扩展名为 .c 或 .cpp 的文件,主要用以实现程序的各种功能,我们称为代码文件。
扩展名为 .h 的文件,称为头文件。
在头文件里主要写一些函数、数据(包括数据类型的定义)、等的声明,这样可以在多个.c或.cpp文件内共享这些函数、数据。
说它可以起到函数“名片夹”的作用。
大都数时候,源文件和头文件是对应出现的,比如有一个A.cpp 的源文件,就会有一个A.h 的头文件。
这种情况在我们写应用程序时,更是常见。
所以C++ Builder对此进行了强化。
比如,它支持在同名源文件和头文件之间通过热键来回切换。
在CB6.0里,编辑器打开对应的源文件和头文件时,将显示为同一页下的两个子页。
找到“代码窗口”。
如果你看到的是一个叫"Form1"的表单,请按F12,“代码窗口”将跑到前面。
它的标题应该是默认的"Unit1.cpp"。
正是当前代码文件的文件名。
如下图:对于CB6,还可以看到在该窗口的底部有这样一个分页:源文件:Unit1.cpp 和头文件:Unit1.h 并列着,我们可以方便地选择。
至于"Diagram",称为“图解”。
这是一个给这个源文件加配套注解,及表单上各控件的依赖关系的地方。
如果是一个开发小组在进行共同开发,严格地要求每个成员为每个单元文件写上“Diagram”,可以更好地实现程序员之间的沟通。
CB5没有这些,不过下面的热键操作两个版本均一样的,要求大家记住。
按Ctrl + F6 可以在源文件和头文件之间来回切换。
请大家试试。
这个简单的操作将在我们今后的编程过程中高频率地使用。
14.2如何创建多个单元文件前面我们在“Wind ows应用程序工程”中看到了头文件与源文件的匹配关系,在“控制台”的工程中,也同样存在。
c语言 头文件和.c文件分离原理
c语言头文件和.c文件分离原理
在C语言中,通常将函数的声明和定义分别放在头文件(.h文件)和源文件(.c 文件)中。
头文件包含了函数的声明,而源文件包含了函数的定义。
分离头文件和源文件的主要目的是为了提高代码的可维护性和可重用性。
通过将函数的声明放在头文件中,可以使其他源文件能够访问和调用这些函数,而无需了解函数的具体实现细节。
这样可以降低代码的耦合度,提高代码的可重用性。
分离头文件和源文件的原理如下:
1. 在头文件中声明函数的原型(即函数的返回类型、函数名和参数列表),并定义相关的宏、结构体、枚举等。
2. 在源文件中定义函数的具体实现,包括函数的逻辑和算法。
3. 在需要使用这些函数的源文件中,通过#include指令将头文件包含进来,以便能够访问和调用这些函数。
4. 在编译时,编译器会将源文件和头文件分别编译成目标文件(.o文件)。
5. 在链接时,链接器将目标文件和库文件(如果有的话)链接在一起,生成最终的可执行文件。
通过分离头文件和源文件,可以实现模块化的编程,提高代码的可维护性和可重用性。
同时,也可以减少编译时间,因为只有在头文件发生改变时,才需要重新编译相关的源文件。
头文件和源文件的用法
头文件和源文件的用法头文件和源文件是C++程序中最基本的组成部分之一。
头文件通常包含函数和变量的声明,而源文件包含函数的实现和变量的定义。
程序员使用头文件和源文件来帮助组织和分离程序代码,以增强代码的可读性和可维护性。
一、头文件的作用头文件是C++程序中的重要组成部分,其作用有以下三个方面:1. 声明函数和变量头文件可以包含函数和变量的声明,这些声明通常是在源文件中使用的函数和变量的可见性,也就是让源文件知道这些内容,以便正确地使用它们。
头文件声明的内容可以在多个源文件中使用,因此它们通常是共享的。
2. 定义常量头文件中定义的常量是程序中不变的数值,例如常量π等,使用者可以在程序中引用这些常量名而无需在源文件中再次定义。
3. 包含其他头文件头文件还可以包含其他头文件,这些头文件包含了其他的函数和变量,它们都可以在程序中使用。
在程序的头文件层次结构中,通常会让一个主要的头文件包含所有的子头文件,以减少重复代码并保证程序的清晰易读性。
二、源文件的用途源文件通常包含函数的实现和变量的定义。
这些文件是程序的核心部分,用于执行代码并计算结果。
源文件的主要用途有以下几个方面:1. 实现函数源文件中的函数实现了程序设计中的算法和逻辑。
这些实现是由程序员编写的,根据参数的值计算结果并返回。
这样源文件中的函数可以在程序中被调用和使用。
2. 定义变量源文件中定义变量是为了处理数据时的存储和管理。
变量是在程序中被赋值和使用的,因此源文件中的变量是由程序员定义的。
3. 包含头文件源文件通常包含头文件,以便知晓头文件中声明的函数和变量,同时避免重复定义。
三、头文件和源文件的区别头文件和源文件有以下几个区别:1. 头文件提供了函数和变量的声明,而源文件提供了函数和变量的实现。
2. 头文件可以被多个源文件包含,以便共享声明,而源文件只能被单独编译为二进制代码。
3. 头文件通常使用.h扩展名,而源文件通常使用.cpp扩展名。
4. 头文件和源文件都是文本文件,但在处理方法上有不同的方式。
头文件如何来关联源文件
头文件如何来关联源文件?这个问题实际上是说,已知头文件“a.h”声明了一系列函数,“b.cpp”中实现了这些函数,那么如果我想在“c.cpp”中使用“a.h”中声明的这些在“b.cpp”中实现的函数,通常都是在“c.cpp”中使用#include “a.h”,那么c.cpp是怎样找到b.cpp中的实现呢?其实.cpp和.h文件名称没有任何直接关系,很多编译器都可以接受其他扩展名。
比如偶现在看到偶们公司的源代码,.cpp文件由.cc 文件替代了。
在Turbo C中,采用命令行方式进行编译,命令行参数为文件的名称,默认的是.cpp和.h,但是也可以自定义为.xxx等等。
谭浩强老师的《C程序设计》一书中提到,编译器预处理时,要对#include命令进行“文件包含处理”:将file2.c的全部内容复制到#include “file2.c”处。
这也正说明了,为什么很多编译器并不care 到底这个文件的后缀名是什么----因为#include预处理就是完成了一个“复制并插入代码”的工作。
编译的时候,并不会去找b.cpp文件中的函数实现,只有在link 的时候才进行这个工作。
我们在b.cpp或c.cpp中用#include “a.h”实际上是引入相关声明,使得编译可以通过,程序并不关心实现是在哪里,是怎么实现的。
源文件编译后成生了目标文件(.o或.obj文件),目标文件中,这些函数和变量就视作一个个符号。
在link的时候,需要在makefile里面说明需要连接哪个.o或.obj文件(在这里是b.cpp生成的.o或.obj文件),此时,连接器会去这个.o或.obj文件中找在b.cpp中实现的函数,再把他们build到makefile 中指定的那个可以执行文件中。
在Unix下,甚至可以不在源文件中包括头文件,只需要在makefile中指名即可(不过这样大大降低了程序可读性,是个不好的习惯哦^_^)。
在VC中,一帮情况下不需要自己写makefile,只需要将需要的文件都包括在project中,VC会自动帮你把makefile写好。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
C++中的头文件和源文件一、C++编译模式通常,在一个C++程序中,只包含两类文件——.cpp文件和.h文件。
其中,.cpp 文件被称作C++源文件,里面放的都是C++的源代码;而.h文件则被称作C++头文件,里面放的也是C++的源代码。
C+ +语言支持“分别编译”(separate compilation)。
也就是说,一个程序所有的内容,可以分成不同的部分分别放在不同的.cpp文件里。
.cpp文件里的东西都是相对独立的,在编译(compile)时不需要与其他文件互通,只需要在编译成目标文件后再与其他的目标文件做一次链接(link)就行了。
比如,在文件a.cpp 中定义了一个全局函数“void a() {}”,而在文件b.cpp中需要调用这个函数。
即使这样,文件a.cpp和文件b.cpp并不需要相互知道对方的存在,而是可以分别地对它们进行编译,编译成目标文件之后再链接,整个程序就可以运行了。
这是怎么实现的呢?从写程序的角度来讲,很简单。
在文件 b.cpp中,在调用“void a()”函数之前,先声明一下这个函数“void a();”,就可以了。
这是因为编译器在编译b.cpp的时候会生成一个符号表(symbol table),像“void a()”这样的看不到定义的符号,就会被存放在这个表中。
再进行链接的时候,编译器就会在别的目标文件中去寻找这个符号的定义。
一旦找到了,程序也就可以顺利地生成了。
注意这里提到了两个概念,一个是“定义”,一个是“声明”。
简单地说,“定义”就是把一个符号完完整整地描述出来:它是变量还是函数,返回什么类型,需要什么参数等等。
而“声明”则只是声明这个符号的存在,即告诉编译器,这个符号是在其他文件中定义的,我这里先用着,你链接的时候再到别的地方去找找看它到底是什么吧。
定义的时候要按C++语法完整地定义一个符号(变量或者函数),而声明的时候就只需要写出这个符号的原型了。
需要注意的是,一个符号,在整个程序中可以被声明多次,但却要且仅要被定义一次。
试想,如果一个符号出现了两种不同的定义,编译器该听谁的?这种机制给C++程序员们带来了很多好处,同时也引出了一种编写程序的方法。
考虑一下,如果有一个很常用的函数“void f() {}”,在整个程序中的许多.cpp 文件中都会被调用,那么,我们就只需要在一个文件中定义这个函数,而在其他的文件中声明这个函数就可以了。
一个函数还好对付,声明起来也就一句话。
但是,如果函数多了,比如是一大堆的数学函数,有好几百个,那怎么办?能保证每个程序员都可以完完全全地把所有函数的形式都准确地记下来并写出来吗?二、什么是头文件很显然,答案是不可能。
但是有一个很简单地办法,可以帮助程序员们省去记住那么多函数原型的麻烦:我们可以把那几百个函数的声明语句全都先写好,放在一个文件里,等到程序员需要它们的时候,就把这些东西全部copy进他的源代码中。
这个方法固然可行,但还是太麻烦,而且还显得很笨拙。
于是,头文件便可以发挥它的作用了。
所谓的头文件,其实它的内容跟.cpp文件中的内容是一样的,都是C++的源代码。
但头文件不用被编译。
我们把所有的函数声明全部放进一个头文件中,当某一个.cpp源文件需要它们时,它们就可以通过一个宏命令“#include”包含进这个.cpp文件中,从而把它们的内容合并到.cpp文件中去。
当.cpp文件被编译时,这些被包含进去的.h文件的作用便发挥了。
举一个例子吧,假设所有的数学函数只有两个:f1和f2,那么我们把它们的定义放在math.cpp里:/* math.cpp */double f1(){//do something here....return;}double f2(double a){//do something here...return a * a;}/* end of math.cpp */并把“这些”函数的声明放在一个头文件math.h中:/* math.h */double f1();double f2(double);/* end of math.h */在另一个文件main.cpp中,我要调用这两个函数,那么就只需要把头文件包含进来:/* main.cpp */#include "math.h"main(){int number1 = f1();int number2 = f2(number1);}/* end of main.cpp */这样,便是一个完整的程序了。
需要注意的是,.h文件不用写在编译器的命令之后,但它必须要在编译器找得到的地方(比如跟main.cpp在一个目录下)。
main.cpp和math.cpp都可以分别通过编译,生成main.o和math.o,然后再把这两个目标文件进行链接,程序就可以运行了。
三、#include#include 是一个来自C语言的宏命令,它在编译器进行编译之前,即在预编译的时候就会起作用。
#include的作用是把它后面所写的那个文件的内容,完完整整地、一字不改地包含到当前的文件中来。
值得一提的是,它本身是没有其它任何作用与副功能的,它的作用就是把每一个它出现的地方,替换成它后面所写的那个文件的内容。
简单的文本替换,别无其他。
因此,main.cpp文件中的第一句(#include "math.h"),在编译之前就会被替换成math.h文件的内容。
即在编译过程将要开始的时候,main.cpp的内容已经发生了改变:/* ~main.cpp */double f1();double f2(double);main(){int number1 = f1();int number2 = f2(number1);}/* end of ~main.cpp */不多不少,刚刚好。
同理可知,如果我们除了main.cpp以外,还有其他的很多.cpp 文件也用到了f1和f2函数的话,那么它们也通通只需要在使用这两个函数前写上一句#include "math.h"就行了。
四、头文件中应该写什么通过上面的讨论,我们可以了解到,头文件的作用就是被其他的.cpp包含进去的。
它们本身并不参与编译,但实际上,它们的内容却在多个.cpp文件中得到了编译。
通过“定义只能有一次”的规则,我们很容易可以得出,头文件中应该只放变量和函数的声明,而不能放它们的定义。
因为一个头文件的内容实际上是会被引入到多个不同的.cpp文件中的,并且它们都会被编译。
放声明当然没事,如果放了定义,那么也就相当于在多个文件中出现了对于一个符号(变量或函数)的定义,纵然这些定义都是相同的,但对于编译器来说,这样做不合法。
所以,应该记住的一点就是,.h头文件中,只能存在变量或者函数的声明,而不要放定义。
即,只能在头文件中写形如:extern int a;和void f();的句子。
这些才是声明。
如果写上int a;或者void f() {}这样的句子,那么一旦这个头文件被两个或两个以上的.cpp文件包含的话,编译器会立马报错。
(关于extern,前面有讨论过,这里不再讨论定义跟声明的区别了。
)但是,这个规则是有三个例外的。
一,头文件中可以写const对象的定义。
因为全局的const对象默认是没有extern 的声明的,所以它只在当前文件中有效。
把这样的对象写进头文件中,即使它被包含到其他多个.cpp文件中,这个对象也都只在包含它的那个文件中有效,对其他文件来说是不可见的,所以便不会导致多重定义。
同时,因为这些.cpp 文件中的该对象都是从一个头文件中包含进去的,这样也就保证了这些.cpp文件中的这个const对象的值是相同的,可谓一举两得。
同理,static对象的定义也可以放进头文件。
二,头文件中可以写内联函数(inline)的定义。
因为inline函数是需要编译器在遇到它的地方根据它的定义把它内联展开的,而并非是普通函数那样可以先声明再链接的(内联函数不会链接),所以编译器就需要在编译时看到内联函数的完整定义才行。
如果内联函数像普通函数一样只能定义一次的话,这事儿就难办了。
因为在一个文件中还好,我可以把内联函数的定义写在最开始,这样可以保证后面使用的时候都可以见到定义;但是,如果我在其他的文件中还使用到了这个函数那怎么办呢?这几乎没什么太好的解决办法,因此C++规定,内联函数可以在程序中定义多次,只要内联函数在一个.cpp文件中只出现一次,并且在所有的.cpp文件中,这个内联函数的定义是一样的,就能通过编译。
那么显然,把内联函数的定义放进一个头文件中是非常明智的做法。
三,头文件中可以写类(class)的定义。
因为在程序中创建一个类的对象时,编译器只有在这个类的定义完全可见的情况下,才能知道这个类的对象应该如何布局,所以,关于类的定义的要求,跟内联函数是基本一样的。
所以把类的定义放进头文件,在使用到这个类的.cpp文件中去包含这个头文件,是一个很好的做法。
在这里,值得一提的是,类的定义中包含着数据成员和函数成员。
数据成员是要等到具体的对象被创建时才会被定义(分配空间),但函数成员却是需要在一开始就被定义的,这也就是我们通常所说的类的实现。
一般,我们的做法是,把类的定义放在头文件中,而把函数成员的实现代码放在一个.cpp 文件中。
这是可以的,也是很好的办法。
不过,还有另一种办法。
那就是直接把函数成员的实现代码也写进类定义里面。
在C++的类中,如果函数成员在类的定义体中被定义,那么编译器会视这个函数为内联的。
因此,把函数成员的定义写进类定义体,一起放进头文件中,是合法的。
注意一下,如果把函数成员的定义写在类定义的头文件中,而没有写进类定义中,这是不合法的,因为这个函数成员此时就不是内联的了。
一旦头文件被两个或两个以上的.cpp文件包含,这个函数成员就被重定义了。
五、头文件中的保护措施考虑一下,如果头文件中只包含声明语句的话,它被同一个.cpp文件包含再多次都没问题——因为声明语句的出现是不受限制的。
然而,上面讨论到的头文件中的三个例外也是头文件很常用的一个用处。
那么,一旦一个头文件中出现了上面三个例外中的任何一个,它再被一个.cpp包含多次的话,问题就大了。
因为这三个例外中的语法元素虽然“可以定义在多个源文件中”,但是“在一个源文件中只能出现一次”。
设想一下,如果a.h中含有类A的定义,b.h中含有类B的定义,由于类B的定义依赖了类A,所以b.h中也#include了a.h。
现在有一个源文件,它同时用到了类A和类B,于是程序员在这个源文件中既把a.h包含进来了,也把b.h包含进来了。
这时,问题就来了:类A的定义在这个源文件中出现了两次!于是整个程序就不能通过编译了。