zoukankan      html  css  js  c++  java
  • [转]C++中头文件相互包含的几点问题

    转自:http://www.sf.org.cn/article/base/200808/21170.html

    一、类嵌套的疑问

    C++头文件重复包含实在是一个令人头痛的问题,前一段时间在做一个简单的数据结构演示程序的时候,不只一次的遇到这种问题。假设我们有两个类A和B,分别定义在各自的有文件A.h和  B.h中,但是在A中要用到B,B中也要用到A,但是这样的写法当然是错误的:
    class B;

    class A
    {
    public:
    B b;
    };

    class B
    {
    public:
    A a;
    };
    因为在A对象中要开辟一块属于B的空间,而B中又有A的空 间,是一个逻辑错误,无法实现的。在这里我们只需要把其中的一个A类中的B类型成员改成指针形式就可以避免这个无限延伸的怪圈了。为什么要更改A而不是 B?因为就算你在B中做了类似的动作,也仍然会编译错误,表面上这仅仅上一个先后顺序的问题。
    为什么会这样呢?因为C++编译器自上而下编译源文件的时候,对每一个数据的定义,总是需要知道定义的数据的类型的大小。在预先声明语句class B;之后,编译器已经知道B是一个类,但是其中的数据却是未知的,因此B类型的大小也不知道。这样就造成了编译失败,VC++6.0下会得到如下编译错 误:
    error C2079: ‘b’ uses undefined class ‘B’
    将A中的b更改为B指针类型之后,由于在特定的平台上,指针所占的空间是一定的(在Win32平台上是4字节),这样可以通过编译。

    二、不同头文件中的类的嵌套

    在实际编程中,不同的类一般是放在不同的相互独立的头文件中的,这样两个类在相互引用时又会有不一样的问题。重复编译是问题出现的根本原因。为了保 证头文件仅被编译一次,在C++中常用的办法是使用条件编译命令。在头文件中我们常常会看到以下语句段(以VC++6.0自动生成的头文件为例):

    #if !defined(AFX_STACK_H__1F725F28_AF9E_4BEB_8560_67813900AE6B__INCLUDED_)
    #define AFX_STACK_H__1F725F28_AF9E_4BEB_8560_67813900AE6B__INCLUDED_
    //很多语句……
    #endif

    其中首句#if !defined也经常做#ifndef,作用相同。意思是如果没有定义过这个宏,那么就定义它,然后执行直到#endif的所有语句。如果下次在与要这 段代码,由于已经定义了那个宏,因此重复的代码不会被再次执行。这实在是一个巧妙而高效的办法。在高版本的VC++上,还可以使用这个命令来代替以上的所 有:
    #pragma once
    它的意思是,本文件内的代码只被使用一次。

    但是不要以为使用了这种机制就全部搞定了,比如在以下的代码中:

    //文件A.h中的代码
    #pragma once

    #include “B.h”

    class A
    {
    public:
    B* b;
    };

    //文件B.h中的代码
    #pragma once

    #include “A.h”

    class B
    {
    public:
    A* a;
    };

    这里两者都使用了指针成员,因此嵌套本身不会有什么问题,在主函数前面使用#include “A.h”之后,主要编译错误如下:
    error C2501: ‘A’ : missing storage-class or type specifiers
    仍然是类型不能找到的错误。其实这里仍然需要前置声明。分别添加前置声明之后,可以成功编译了。代码形式如下:

    //文件A.h中的代码
    #pragma once

    #include “B.h”

    class B;

    class A
    {
    public:
    B* b;
    };

    //文件B.h中的代码
    #pragma once

    #include “A.h”

    class B;

    class B
    {
    public:
    A* a;
    };

    这样至少可以说明,头文件包含代替不了前置声明。有的时候只能依靠前置声明来解决问题。我们还要思考一下,有了前置声明的时候头文件包含还是必要的 吗?我们尝试去掉A.h和B.h中的#include行,发现没有出现新的错误。那么究竟什么时候需要前置声明,什么时候需要头文件包含呢?

    三、两点原则

    头文件包含其实是一想很烦琐的工作,不但我们看着累,编译器编译的时候也很累,再加上头文件中常常出现的宏定义。感觉各种宏定义的展开是非常耗时间的,远不如自定义函数来得速度。我仅就不同头文件、源文件间的句则结构问题提出两点原则,仅供参考:

    第一个原则应该是,如果可以不包含头文件,那就不要包含了。这时候前置声明可以解决问题。如果使用的仅仅是一个类的指针,没有使用这个类的具体对象(非指针),也没有访问到类的具体成员,那么前置声明就可以了。因为指针这一数据类型的大小是特定的,编译器可以获知。

    第二个原则应该是,尽量在CPP文件中包含头文件,而非在头文件中。假设类A的一个成员是是一个指向类B的指针,在类A的头文件中使用了类B的前置 声明并编译成功,那么在A的实现中我们需要访问B的具体成员,因此需要包含头文件,那么我们应该在类A的实现部分(CPP文件)包含类B的头文件而非声明 部分(H文件)。

    PS:

    c++中通常 将struct class #define typedef function 等的定义放到头文件中
    而将 成员声明、function实现、类实现、放入cpp中

    h和cpp中同时可以使用include包含头文件,但是我们通常有这个习惯

    也就是尽量不要在.h中include非必须的其他.h

    也就是说
    当.h中的 定义 内容 用到了T* t那么我们 没有必要 #include "t.h"之需要在。h中 class T;声明一下 即可。
    当.h中用到了T t;也就是 非指针时,此时可以在.h中 include "t.h"

    以上均是书写规范问题。当然所有的.h都include进来也不会编译错。

    至于为什么有这个书写规范,就需要你再仔细理解 一下c++接口设计了。也就是说 如果你的.h作为一个模块给别人使用时 没有必要把没有用的.h同时提供给别人。

    至于编译速度的话 ide有优化的。你没有用到的东西他不会编译进去的。
  • 相关阅读:
    Codeforces Round #344 (Div. 2) C. Report 其他
    Codeforces Round #344 (Div. 2) B. Print Check 水题
    Codeforces Round #344 (Div. 2) A. Interview 水题
    8VC Venture Cup 2016
    CDOJ 1280 772002画马尾 每周一题 div1 矩阵快速幂 中二版
    CDOJ 1280 772002画马尾 每周一题 div1 矩阵快速幂
    CDOJ 1279 班委选举 每周一题 div2 暴力
    每周算法讲堂 快速幂
    8VC Venture Cup 2016
    Educational Codeforces Round 9 F. Magic Matrix 最小生成树
  • 原文地址:https://www.cnblogs.com/apigiraffe/p/3109968.html
Copyright © 2011-2022 走看看