IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 实用经验 58 防止重复包含头文件 -> 正文阅读

[C++知识库]实用经验 58 防止重复包含头文件

头文件重复包含,是每个程序员都遇到过的问题。头文件重复包含,可能会导致的错误包括:变量重定义,类型重定义及其他一些莫名其妙的错误。现在我们就讨论如何避免这些问题。

假设,我们的工程中存在a.h,b.h及c.cpp等3个文件。其中b.h中包含了a.h,而c.cpp又包含了a.h和b.h两个文件。文件代码如下所示。

/*  测试头文件重复包含问题 */
// File : a.h

#include <iostream.h>

// func_1 实现func_1函数名称打印。
void func_1()
{
	std::cout << "this is" << __FUNCTION__ << endl;
}

// File : b.h

#include "a.h"

// Func_2 实现Func_2函数名称打印。并调用func_1。
void Func_2()
{
   std::cout << "this is " << __FUNCTION__ << endl;
   std::cout << "this function called " << endl;
   func_1();
}

// File : c.cpp
#include "a.h"
#include "b.h"

int main()
{
    // ....
    return 0;
}

如果,你在VC++编译器中编译上述代码。在编译过程中,编译器会抛出“重复定义”错误。因为a.h被重复包含了2次。为了避免同一个文件被重复包含多次。C++提出了2种解决方案。他们是#ifndef方式和#pragman once方式。

方式1:

#ifndef __SOME_FILE_H__
#define __SOME_FILE_H__
...   // 一些声明语句
#endif

方式2:

#pragma once
...  // 一些声明语句

1. #ifndef方式

这种实现方式,通过预处理实现唯一检查。预处理器首先测试__SOME_FILE_H__预处理器变量是否未定义。如果__SOME_FILE_H__未定义,那么#ifndef测试成功,跟在#ifndef 后面的所有行都被执行,直到发现 #endif。相反,如果__SOME_FILE_H__已定义,那么#ifndef 指示测试为假,该指示和#endif指示间的代码都被忽略。

为了保证头文件在给定的源文件中只处理过一次,我们首先检测#ifndef。第一次处理头文件时,测试会成功,因为__SOME_FILE_H__还未定义。下一条语句定义了__SOME_FILE_H__。那样的话,如果我们编译的文件恰好又一次包含了该头文件。#ifndef 指示会发现__SOME_FILE_H__已经定义,并且忽略该头文件的剩余部分。

头文件应该含有保护符,即使这些头文件不会被其他头文件包含。编写头文件保护符并不困难,而且如果头文件被包含多次,它可以避免难以理解的编译错误。

当没有两个头文件定义和使用同名的预处理器常量时,这个策略相当有效。当有两个文件使用同一个宏,这个策略就失效了。当遇到这种问题时,一般有两种解决方案。

方案1:可以为定义在头文件里的实体(如类)命名预处理器变量,来避免预处理器变量重名的问题。例如:一个程序只能含有一个名为Sales_item的类。通过使用类名来组成头文件和预处理器变量的名字,可以使得很可能只有一个文件将会使用该预处理器变量。

方案2:为了保证宏的唯一性,我们可以采用google提供的解决方案,在定义宏时,宏名基于其所在的项目源代码数的全路径命名。宏命名格式为:

_<PROJECT>_<PATH>_<FILE>_H_

最佳实践

  • 在定义#ifndef测试宏时,宏名最好采用全大写字母表示。
  • 在定义测试宏时,最好采用google提供的解决方案。

2. #pragma once

这种方式一般又编译器提供,只要在头文件的最开始加入这条指令就能够保证头文件被编译一次#pragma once用来防止某个头文件被多次include,#ifndef方式用来防止某个宏被多次定义。

#pragma once是编译相关,就是说这个编译系统上能用,但在其他编译系统不一定可以,也就是说移植性差,不过现在基本上已经是每个编译器都有这个定义了。

#ifndef,#define,#endif这是C++语言相关的,是C++语言中的宏定义,通过宏定义避免文件多次编译。所以在所有支持C++语言的编译器上都是有效的,如果写的程序要跨平台,最好使用这种方式。

小心陷阱

  • 针对#pragma once,GCC已经取消对其的支持了。而微软的VC++却依然支持。
  • 如果写的程序需要跨平台,最好使用#ifndef方式,而避免使用#progma once方式。

3. #pragma once与 #ifndef的区别

#ifndef的方式依赖于宏名字不能冲突,这不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含。当然,缺点就是如果不同头文件的宏名不小心“撞车”,可能就会导致头文件明明存在,编译器却硬说找不到声明的窘况。

#pragma once则由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。带来的好处是,你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题。对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名碰撞引发的“找不到声明”的问题,重复包含更容易被发现并修正。

请谨记

  • 为了避免重复包含头文件,建议在每个头文件时采用“头文件卫士”加以保护。头文件卫士有两种形式一种是#progma once,一种是#ifndef。
  • 如果你的程序需要跨平台,建议你使用#ifndef方式。因为这种方式仅与C++语言有关。
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-08-03 10:58:44  更:2021-08-03 10:59:44 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/9 14:14:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码