c++中友元函数带来的头文件的互包含问题

关于头文件的一点使用感悟

  1. 模块化。这没啥好说的,一个类一个头文件,然后在各自的cpp文件中定义。

  2. 当模块化的类之间有交互关系的时候,利用合理的头文件声明可以让你的模块代码能够写下去。

  3. 如果用到了一个类的具体内容,那么应该包含这个类所在的头文件,只有这样才能知道这个类的具体定义,从而能够编译出相应内容。

  4. 如果只用到了一个类的名字,而没有用到实际内容,则只需要给出这个类的申明即 class A 即可,不用包含头文件了。

提出问题

	如何构造头文件来体现以下关系:
	
	A类,B类。
	A类中有一个私有变量。
	B类中的fun函数是A类中的友元函数。
	可以通过fun这个友元函数来改变A的对象的私有变量值。
	
	要求:B的所有实现放在B.cpp中
		  A的所有实现放在A.cpp中
		  从而实现模块化。


网上对这个问题的大多数分析都是从编译的顺序来说的
这是没错的,但这里说的是自己总结的一个小技巧。

直接给出解决办法

以下内容省略了ifndef…define…endif

A的头文件

/************************************************************/
#include"B.h"				//因为在类A中声明了B中的一个函数
							//而只有B已经定义了才能知道B中有什么
							//所以需要包含B类所在的头文件
							//这一点理解非常重要
							//因为它涉及到了类的内容的使用,不单单是声明
class A{
	friend void B::fun(A&);
	public:
		A(int i):a(i){}
		int getA(){return a;}
		
	privateint a;
};

B的头文件

/**********************************************************/

//B.h文件
class A;		//因为在这里只涉及到了A类的声明
				//而没有涉及到A类的内容的使用
				//所以只需要申明有A这个类型就可以了

class B{
	public:
		void fun(A&);
}

B的cpp文件

/*********************************************************/

//B的函数一般思想是在B.cpp中实现
//B.cpp
#include"B.h"	//这里是基础的必须有的内容
#include"A.h"	//这里是因为用到A中的内容了,并且在这里需要得到fun函数是
				//A的友元这一含义,所以说包含这个头文件是必须的
void B::fun(A& aa){
	aa.num = 9999;	//在B类的函数中修改A中的私有成员
}

main.cpp(运行主程序)

#include<iostream>
#include"B.h"
#include"A.h"

int main(){
	A a(10);
	B b;
	b.fun(a);
	cout << a.getA() << endl;
}

输出结果:
9999

总结:

  1. 头文件并不是都在另一个头文件中被包含(也可以在cpp中包含),是要看需要进行包含,要避免不必要的包含。
  2. 把类、函数的申明和实现分开放,一个类放一个头文件,每个头文件有对应的cpp实现文件,努力做到模块分离。
  3. 当前头文件 包含了另一个类的具体内容的时候,需要包含头文件只含有另一个类的名字直接申明 class 另一个类即可,不需要包含头文件
  4. 当前cpp实现文件中如果用到了另一个类的具体内容则需要包含另一个类所在的头文件(若已经被当前头文件包含,就不需要包含了);如果没有用到另一个类的具体内容,则不用干啥了(不用class申明,因为在3中你已经做到了)。

用指针解决

只需要把传对象的地方换成对象的指针,因为指针在编译器中的大小都是固定的,所以不需要知道另一个类的已经实现的东西,而可以直接编译通过。具体不再赘述。

更新2020.12.25 18:49

一种错误的头文件表达友元的方式:
A.h

#ifndefine
#define _B_H_
#include"B.h"			//因为申明的时候用到了B的内容(指fun函数)
						//所以要包含头文件,得到B类的整个声明结构
						//而不能只单纯的class B;

class A {
	friend void B::fun(A&);			
private:
	int num;
public:
	A(int);
	int getNum();
};

#endif

B.h

#ifndefine
#define _B_H_

#include"A.h"		//这里模仿的是一般的写法
					//即认为用到了A(无论只是名字还是有内容)就包含头文件。

class B {
private:
	int x;
public:
	B(int);
	void fun(A&);
};

#endif

B.cpp

#include "B.h"

B::B(int a)
{
	x = a;
}

void B::fun(A& aa)
{
	aa.num = 9999;
}

main.cpp

#include<iostream>
#include"A.h"
#include"B.h"
using namespace std;

int main() {
	B b(10);
	A a(5);
	b.fun(a);
	cout << a.getNum();
}

执行过程分析:(我们要时刻注意#ifndefine这个条件)

  1. 预编译是从main.cpp开始的,因为这个文件中有标准入口函数main函数

  2. 第一句#include 正常替换

  3. 第二句#include"A.h" ---->>>使得替换目标向A.h转变。
    经过A.h中的#ifndefine…#define…#endif,编译器已经定义了和A.h有关的头文件标识!!

    我们看A.h

#include"B.h"

class A {
	friend void B::fun(A&);			
private:
	int num;
public:
	A(int);
	int getNum();
};

发现了A.h的第一句是 #include"B.h" ,则应该先把这部分替换到A.h文件中,再把整个A.h文件中的声明信息替换到main中。

  1. 既然上面要先替换B.h则我们转到B.h进行同样的查看,看是否有包含更深的头文件包含,B.h内容如下::
#ifndefine
#define _B_H_

#include"A.h"

class B {
private:
	int x;
public:
	B(int);
	void fun(A&);
};

#endif

发现了B.h第一句是 #include"A.h" ,因为我们本意是希望B中声明的fun的参数有意义。但是但是但是!!!!
由于在第一步main.cpp中我们先找到的是 #include"A.h",经过了A.h中的已经定义过的头文件标识,这里的 #include"A.h"不会再生效了,因此实际上包含的是除了**#include"A.h"**之外的B的头文件中的内容。

这时候main.cpp经过替换后的内容结构如下:

iostream.h 部分的声明(我们不关心)

B.h中的声明部分

class B {
private:
	int x;
public:
	B(int);
	void fun(A&);
};



A.h中除了     #include"B.h"   的其余内容(因为这一个头文件已经被替换成上面的B.h中的内容)

class A {
	friend void B::fun(A&);			
private:
	int num;
public:
	A(int);
	int getNum();
};


开始编译,则会报出B类中A是错误的语法标识。原因是类A在之前并没有被定义或者引用申明。
并且由于这个**#include"A.h"**没被替换,所以在B.cpp中的实现也会有问题:

#include "B.h"

B::B(int a)
{
	x = a;
}

void B::fun(A& aa)
{
	aa.num = 9999;		///由于没有A.h,所以不可访问
}

再说一句,根据大量的实验表明,如果你的A,B头文件是互相包含关系,那么,即便你在B中申明Class A;也不顶事,编译器会报错说你A类中定义友元friend void B::fun(A&) 的B 不是标识符也不是命名空间!!!

一句话,用到内容再包含头文件,用不到内容就只申明class。

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值