C/C++头文件的作用和用法

示例代码编译运行环境:Windows 64bits+VS2017+Debug+Win32。


头文件是C/C++程序不可缺少的组成部分,使用时,应该了解头文件的作用和相关规范。

1.头文件的作用

C/C++编译采用的是分离编译模式。在一个项目中,有多个源文件存在,但是它们总会有一些相同的内容,比如用户自定义类型、全局变量、全局函数的声明等。将这些内容抽取出来放到头文件中,提供给各个源文件包含,就可以避免想相同内容的重复书写,提高编程效率和代码安全性。所以,设立头文件的目的主要是:提供全局变量、全局函数的声明或公用数据类型的定义,从而实现分离编译和代码复用。

概括的说,头文件有如下三个作用。 (1)加强类型检查,提高类型安全性。 使用头文件,可有效地保证自定义类型的一致性。虽然,在语法上,同一个数据类型(如一个class)在不同的源文件中书写多次是允许的,程序员认为他们是同一个自定义类型,但是,由于数据类型不具有外部连接特性,编译器并不关心该类型的多个版本之间是否一致,这样有可能会导致逻辑错误的发生。考察如下程序。

//source1.cpp
#include <iostream>

class A
{
private:
    char num;
public:
    A();
    void show();
};

void A::show(){std::cout<<num<<std::endl;}

void see(A& a){a.show();}
//end source1.cpp

//source2.cpp
#include <iostream>

class A
{
private:
    int num;
public:
    A(){num=5;};
    void show();
};

void see(A& a);

int main()
{
    A a;
    see(a);
}
//end source2.cpp

这个程序能够顺利通过编译并正确的运行,在构成项目的两个源文件中,对class A的定义出现了一点小小的不一致。两个源文件中,成员变量num一个是char类型,一个是int类型,这就导致输出了一个特殊的字符。

如果将class A的定义放到一个头文件中,用到class A的源文件都包含这个头文件,可以绝对保证数据类型的一致性和安全性。

(2)减少公用代码的重复书写,提高编程效率。 程序开发过程中,对某些数据类型或者接口进行修改是在所难免的,使用头文件,只需要修改头文件中的内容,就可以保证修改在所有源文件中生效,从而避免了繁琐易错的重复修改。

(3)提供保密和代码重用的手段。 头文件也是C++代码重用机制中不可缺少的一种手段,在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制库即可。用户只需要按照头文件的接口声明来调用库函数,而不必关心接口的具体实现,编译器会从库中连接相应的实现代码。

2.头文件的用法

2.1头文件的内容

头文件包含的是多个源文件的公用内容,因此,全局函数原型声明、全局变量声明、自定义宏和类型等应该放在头文件中。规范的头文件允许被多个源文件包含而不会引发编译错误,所以全局变量的定义、外部变量的定义、全局函数的定义、在类体之外的类成员函数的定义等只能出现一次的内容不应该放在头文件中。

2.2使用系统提供的头文件

C语言提供的头文件都是以.h结尾的,如stdio.h等。C++语言最初的目的是成为一个“更好的C”,所以C++语言沿用了C语言头文件的命名习惯,将头文件后面加上.h标志。随着C++语言的发展,C++加入了全新的标准库,为了避免与C发生冲突,C++引入了命名空间来避免名称冲突,也去掉了头文件的.h后缀。于是,在一段时间里,很多头文件有两个版本,一个以.h结尾,而另一则不是,如iostream.h(位于全局名字空间)和iostream(位于名字空间std)。程序员编写程序也有不同的选择,很多C++源程序以这样的语句开始:

#include <iostream.h>

而另一些,则以这样的两条语句开始:

#include <iostream>
using namespace std;

这种现象有些混乱,于是C++标准委员会规定,旧C头文件(如stdio.h)和C++中新的C头文件(如cstdio)继续使用,但是旧的C++头文件(如iostream.h)已被废弃,一律采用C++新标准规定的头文件(如iostream)。另外,在包含系统头文件的时候,应该使用<>(尖括号)而不是””(双引号)。例如应该这样包含头文件iostream:

#include <iostream>

而不是这样:

#include “iostream”

双引号””用来包含自定义的头文件,用它来包含系统头文件是一种不良的编程习惯。原因是编译器遇到双引号包裹的头文件默认为用户自定义头文件,从项目目录下查找,查找不到才会到系统目录中查找,如果存在与系统头文件同名的用户自定义头文件,则会出现不符合预期的错误。

2.3避免头文件被重复包含

