漫谈:C C++ 嵌套包含与前置声明

 初级代码游戏的专栏介绍与文章目录-CSDN博客

我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。

这些代码大部分以Linux为目标但部分代码是纯C++的,可以在任何平台上使用。


目录

嵌套包含导致无限

要有结束机制终止无限

头文件的避免重复包含机制

结构可以包含指针而不是嵌套

不可避免的成员互相调用

总结

结构不可能对象嵌套

但结构可以包含另一个结构的指针,只需要前置声明

操作结构成员代码无法通过前置声明解决,只能放到cpp文件里面去


嵌套包含导致无限

        C、C++的头文件和结构都是可以互相包含的,虽然头文件和数据结构是完全不相干的东西,但是嵌套包含这种事,是有其内在的规律的。

        两个“东西”的互相包含是不可能的,互相嵌套至少会产生一个“无限”:无限空间、无限时间或者无限精度。

        自包含就是递归,函数递归会产生无限层级,从而需要无限时间和无限尺寸的栈空间,结构的递归在编译器计算结构尺寸这一步就会遇上无限。

要有结束机制终止无限

        所以一定要某种机制破坏无限,对于递归函数我们都知道,必须要有结束条件,到了某一步停止递归。

        对结构的互相调用呢?我们似乎本能地觉得没什么啊,互相包含,水乳交融嘛,比如

--示意代码,不严格
struct A
{
    struct B b;
    int a;
}
struct B
{
    struct A a;
    int b;
}

        这不挺好吗?但是这个代码注定无法编译,因为结构A有多大?整数4字节,再加上B,B包含一个整数和一个A,就是4+4+A,A又是4+B,就是4+4+4+B,B又是4+A……子子孙孙无穷匮也。

        头文件也是如此,A.h包含B.h,B.h又包含a.h,编译器到这里也傻眼了,无限包含停不下来。

头文件的避免重复包含机制

        当然了,解决头文件的互相包含很简单,对头文件有结束包含的机制:

#ifndef XXXX_H
#define XXXX_H

//头文件内容

#endif

        或者更简单的:

#pragma once

        这是个编译器指令,大部分编译器都支持。

        头文件这么做很简单,数据结构包含怎么解决?数据结构的互相包含没法用头文件那种机制解决,压根不能那样做,因为得到的数据结构不是预期的。

结构可以包含指针而不是嵌套

        那么为什么还能看到很多数据结构互相包含呢?不互相包含为什么需要前置声明呢?

        嗯……你指的是这样的代码吗?

--代码示意,不严格

struct B;--这是前置声明,仅仅说明B是一种结构的名字,具体内容不知道

struct A
{
    B * b;--这是指针,任何指针都是同样的长度,跟指向的东西没关系
    int a;
};

        看清楚啊,里面只是用到了指针——指针的大小和指向的数据的类型无关。编译器知道B是个类型名就可以生成A了,只有在后续使用b->的时候编译器才需要知道B的具体结构。

不可避免的成员互相调用

        即使我们不犯两个结构互相包含这种错误,而且我们也知道用前置声明来解决指针问题,我们也仍然会遇到头文件问题:

//A.h
#include "B.h"
class A
{
public:
    int iA;
    void fA(B * b)
    {
        b->iB;
    }
};


//B.h
#include "A.h"
class B
{
public:
    int iB;
    void fB(A * a)
    {
        a->iA;
    }
};

        由于两个类里面要用到->操作,编译器必须知道指针指向的结构的全部信息,所以必须包含头文件,这就不可避免地发生了头文件互相包含,当然我们在cpp文件里同时包含这两个头文件或仅仅只包含一个的时候,都会发生无限嵌套。

        不过前面已经说了头文件有避免无限嵌套的机制,而且一般每个头文件都应该使用这个机制。好吧,假设已经在上面的源码里添加了这个机制,那么处理A.h时发生什么:

  1. 处理A.h,记住A.h已经包含
  2. 包含B.h,记住B.h已经包含,此时B.h的内容被嵌入到A.h前面(替换包含语句)
  3. 处理B.h,又遇到包含A,发现A.h已经包含,所以不处理,无限嵌套被终止

        于是得到了如下代码:

//A.h
    //B.h
    //#include "A.h"--已经包含过,忽略
    class B
    {
    public:
        int iB;
        void fB(A * a)
        {
            a->iA;
        }
    };
class A
{
public:
    int iA;
    void fA(B * b)
    {
        b->iB;
    }
};


        编译器试图编译这个代码,却发现A未定义,即使在B.h加上A的前置声明也只能解决fB的参数“A* a”的问题,不能解决函数体里面的“a->iA”。

        这个问题唯一的解决方法就是把函数体放到cpp文件里面去。

总结

  • 结构不可能对象嵌套

  • 但结构可以包含另一个结构的指针,只需要前置声明

  • 操作结构成员代码无法通过前置声明解决,只能放到cpp文件里面去


(这里是结束)

相关推荐

  1. 漫谈:C C++ 嵌套包含声明

    2024-05-16 14:18:10       12 阅读
  2. 头文件相互包含 声明

    2024-05-16 14:18:10       7 阅读
  3. pwn基础

    2024-05-16 14:18:10       35 阅读
  4. 知识:字符集

    2024-05-16 14:18:10       27 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-05-16 14:18:10       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-05-16 14:18:10       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-05-16 14:18:10       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-05-16 14:18:10       18 阅读

热门阅读

  1. 【postgresql】PostgreSQL中的pgrowlocks插件介绍

    2024-05-16 14:18:10       11 阅读
  2. 机器学习 - 朴素贝叶斯

    2024-05-16 14:18:10       10 阅读
  3. 解决el-dialog弹框出现后页面滚动条可滚动问题

    2024-05-16 14:18:10       13 阅读
  4. nginx中,location匹配规则解析

    2024-05-16 14:18:10       11 阅读
  5. ubuntu 修改网卡名

    2024-05-16 14:18:10       7 阅读
  6. .net 框架基础(一) 字符、字符串

    2024-05-16 14:18:10       12 阅读
  7. leensa邀请码

    2024-05-16 14:18:10       10 阅读
  8. AI绘画原理及工具介绍

    2024-05-16 14:18:10       14 阅读
  9. Vue.js介绍

    2024-05-16 14:18:10       10 阅读
  10. 【Leetcode 每日一题】20. 有效的括号

    2024-05-16 14:18:10       13 阅读
  11. 【哈希】Leetcode 242. 有效的字母异位词【简单】

    2024-05-16 14:18:10       10 阅读
  12. Linux- cron调度进程

    2024-05-16 14:18:10       11 阅读
  13. 深度解析Kubernetes网络模型

    2024-05-16 14:18:10       10 阅读
  14. FunASR语音识别快速上手指南

    2024-05-16 14:18:10       10 阅读
  15. SDL系列(二)—— 渲染窗口与BMP图片

    2024-05-16 14:18:10       12 阅读
  16. Gone框架介绍19 -如何进行单元测试?

    2024-05-16 14:18:10       12 阅读