友元
友元的目的是让一个函数或者类,访问另一个类中的私有成员。
友元的关键字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 的虚表(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;
}
感谢大家支持,今日份知识点已经补充完毕。