`

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, http://www.cse.wustl.edu/~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选项。

而实际上,头文件以什么为扩展名并没有什么影响,因为没有人会直接编译头文件,因为头文件里只有声明而没有定义,而在实际的编译过程中,#include预编译指令用到的头文件是被直接插入到源代码文件中再进行编译的,这与直接将头文件的内容复制到#include行所在的位置是没有区别的,这样就很容易理解#include可以出现在文件的什么位置,显然放到一个函数体或类的定义里是不合适的。

1.1.1. 定义与声明有什么不同
一般来讲定义要放在源代码文件中,而声明要放在头文件中。具体哪些内容应该放在源代码文件中,哪些内容应该放在头文件中,需要清楚地理解,哪些是定义,哪些是声明。

1.1.1.1. 类的定义与声明
类的定义是定义了类的完整结构,包括成员函数与成员变量,如例程[2-1]。

// 例程2-1: 类的定义

class Point

{

private:

int x_;

int y_;

public:

Point( int x, int y);

int X( void ) const;

int Y( void ) const;

};

而类的声明,只说明存在这一种类型,但并不定义它是什么样的类型,如例程[2-2]。

// 例程2-2: 类的声明

class Point;

类的说明与实现都可以放在头文件中,因为上层代码需要使用Point的类必须知道当前工程已经定义了这个类。但应该使用定义还是声明呢?使用声明可以的地方使用定义都是可以的,但是,过多得使用定义会使项目编译时间加长,减慢编译速度,细节可参见(@see effective series,item 34)。

还有一种情况是必须使用声明的,就是当两个类在定义中出现互相引用的情况时,如例程[2-3]。当然,这种情况出现的情况比较少,多数情况下也可以通过修改设计尽量避免,在不可避免的情况下只能使用这种方式。

// 例程2-3: 类定义的交叉引用

class B;

class A { public : B& GetB( void ) const; }

class B { public: A* CreateA( void ) const; }

类的定义只给出了类包含了哪些数据(成员变量)和接口(成员函数),但并没有给出实现,程序的实现应该放在原代码文件中。如例程[2-1]中的Point类定义在Point.hpp头文件中,相应的源代码文件Point.cpp的内容如例程[2-4]所示。

// 例程2-4: 成员函数的实现

Point::Point(int x, inty)

:x_(x), y_(y)

{

}

int Point::X( void ) const

{

return x_;

}

int Point::Y( void ) const

{

return y_;

}

当然,类的成员函数的实现也可以放到头文件中,但编译时默认会为这些函数加上inline修饰符,当成内联函数处理。像Point::X和PointY这样的简单的读值函数,比较适合放到头文件中作为内联函数,详见[??inline]一节。

1.1.1.2. 函数的定义与声明
函数的声明只说明函数的外部接口,而不包含函数的实现函数体,如例程[2-5]所示。

// 例程2-5: 函数的声明

int SplitString(vector& fields

, const string& str

, const string& delimiter);

而函数定义则是包含函数声明和函数体在内的所有部分,如例程[2-6]所示,给出了一个拆分字符串的函数,虽然效率不高,但它的确是一个能工作的函数。

// 例程2-6: 函数的定义

int SplitString(vector& fields

, const string& str

, const string& delimiters)

{

string tmpstr = str;

fields.clear();

string::size_type pos1, pos2;

for(;;) {

pos1 = pos2 = 0;

if((pos1 = tmpstr.find_first_not_of(delimiters, pos2))

== string::npos)

break;

if((pos2 = tmpstr.find_first_of(delimiters, pos1))

!= string::npos){

fields.push_back(tmpstr.substr(pos1, pos2 - pos1));

}else {

fields.push_back(tmpstr.substr(pos1));

break;

}

tmpstr.erase(0, pos2);

}

return fields.size();

}

函数声明可以放在任何一个调用它的函数之前,而且在调用一个函数之前必须在调用者函数之前定义或声明被调函数。函数的定义只能有一次,如果调用者与被调用者不在同一编译单元,只能在调用者之前添加函数的声明。函数定义只能有一次,函数声明可以有无限次(理论上),这也是头文件的作用,将一批函数的声明放入一个头文件中,在任何需要这些函数声明的地方引用该头文件,以便于维护。

函数声明之前有一个可选的extern修饰符,表示该函数是在其它编译单元内定义的,或者在函数库里。虽然它对于函数的声明来讲不是必须的,但可以在一个源文件中直接声明其它编译单元内实现的函数时使用该关键词,从而提高可读性。假如例程[2-6]中的函数SplitString定义在strutil.cpp文件中定义,而且在strutil.cpp还定义了很多字符串相关的函数,other.cpp只用到了strutil.cpp中SplitString这一个函数。而您为了提高编译速度, 可以直接在other.cpp中声明该函数,而不是直接引用头文件,此时最好使用extern标识,使程序的可读性更好。

1.1.1.3. 变量的定义与声明
变量的声明是带有extern标识,而且不能初始化;而变量的定义没有extern标识,可以在定义时初始化,如例程[2-7]所示。

// 例程2-7:变量的定义与声明

// 声明

extern int global_int;

extern std::string global_string ;

// 定义

int global_int = 128;

std::string global_string = “global string”;

在形式上,与函数的声明不同的是,变量的声明中的extern是必须的,如果没有extern修饰,编译器将当作定义。之所以要区分声明与变量,是在为对于变量定义编译器需要分配内存空间,而对于变量声明则不需要分配内存空间。

1.1.1.4. 小结
从理论上讲,声明与定义的区别就是:定义描述了内部内容,而声明不表露内部内容,只说明对外接口。例如,类的定义包含了内部成员的声明,而类的声明不包含任何类的内部细节;函数的定义包含了函数体,而函数声明只包括函数的签名;变量的定义可以包含初始化,而变量的声明不可以包含初始化。

从语法表现上的共同点,声明可以重复,而定义不可以重复。

声明与定义的分离看似有些不方便,但是它可以使实现与接口分离,而且头文件本身就是很好的接口说明文档,具有较好的自描述性,加上现在较智能的集成开发环境(IDE),比起阅读其它类型的文档更方便。C#在3.0中也加入了“部分方法(Partial method)”的概念,其作用与头文件基本相似,这也说明了头文件的优点。

从工程上讲,头文件的文件名应该与对应的源文件名相同便于维护,如果头文件中包含了多个源文件中的定义或声明,则应该按源文件分组布局头文件中的代码,并且通过注释注明每组所在的源文件。当一个工程的文件较多时应该将源文件与头文件分开目录存放,一般头文件存放在include或inc目录下,而源文件存放在source或src目录下,根据经验,一个工程的文件数超过30个时应该将源文件与头文件分开存放,当文件较少时直接放到同一目录即可。

1.1.2. 头文件中为什么有#ifndef/#define/#endif预编译指令
虽然函数、变量的声明都可以重复,所以同一个声明出现多次也不会影响程序的运行,但它会增加编译时间,所以重复引用头文件会使浪费编译时间;而且,当头文件中包含类的定义、模板定义、枚举定义等一些定义时,这些定义是不可以重复的,必须通过一定措施防止重复引用,这就是经常在头文件中看到的#ifndef/#define/#endif的原因,一般形式如例程[2-8] 所示。

// 例程[2-8]

#ifndef HEADERFILE_H

#define HEADERFILE_H

// place defines and declarations here

#endif

一些编译器还支持一些编译器指令防止重复引用,例如Visual C++支持

#pragma once

指令,而且可以避免读磁盘文件,比#ifndef/endif效率更高。

1.1.3. #include与#include”filepath”有什么区别
在C++中有两种引用头文件的形式:

// 形式1

#include

// 形式2

#include “filename”

其实,C++标准中也没有确定这两种方式搜索文件filepath的顺序,而是由编译器的实现确定,其区别就是如果编译器按照第二种形式定义的顺序搜索文件filepath失败或者不支持这种方式时,将其替换为第一种顺序再进行搜索。

而实际上,一般来讲第一种方式都是先搜索编译器的系统目录,而第二种方式则是以被编译的头文件所在目录为当前目录进行搜索,如果搜索失败再在系统头文件里搜索。这两种方式从本质上讲没有什么区别,但当我们自己的程序文件与系统头文件重名时,用后者就会先搜到我们的头文件而不是系统的。但无论如何,与系统头文件重名都不是一个好习惯,一不小心就可能带来不必要的麻烦,当我们自己编写程序库时,最好把它放入一个目录里,不把这个目录直接添加到编译器的头文件搜索路径中(如gcc的-I, visual c++的/I选项等,其实在UNIX/Linux平台的编译器一般都是-I选项),而是添加到上一级目录,而在我们的源文件中引用该头文件时就包含该目录名,这样不容易造成冲突。

例如,我们创建了一个程序库叫mylib,其中一个头文件是strutil.hpp,我们可以创建一个/home/user/project/src/mylib目录,然后把strutil.hpp放进去,然后把 /home/user/project/src添加到编译选项里:

gcc -I/home/user/project/src

这样,在我们的源程序中可以这样引用strutil.hpp文件:

#include “mylib/strutil.hpp”

通过显示的目录名引用头文件就不容易产生冲突,不容易使我们自己的头文件与系统头文件产生混淆。

当然,从代码逻辑上我们还有另外一种解决冲突的方案,那就是命名空间,详见第[?]节。

1.1.4. #include 与#include有什么区别
这两个的区别是比较明显的,因为它们引用的不是同一个头文件,但其作用是不明显的,在功能上并没有任何区别。不带扩展名,以字母c为前缀的一系列头文件只是C++将对应的C语言标准头文件引入到了std命名空间中,将标准库统一置入std命名空间中,另外如cstdlib、cmath等。

如果引用了后者,则需要在使用标准函数库时使用

using namespace std;

以引入std命名空间,或显示通过域作用符调用标准库函数,如

std::printf(“hello from noock”);

建议在C++项目中,特别是大中型项目中使用后者,尽可能避免标识符的冲突。

trackback:http://edu.codepub.com/2010/1206/27753.php

 

分享到:
评论

相关推荐

    源文件与头文件的区别

    源文件与头文件的区别 在编程中,源文件和头文件是两个不同的概念,它们在编译和链接过程中扮演着不同的角色。源文件是指包含程序实现代码的文件,而头文件是指包含函数声明和数据接口的文件,本文将详细介绍源文件...

    源文件与头文件的关系

    源文件与头文件的关系 源文件和头文件是C++编程语言中的两个基本组成部分,它们之间存在着紧密的关系。本文将详细介绍源文件如何根据#include来关联头文件,头文件如何来关联源文件,以及它们之间的关系。 一、...

    C++头文件转源文件工具

    《C++头文件转源文件工具详解》 在C++编程中,头文件(.h)和源文件(.cpp)是程序结构的基础元素。头文件通常包含类定义、函数声明和其他接口信息,而源文件则包含函数实现和类成员的具体代码。然而,在某些情况下...

    一个完整的.proto转为c++类的源文件和头文件的测试案例,IDE使用clion

    要将`.proto`文件转换为C++源文件和头文件,我们需要使用protobuf编译器`protoc`。安装protobuf库后,可以运行以下命令: ```bash protoc --cpp_out=. your_file.proto ``` 这将在当前目录下生成`your_file.pb.h`...

    c++头文件(传统头文件标准头文件)

    - `cctype`, `cerrno`, `clocale`, `cmath`等:与C头文件对应的C++版本,提供C++风格的接口。 - `complex`: 定义了复数类。 - `deque`, `exception`, `fstream`, `functional`, `limits`, `list`, `map`, `...

    C++源文件和头文件关于求解直线方程和法线方程

    在给定的“C++源文件和头文件关于求解直线方程和法线方程”中,我们可以深入理解如何在C++中处理几何计算,特别是直线和法线的数学概念。 首先,直线方程在二维空间中通常表示为点斜式或一般式。点斜式为y - y1 = m...

    软件开发源文件和头文件模板

    - 使用:在C++中,通过`#include`指令将头文件引入到源文件中,以便访问头文件中声明的元素。例如,`HeaderFile_Template.h`可能包含了函数原型和类的声明。 3. **模板(Template)** - 模板在C++中是一种泛型...

    c++全部的头文件大集合

    在C++中,头文件是包含函数声明、类定义和其他编程元素的关键部分,它们允许程序员在不同的源文件之间共享代码。"C++全部的头文件大集合"这个资源提供了一个详尽的指南,涵盖了C++的所有标准库头文件,这对于学习和...

    c++头文件及相关解释

    C++头文件及相关解释 C++头文件是C++语言的基础组件之一,它们提供了各种功能函数、类和模板,以帮助开发者快速构建应用程序。头文件是编译器在编译源代码时需要的依赖文件,它们包含了函数和变量的声明,以便在源...

    sha256_c++sha256头文件_SHA256_

    在C++项目中,通常会有对应的头文件(如`sha256.h`),它会声明这些函数以便在其他源文件中使用。如果没有这个头文件,编译器将不知道如何使用`sha256.c`中定义的函数,导致链接错误。 为了使用这个`sha256.c`文件...

    C语言基础-源文件与头文件详解

    什么是头文件?使用#include引入的文件,以.h为后缀名的文件。一般地,在C语言或C++中,会把用来#include的文件的扩展名叫.h,称其为头文件。#include文件的目的就是把多个编译单元(也就是c或者cpp文件)公用的内容...

    visual studio 中如何设置头文件与源文件切换

    在Visual Studio中,开发C++项目时经常需要在头文件(.h)和源文件(.cpp)之间频繁切换,这对于提高开发效率至关重要。然而,Visual Studio默认并未提供快捷键来实现这一功能,这给程序员带来了不便。为了解决这个...

    c++自制game.h头文件

    在C++编程中,自定义头文件是一种常见做法,它允许程序员封装常用的功能或定义,以便在多个源文件之间共享。"game.h"这个头文件就是这样的一个自定义头文件,它似乎集成了多种功能,方便游戏开发或者涉及到游戏逻辑...

    C++半精度函数头文件

    头文件是C++中的关键组成部分,它包含了函数声明、类定义、常量定义等信息,允许我们在不同的源文件之间共享代码。 在描述中提到的“函数头文件”,通常指的是如`half.h`这样的文件,它可能包含了一系列处理半精度...

    c++ 头文件详解

    5. **头文件组织**:大型项目通常会有一套完整的头文件组织结构,如将类的头文件放在`include/`目录,源文件放在`src/`目录,便于管理。 总的来说,理解和熟练使用C++头文件是编写高效、可维护代码的关键。正确地...

    C++头文件手册

    3. **预处理器指令#include**:`#include`是C++中引入头文件的关键字,它告诉编译器将指定的头文件内容插入到当前源文件中。有两种形式:`#include <file>`用于引入标准库头文件,`#include "file"`则用于引入用户...

    QT使用pen描点画图,源文件与头文件

    在"QT使用pen描点画图,源文件与头文件"这个主题中,我们将会探讨如何在QT环境中利用QPainter类和Pen对象来实现基本的图形绘制,包括描点和画线。 首先,`hpdtcs.cpp`和`main.cpp`是C++源代码文件,它们包含了程序...

    C++ PRIMER 部分头文件及资源

    3. **预处理器指令#include**:在C++程序中,`#include`指令用于将头文件的内容插入到源文件中。例如,`#include <iostream>`会将iostream头文件的内容引入,使得我们可以使用`std::cout`和`std::cin`进行I/O操作。 ...

    C++中的万能头文件.md

    下面将详细介绍C++中几个重要的标准头文件以及与预编译头文件相关的知识点。 首先,让我们明确一个概念:C++中不存在所谓的“万能库头文件”。每个库都有其专用的头文件,用户需要根据实际的编程需求选择合适的...

    C++头文件与源文件[参考].pdf

    C++头文件与源文件 C++语言中,头文件和源文件是两个非常重要的概念。头文件通常以`.h`结尾,用于存放函数和变量的声明;源文件通常以`.cpp`结尾,用于存放函数和变量的定义。 在C++中,编译模式通常是“分离编译...

Global site tag (gtag.js) - Google Analytics