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


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

    目录

    嵌套包含导致无限

    要有结束机制终止无限

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

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

    不可避免的成员互相调用

    总结

    结构不可能对象嵌套

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

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


    嵌套包含导致无限

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

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

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

    要有结束机制终止无限

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

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

    1. --示意代码,不严格
    2. struct A
    3. {
    4. struct B b;
    5. int a;
    6. }
    7. struct B
    8. {
    9. struct A a;
    10. int b;
    11. }

            这不挺好吗?但是这个代码注定无法编译,因为结构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,编译器到这里也傻眼了,无限包含停不下来。

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

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

    1. #ifndef XXXX_H
    2. #define XXXX_H
    3. //头文件内容
    4. #endif

            或者更简单的:

    #pragma once

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

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

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

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

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

    1. --代码示意,不严格
    2. struct B;--这是前置声明,仅仅说明B是一种结构的名字,具体内容不知道
    3. struct A
    4. {
    5. B * b;--这是指针,任何指针都是同样的长度,跟指向的东西没关系
    6. int a;
    7. };

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

    不可避免的成员互相调用

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

    1. //A.h
    2. #include "B.h"
    3. class A
    4. {
    5. public:
    6. int iA;
    7. void fA(B * b)
    8. {
    9. b->iB;
    10. }
    11. };
    12. //B.h
    13. #include "A.h"
    14. class B
    15. {
    16. public:
    17. int iB;
    18. void fB(A * a)
    19. {
    20. a->iA;
    21. }
    22. };

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

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

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

            于是得到了如下代码:

    1. //A.h
    2. //B.h
    3. //#include "A.h"--已经包含过,忽略
    4. class B
    5. {
    6. public:
    7. int iB;
    8. void fB(A * a)
    9. {
    10. a->iA;
    11. }
    12. };
    13. class A
    14. {
    15. public:
    16. int iA;
    17. void fA(B * b)
    18. {
    19. b->iB;
    20. }
    21. };

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

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

    总结

    • 结构不可能对象嵌套

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

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

    (这里是结束)

  • 相关阅读:
    Elasticsearch深入理解(六) —— 如何处理并发冲突
    线程的状态
    从zip文件到QByteArray再到zip文件
    c++学习之红黑树
    低代码:让软件开发不再遥不可及
    联邦学习-Tensorflow实现联邦模型AlexNet on CIFAR-10
    详解:程序部署在服务器上,localhost可以访问Tomcat,但是外网ip无法访问
    ROS1 and ROS2一键安装
    【.net core】【sqlsugar】条件查询时使用Contains的注意问题
    4月平板电脑行业线上销售数据分析
  • 原文地址:https://blog.csdn.net/2301_77171572/article/details/133986027