C++中的头文件(.h)
1.定义
头文件是扩展名为 .h 的文件,头文件也是C++的源代码,头文件中包含了 C++中函数、类、对象等的声明和宏定义,它可以被多个源文件通过#include引用共享。
2.使用头文件原因
C++中有“单一定义”规则,即一个对象只能被定义一次,如果在一个源文件中定义了一个函数,其他的源文件想要使用这个函数就需要在使用前声明一下这个函数,在编译结束之后,编译器链接的时候再去查找这些函数的定义。
故要使用其他文件中定义的函数、类、对象(变量)时,需要对函数、类、对象进行声明。这些声明文件一般放在一个头文件中,这样只要通过#include就可以一下引入所有的声明。当然也可以在头文件中定义宏。
自定义的头文件,使用#include"头文件.h"。对于标准库头文件的包含使用#include<头文件.h>
3.编译过程中的头文件
C++代码的编译主要通过以下几个过程:预编译->编译->汇编->链接【可参考:链接(建议先看此链接,再看下面内容)】,最后生成可执行文件。
在预编译阶段,编译器将#include"头文件.h"替换成“头文件.h”中具体的声明内容。
在链接阶段,编译器查找声明对象的定义。
3.1.预编译阶段,头文件被替换
我们看一个简单的例子,下面是一个头文件CA.h
#ifndef CA_H
#define CA_H
int Fun();
#endif
头文件中的函数、类、对象(变量)必须在一个源文件有进行定义,这里在A.cpp中进行定义。
A.cpp:
#include "CA.h"
int Fun()
{
return 1;
}
B.cpp中引用头文件:
#include "CA.h"
int Fun1()
{
return Fun() + 1;
}
int main(){
return Fun1();
}
预编译命令:
g++ -E B.cpp -o B.i
经过预处理后, B.i:
# 1 "B.cpp"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "B.cpp"
# 1 "CA.h" 1
int Fun();
# 2 "B.cpp" 2
int Fun1()
{
return Fun() + 1;
}
int main(){
return Fun1();
}
可以看到,B.cpp中的头文件#include "CA.h"都被CA.h中的具体内容所代替。
【注】“#”是注释
3.2.使用头文件
g++ A.cpp B.cpp -o main
B.cpp中通过头文件引入的东西,会在A.cpp中自动找到。
4.如何写头文件
参考:链接
在写头文件时需要注意,在开头和结尾处必须按照如下样式加上预编译语句(如下)
Circle.h:
#ifndef CIRCLE_H
#define CIRCLE_H
// 你的代码写在这里
#endif
#ifndef代表没有定义CIRCLE_H时,才能进入if。进入if之后,第一步就是执行#define CIRCLE_H来定义CIRCLE_H。这样做以后,即使重复引入头文件,也不会重复执行if中的东西。
至于CIRCLE_H这个名字实际上是无所谓的,你叫什么都行,只要符合规范都行。原则上来说,非常建议把它写成这种形式,因为比较容易和头文件的名字对应。
下面举个最简单的例子来描述一下,咱就求个圆面积。
第1步,建立一个空工程(以在VS2003环境下为例)。
第2步,在头文件的文件夹里新建一个名为Circle.h的头文件,它的内容如下:
#ifndef CIRCLE_H
#define CIRCLE_H
class 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
最后,我们建一个main.cpp来测试我们写的Circle类,它的内容如下:
#include < iostream >
#include " Circle.h "
using namespace std;
int main()
{
Circle c(3);
cout<<"Area="<<c.Area()<<endl;
return 1;
}
运行命令:
g++ Circle.cpp main.cpp -o main
./main
5.C++头文件有.h和没有.h
iostream.h是非标准头文件,iostream是标准头文件形式。iostream.h时代没有名词空间,即所有库函数包括头文件iostream.h都声明在全局域。为了体现结构层次,c++标准委员会引入了名词空间这一概念,并把所有库函数声明由全局域改到了名词空间std。iostream.h里面定义的所有类以及对象都是在全局空间里,所以可以直接使用cout,但如果你用iostream,就不能直接使用cout了,iostream里面所定义的东西都在标准命名空间std里面,所以你必须加上 using namespace std才能使用cout。
故而,
在早些时候,这两种头文件是等价:
#include<iostream.h> // 这个现在已经不支持了
和
#include
using namespace std;
现在标准的C++头文件没有.h扩展名,将以前的C的头文件转化为C++的头文件后,可以加上c的前缀表示来自于c,例如cmath就是由math,h变来。
注意:c语言的string.h变为cstring,和c++的string是两个完全不同的东西。
6.后缀为.hpp的文件
后缀为.hpp的文件一般提供程序使用的接口。
我们公司和另一家软件公司合作,这样就必然要互相提供一些软件的信息(比如一些类,它到底是要做什么的),可是在提供这些信息的同时我们又不像让对方知道我们这些类的具体实现,毕竟这些是我们公司的算法核心和心血啊。所以这个时候就可以把类的接口(这个类是要做什么的)放在*.hpp文件中,而具体类的实现放在 .cpp文件。这时候我们只要给对方公司.hpp文件就行了。这样既提供了必要的信息,又保护了我们的核心代码。