转自:http://blog.csdn.net/hazir/article/details/38600419

今天下午遇到一个头文件相互包含而导致的编译问题,花了我不少时间去调试没找到问题,最后晚上跟师兄讨论不少时间,突然有所顿悟!

问题重现

我把问题脱离于项目简单描述一下:我写了一个函数 bool func(ClassA* CA) 需要加到项目中,我就把这个函数的声明放到 head1.h 中,函数参数类型 ClassA 定义在另一个头文件 head2.h 中,因此我需要在 head1.h 中包含 head2.h;而 head2.h 中之前又包含了 head1.h,这样就构成了一种头文件相互包含的场景。再加上一些其它的声明与定义,就构成了这样的一个文件结构:

  • head1.h
#ifndef __HEAD_1_H__
#define __HEAD_1_H__ #include "head2.h" #define VAR_MACRO 1 //define a macro, which used in head2.h bool func(ClassA* CA); //ClassA is defined in head2.h #endif
  • head2.h
#ifndef __HEAD_2_H__
#define __HEAD_2_H__ #include "head1.h" class ClassA{
int mVar;
void setMem(){ mVar = VAR_MACRO }; //macro VAR_MACRO is defined in head1.h ... //other members and functions
}; #endif

那么,现在另有两个源文件

  • source1.cpp
#include "head1.h"

//... some source code
  • source2.cpp
#include "head2.h"

//... some source code

整个项目会分别编译这两个源文件,编译完之后会报错,大致意思是 ClassA 和 VAR_MACRO 没有定义,那么问题就比较奇怪了,每个头文件都分别引用了另一个头文件,为什么会出现未定义呢?

问题分析

我们都知道 C/C++ 中头文件开始习惯使用 #ifndef ... #define ... #endif 这样的一组预处理标识符来防止重复包含,例如上面的问题中我也使用了,如果不使用的话,两个头文件相互包含,就出现递归包含。这个很好理解就不多叙。

回到问题本身,我在微博上贴出了这个问题,有人说在 head1.h 的函数前加上 ClassA A; 的前置声明,至于为什么要加,估计很多人都没理解...

对于 source1.cpp,它包含了头文件 head1.h,那么在编译之前,在 source1.cpp 中展开 head1.h,而 head1.h 又包含了 head2.h, 那么也展开它,这时 source1.cpp 就变成类似下面这样:

class ClassA{
int mVar;
void setMem(){ mVar = VAR_MACRO }; //macro VAR_MACRO is defined in head1.h ... //other members and functions
}; #define VAR_MACRO 1 //define a macro, which used in head2.h bool func(ClassA* CA); //ClassA is defined in head2.h //... source1.cpp source code

看到没,这地方 func 函数之前有 ClassA 类型的定义,根本没必要像有些人说的那样加上 ClassA CA; 这样的前置声明。

我们再展开 source2.cpp 看看:

#define VAR_MACRO  1          //define a macro, which used in head2.h

bool func(ClassA* CA);        //ClassA is defined in head2.h

class ClassA{
int mVar;
void setMem(){ mVar = VAR_MACRO }; //macro VAR_MACRO is defined in head1.h ... //other members and functions
}; //... source2.cpp source code

这时问题就很清楚了,func 函数声明之前并没有发现 ClassA 类型定义,该定义在函数声明的后面,这时候如果能在head1.h 的函数声明之前加上 ClassA CA; 的前置声明,就不会在编译的时候报找不到 ClassA 的定义的错误了。

再回到 source1.cpp 展开的源码看看,是不是一下子明白了为什么报找不到 VAR_MACRO 的定义的错误了?修改方法也简单,把宏定义拉到 #include "head2.h" 语句之前就 OK 了。

问题反思

现在回头想想这个问题,其实是个非常简单的头文件包含的问题,如果了解一些编译器的预编译过程,错误原理也很简单。但为什么我卡在这个问题很长时间,原因有以下几点:

  • 问题出现在一个项目的编译过程中,未能准确地定位问题的原因
  • 出现问题,没有静下来心来理清问题,C/C++ 编译基础知识点虽然知道,但未能第一时间运用到该问题的分析上
  • 师兄说加上前置类的声明,确实解决了类类型找不到的错误,虽然有问题解决方法,但没有真正理解这种做法的道理,以至于宏定义找不到的错误还是不知如何去解决(其实本质是一样的)

总的来说,遇到问题不要慌,保持大脑清醒,把加一行或减一行代码期望就能碰运气编译通过的时间拿来分析问题更有效,解决问题之后一定确定自己是否知其然亦知其所以然!

参考资料

Google找了一圈,没找到有价值的资料....