C/C++中,如全局变量的定义、全局函数的定义等在项目中只能出现一次。有的可以出现多次,但在一个源文件中只能出现一次,如class的定义等,还有的在一个源文件中可以出现多次,如函数声明等。由于事先无法无法确定头文件的内容,应该避免在一个源文件中对同一头文件包含多次,以免引起重定义错误。考察如下程序。

//header1.h
class A
{
    int num;
public:
    A();
    void show();
};
//end header1.h

//header2.h
#include “header1.h”
class B
{
    A a;
public:
    void disp();
};
//end header2.h

//main.cpp
#include <iostream>
#include “header1.h”
#include “header2.h”
A::A()
{
    num=5;
}
void A::show(){std::cout<<num<<std::endl;}
int main()
{
    A a;
    a.show();
}
//end main.cpp

这个程序无法通过编译,原因是class A在源文件main.cpp中被定义了两次,这是由于头文件header2.h包含了header.1,在源文件main.cpp包含了header2.h,也包含了header1.h,这就导致header1.h在main.cpp中被包含了两次,也就造成了class A重复定义。

一个头文件被别的源文件重复包含是经常发生的,如何避免某个头文件被重复包含呢?利用条件编译轻松解决。在头文件的开始加入:

#ifndef HEADER_NAME
#define HEADER_NAME

在头文件的结尾加上:

#endif

HEADER_NAME可以为任意内容,只要能够唯一标识当前头文件即可,建议使用头文件的名称。将这些条件编译预处理指令加入上面的示例程序中的两个头文件,问题即可解决。此外,也可以使用#paragma once预处理指令来实现,但这种方法并非所有编译器都支持,考虑到代码的可移植性,建议使用条件编译预处理指令。

阅读以上示例代码,需要注意以下几点: (1)条件编译指令#ifndef HEADER_NAME#endif的意思是:如果条件编译标志HEADER_NAME没有定义的话,则编译#ifndef和#endif之间的程序段,否则就忽略它。头文件header1.h只要被包含一次,条件编译标志宏HEADER_NAME就会被定义,这样就不会被再次包含。

(2)iostream是标准库提供的头文件,所以被包含时在头文件两边使用尖括号<>,而header1.h和header2.h是用户自定义的头文件,被包含时使用双引号。


参考文献

[1]陈刚.C++高级进阶教程[M].武汉:武汉大学出版社,2008

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏IT可乐

Java关键字——native

  本篇博客我们将介绍Java中的一个关键字——native。   native 关键字在 JDK 源码中很多类中都有,在 Object.java类中,其 ge...

312110
来自专栏Java帮帮-微信公众号-技术文章全总结

day27.MongoDB【Python教程】

集合:类似于关系数据库中的表,储存多个文档,结构不固定,如可以存储如下文档在一个集合中

12530
来自专栏吴伟祥

Linux下的shell简介(三) 原

        shell的本意是“壳”的意思,其实已经很形象地说明了shell在Linux系统中的作用。shell就是围绕在Linux内核之外的一个“壳”程序...

10830
来自专栏个人随笔

房上的猫:java中的包

包  1.作用:   (1)包允许将类组合成较小的单元(类似文件夹),易于找到和使用相应的类文件   (2)防止命名冲突:     java中只有在不同包中的类...

40970
来自专栏未闻Code

Tenacity——Exception Retry 从此无比简单

Python 装饰器装饰类中的方法这篇文章,使用了装饰器来捕获代码异常。这种方式可以让代码变得更加简洁和Pythonic。

18110
来自专栏九彩拼盘的叨叨叨

Node.js 版本管理器: nvm 介绍

有时候,我们需要测试写的 Nodejs 的程序在不同 Nodejs 版本下是否能正常运行;或是我们想要尝试下最新版 Nodejs 的新特性,但常用的代码需要旧版...

9910
来自专栏醒者呆

你不想干我帮你——代理模式

关键字:设计模式,代理模式,proxy,保护代理,虚拟代理,远程代理,缓冲代理,智能引用代理 代理模式 代理模式:给某一个对象提供一个代理或占位符,并由...

34440
来自专栏自动化测试实战

flask第十篇——url_for【3】

24160
来自专栏好好学java的技术栈

学多线程的看过来,带你学习多线程中断机制

当我们点击某个杀毒软件的取消按钮来停止查杀病毒时,当我们在控制台敲入quit命令以结束某个后台服务时……都需要通过一个线程去取消另一个线程正在执行的任务。Jav...

15930
来自专栏积累沉淀

干货--Redis 30分钟快速入门

一、 redis环境搭建 1.简介        redis是一个开源的key-value数据库。它又经常被认为是一个数据结构服务器。因为它的value不仅...

337100

扫码关注云+社区

领取腾讯云代金券