我的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时发生什么:
- 处理A.h,记住A.h已经包含
- 包含B.h,记住B.h已经包含,此时B.h的内容被嵌入到A.h前面(替换包含语句)
- 处理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文件里面去
(这里是结束)