C/C++ 中头文件相互包含引发的问题的更多相关文章

  1. C++中头文件相互包含与前置声明

    一.类嵌套的疑问 C++头文件重复包含实在是一个令人头痛的问题,前一段时间在做一个简单的数据结构演示程序的时候,不只一次的遇到这种问题.假设我们有两个类A和B,分别定义在各自的有文件A.h和B.h中, ...

  2. C++中头文件与源文件的作用详解

    一.C++ 编译模式 通常,在一个 C++ 程序中,只包含两类文件―― .cpp 文件和 .h 文件.其中,.cpp 文件被称作 C++ 源文件,里面放的都是 C++ 的源代码:而 .h 文件则被称作 ...

  3. C++头文件的包含顺序研究

    一.<Google C++ 编程风格指南>里的观点 公司在推行编码规范,领导提议基本上使用<Google C++ 编程风格指南>.其中<Google C++ 编程风格指南 ...

  4. c中头文件在cpp文件里引用和.h文件引用的思考

    我们在编敲代码中头文件是常常使用的. 可是头文件是应该包括在.H文件里还是在.cpp文件里.在这个其中有什么样去差别呢. 假如说我们编写了一个a.cpp  .我们将a.cpp文件的变量和函数申明在a. ...

  5. C++中头文件、源文件之间的区别与联系

    .h头文件和.cpp文件的区别 疑惑1:.h文件能够编写main函数吗? 实验: 编写test.h文件,里面包含main函数 若直接编译g++ test.h -o test,通过file命令 file ...

  6. C语言中头文件怎么写?(本文来源网络,由黑乌鸦进一步完善)

      c语言头文件怎么写?我一直有这样的疑问,但是也一直没去问问到底咋回事:所以今天一定要把它弄明白! 其实学会写头文件之后可以为我们省去不少事情,可以避免书写大量的重复代码.有利于整理思路.使代码脉络 ...

  7. C++ 中头文件(.h)和源文件(.cc)的写法简述

    用C++编写比较大型的项目时,文件的分割管理确实确实是非常必要的 .下面就非常简洁明了地谈谈头文件(.h)和源文件(.cc)应该怎么写. 头文件(.h):写类的声明(包括类里面的成员和方法的声明).函 ...

  8. C++ 中头文件<bits/stdc++.h>的优缺点

    在编程竞赛中,我们常见一个头文件: #include <bits/stdc++.h> 发现它是部分C++中支持的一个几乎万能的头文件,包含所有的可用到的C++库函数,如<istrea ...

  9. C/C++避免头文件重复包含的方法

    C/C++避免头文件重复包含的方法 1. #ifndef 2. #pragma once 3. 混合使用 在实际的编程过程中,因为会使用多个文件,所以在文件中不可避免的要引入一些头文件,这样就可能会出 ...

随机推荐

  1. Java(静态)变量、(静态)代码块、构造方法的执行顺序

    Java(静态)变量.(静态)代码块.构造方法的执行顺序 总结 1.父类静态变量和静态代码块(先声明的先执行); 2.子类静态变量和静态代码块(先声明的先执行); 3.父类的变量和代码块(先声明的先执 ...

  2. 【转载】Picasso源码解析

    原文地址:http://blog.csdn.net/u012702547/article/details/52273918 Picasso是Squareup公司出的一款图片加载框架,能够解决我们在An ...

  3. linux网络管理----网络基础

    1.1 ISO/OSI 七层模型 笔记: 打入ipconfig: mac地址:Media Access Control 也是物理地址,这是由网卡决定的.负责的是内网,也就是局域网通信. IPv4地址: ...

  4. VIM块操作

    一.可视模式 按v启用可视模式,之后移动光标可以选择. 如:     如果想整行操作,则用大写的V,再移动光标可以按行为单位进行选择. 二.列块操作 在 word中有一个功能,按alt加鼠标拖动,可以 ...

  5. 从JDBC看Mybatis的设计

    Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法. 六 ...

  6. java console 到文件

    System.setOut(new PrintStream(new FileOutputStream("c:\\temp\\test1.txt"))); System.out.pr ...

  7. 【BZOJ 2437】 2437: [Noi2011]兔兔与蛋蛋 (博弈+二分图匹配**)

    未经博主同意不得转载 2437: [Noi2011]兔兔与蛋蛋 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 693  Solved: 442 Des ...

  8. bzoj 1098

    对于关系,看其是否是“等价关系”,即满足:自反,传递,对称. 如果是可以用并查集来连接等价类. 这道题是求原图补集的联通快个数,考虑原图度最少的点(由鸽巢原理,最多为2*e/n个). 先将未与其连边的 ...

  9. ThinkPHP -- magic_quotes_gpc()引起的反斜杠问题

    magic_quotes_gpc()引起的反斜杠问题,通常是因为没有事先判断它的状态,而对字符串进行处理引起的.   (本文学习借鉴于hechunhua楼主) 一般提供的服务器空间默认PHP 指令 m ...

  10. PAT甲级1098. Insertion or Heap Sort

    PAT甲级1098. Insertion or Heap Sort 题意: 根据维基百科: 插入排序迭代,消耗一个输入元素每次重复,并增加排序的输出列表.在每次迭代中,插入排序从输入数据中删除一个元素 ...