【友元补充】【动态链接补充】

时间:2024-10-01 10:30:03

友元

友元的目的是让一个函数或者类,访问另一个类中的私有成员。

友元的关键字friend是一个修饰符。

友元分为友元类和友元函数

1.全局函数作友元

2.类作友元

3.类的一个成员函数作友元

好处:可以通过友元在类外访问类内的私有和受保护类型的成员

坏处:破坏了类的封装性

class A {
	int num_a;
public:
	friend void fun();
	friend class B;
};
void fun() {
    A a;
	a.num_a = 2;
	cout << a.num_a << endl;
}
class B {
public:
	void fun() {
        A a;
		a.num_a = 2;
        cout << a.num_a << endl;
	}
};
int main() {
	fun();
    B b;
    b.fun();
	return 0;
}

 通过自行运行,我们可以看到结果如预期结果一致。

现在对于第三种情况进行单独讲解补充:

class B{
public:
	void work(){
        A a;
        a.num=3;
    };
};
class A{
	friend void  B::work(); //只让类内的某一个成员函数成为类A的友元函数
	int num;
};

当你把它看进去的时候,恭喜你,错了。此时a.num访问权限有问题。因为在这里A还没有将B的这个成员函数作为友元函数,A没有权限访问A类的私有成员。

class B{
public:
	void work();
};
class A{
	friend void  B::work(); //只让类内的某一个成员函数成为类A的友元函数
	int num;
};
void B::work() {
	A ma;
	ma.num = 3;
}

此时我们可以通过类外实现成员函数解决上述问题。而且类外实现的位置,必须在A将B的成员函数设为友元后。否则依然会报错。


 动态绑定

【⚪】成员函数必须声明为virtual

【⚪】如果基类中声明了为虚函数,则派生类中重写的函数不必再用virtual修饰

         调用方式:通过对象的指针或引用调用成员函数,或通过成员函数调用,反之就无法实现动态链接。

        特点:灵活,问题抽象性和问题的易维护性。

class A {
	int m_data1, m_data2;
public:
	virtual void vfunc1();
	virtual void vfunc2();
	void func1();
	void func2();
};
class B :public A {
	int m_data3;
public:
	virtual void vfunc1();
	void func1();
};
class C :public B {
	int m_data1, m_data4;
public:
	virtual void vfunc2();
	void func2();
};
类A、B、C的对象模型

利用终端查看:

 

图解分析: 

【⚪】由于这三个类都有虚函数,故编译器为每个类都创建了一个虚表,即类 A 的虚表(A vtbl),类B的虚表(B vtbl),类 C的虚表(C vtbl)。类 A,类 B,类 C 的对象都拥有一个虚表指针,*vptr,用来指向自己所属类的虚表。
【⚪】类 A 包括两个虚函数,故 A vtbl 包含两个指针,分别指向 A::vfunc1()和A::vfunc2()。

【⚪】类 B 继承于类 A,故类 B 可以调用类 A 的函数,但由于类 B 重写了B::vfunc1()函数,故 B vtbl 的两个指针分别指向 B::vfunc1()和 A::vfunc2()。

【⚪】类 C 继承于类 B,故类 C 可以调用类 B 的函数,但由于类 C 重写了C::vfunc2()函数,故 C vtbl 的两个指针分别指向 B::vfunc1()(指向继承的最近的一个类的函数)和C::vfunc2()。
【⚪】虽然模型看起来有点复杂,但是只要抓住“对象的虚表指针用来指向自己所属类的虚表,虚表中的指针会指向其继承的最近的一个类的虚函数”这个特点,便可以快速将这几个类的对象模型在自己的脑海中描绘出来。
【⚪】非虚函数的调用不用经过虚表,故不需要虚表中的指针指向这些函数。

假设我们定义一个类 B 的对象。由于 bObject是类 B 的一个对象,故 bObject包含一个虚表指针,指向类 B 的虚表。

int main(){
    B bObject;
    return 0;
}

现在,我们声明一个类 A 的指针p来指向对象 bObject。虽然p是基类的指针只能指向基类的部分,但是虚表指针亦属于基类部分,所以p可以访问到对象 bObject的虚表指针。bObiect的虚表指针指向类 B的虚表,所以p可以访问到 Bvtbl。 

int main(){
    B bObject;
    A* p=&bObject;//父类指针指向子类对象
    
    return 0;
}

 当我们使用p来调用vfunc1()函数时,会发生什么现象?

int main(){
    B bObject;
    A* p=&bObject;
    p->vfunc1();

    return 0;
}

 程序在执行 p->vfunc1()时,会发现p是个指针,且调用的函数是虚函数,接下来便会进行以下的步骤。
        首先,根据虚表指针 p->vptr来访问对象 bObject 对应的虚表。虽然指针p是基类 A 类型,但是 vptr 也是基类的一部分,所以可以通过 p->vptr 可以访问到对象对应的虚表
        然后,在虚表中查找所调用的函数对应的条目。由于虚表在编译阶段就可以构造出来了,所以可以根据所调用的函数定位到虚表中的对应条目。对于p->vfunc1()的调用,B vtbl 的第一项即是 vfunc1 对应的条目。
        最后,根据虚表中找到的函数指针,调用函数。从图3可以看到,Bvtbl的第一项指向 B::vfunc1(),所以 p->vfunc1()实质会调用 B::vfunc1()函数。

class A{
public:
    virtual void vfun1(){
        cout<<"father eat"<<endl;
    }
};
class B:public A{    
public:
    virtual void vfun1()//在继承关系下,vfun1前面不写virtual,它也是虚函数
    {
        cout<<"child eat"<<endl;
    }
};
int main(){
    A *a=new B;
    a->vfun1();

    return 0;
}

感谢大家支持,今日份知识点已经补充完毕。