###多台概念
- 多态
同一件事物在不同的场景下表现出的多种形态
C++中虚函数的主要作用就是实现多态。简单说父类的指针/引用调用重写的虚函数, 当父类指针/引用指向父类对象时调用的是父类的虚函数,指向子类对象时调用的是子类的虚函数 - 虚函数
- 虚函数是一种在基类定义为
virtual
的函数,并在一个或多个派生类中再定义的函数。虚函数的特点是,只要定义一个基类的指针,就可以指向派生类的对象。 - 虚函数重写—当在子类定义了一个与父类完全相同的虚函数时,则称子类的这个函数重写(也称覆盖)了父类的这个虚函数。
- 它虚就虚在所谓**“推迟联编”或者“动态联编”上,一个类函数的调用并不是在编译时刻被确定的**,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被称 为“虚”函数。
- 虚函数是一种在基类定义为
eg:* 在两个变量之间表示“乘号”,在指针前表示“解引用”
###静态多态&动态多态
- 静态多态(静态链编译,静态绑定,早绑定)—编译器期间
int Add(int left, int right)
{
return left + right;
}
double Add(double left, double right)
{
return left + right;
}
int main()
{
Add(12, 34);
Add(12.0, 34.0);
return 0;
}
静态多态是编译器在编译期间完成的
编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数
如果有对应的函数就调用该函数,否则出现编译错误
- 动态多态(静态链编,动态绑定,晚绑定)—程序运行期间
class Base
{
public:
virtual void TestFun()//虚函数
{
cout << "Base::TestFun()" << endl;
}
}
class Derived :public Base
{
public:
virtual void TestFun()//virtual可加可不加
{
cout << "Derived::TestFun()" << endl;
}
};
void TestVirualFun(Base &rb)
{
rb.TestFun();
}
void TestVirualFun(Base *pb)
{
pb->TestFun();
}
int main()
{
Base b;
Derived d;
TestVirualFun(b);
TestVirualFun(d);
TestVirualFun(&b);
TestVirualFun(&d);
system("pause");
return 0;
}
动态多态是程序运行期间
普通调用跟对象类型有关,多态调用跟具体对象有关。
###动态多态的构成条件
-
基类中必须包含虚函数,并且派生类一定要对基类中的虚函数进行重写
-
通过基类的指针或引用来调用虚函数
重写:- 在派生类中要重写的函数在基类中必须是虚函数
-
派生类中虚函数必须与基类中虚函数原型保持一致
- 返回值一样、函数名一样、参数列表一样
- 例外:析构函数(函数名不同)
协变(基类的虚函数返回基类的引用或者指针,派生类的虚函数返回虚函数的引用或指针)- 派生类中函数virual可加可不加,仍保持虚函数特性
-
基类与派生类虚函数的访问权限可以不同,注意:基类中虚函数访问权限必须是public
###重写&重定义(同名隐藏)&函数重载
总结:
- 派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变||析构函数除外)
- 基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性
- 只有类的成员函数(非静态成员函数)才能定义为虚函数
- 静态成员函数不能定义为虚函数
- 如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加
- **构造函数不能为虚函数,**虽然可以将operator=定义为虚函数,但是最好不要将operator=定义为虚函数,因为容易使用时容易引起混淆。
- 不要在构造函数和析构函数里面调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会发生未定义的行为。
- 最好把基类的析构函数声明为虚****函数
- 虚表示所有类对象实例共用的
###抽象类 - 在成员函数(必须为虚函数)的形参列表后面写上=0,则成员函数为纯虚函数。
- 包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。
- 纯虚函数在派生类中重新定义以后,派生类才能实例化出对象
class Person
{
public:
virtual void GoToWashRoom() = 0;
private:
string _strName;
};
class Man
{
public:
virtual void GoToWashRoom()
{
cout<<"请上男厕,否则会被打死!!!"<<endl; } private: string _strGender;
};
int main()
{
Person p; // 该条语句会编译失败,一个人的型别都不知道,怎么生出来
Man m;
return 0;
}
###哪些函数不能定义为虚函数?
- 不能被继承的函数
- 不能被重写的函数
- 普通函数(不在继承体系里)
- 友元函数 (不是类成员)
- 构造函数 (对象不完整)
- 内联成员函数 (已经被展开)
- 静态成员函数(调用不依赖于对象)
原因:虚函数是为了实现多态调用,具体调哪个函数需要用虚表来找,所以需要知道虚表的地址,而其地址在对象的前四个字节,所以核心是必须要有对象。
###在什么情况下,析构函数需要是虚函数?
每个析构函数(不加 virtual) 只负责清除自己的成员。
那么当析构一个指向派生类成员的基类指针时,程序就不知道怎么办了。所以要保证运行适当的析构函数,基类中的析构函数必须为虚析构。
- 基类指针可以指向派生类的对象(多态性),如果删除该指针delete []p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。
- 如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。
所以,将析构函数声明为虚函数是十分必要的。
###多态的原理
虚函数表 :通过一块连续内存来存储虚函数的地址。这张表解决了继承、虚函数(重写)的问题。在有虚函数的对象实例中都存在一张虚函数表,虚函数表就像一张地图,指明了实际应该调用的虚函数函数。
class Base
{
public :
virtual void TestVirtual1()
{
cout << "Base::TestVirtual1()" << endl;
}
virtual void TestVirtual2()
{
cout << "Base::TestVirtual2()" << endl;
}
int _data;
};
int main()
{
cout << sizeof(Base) << endl;//12
Base b;
b._data=10;
system("pause");
return 0;
}
原理:
- 取虚表地址
- 传递this指针
- 取虚函数地址(虚表地址+偏移量)
- 调用该虚函数
如何验证图3中地址是虚函数地址???
typedef void (*PVFT)();
void PrintVFT(Base& t)
{
PVFT* pVFT = (PVFT*)(*((int*)&t));
while(*pVFT)
{
(*pVFT)();
pVFT++;
}
cout<<endl;
}
-
静态绑定与动态绑定
对象的静态类型(static type):就是它在程序中被声明时所采用的类型(或理解为类型指针或引用的字面类 型),在编译期确定 。
对象的动态类型(dynamic type):是指“目前所指对象的类型”(或理解为类型指针或引用的实际类型),在运 行期确定 。静态绑定(statically bound):又名前期绑定(eraly binding),绑定的是静态类型,所对应的函数或属性依 赖于对象的静态类型,发生在编译期 。静态绑定也称为静态多态。
动态绑定(dynamically bound):又名后期绑定(late binding),绑定的是动态类型,所对应的函数或属性依 赖于对象的动态类型,发生在运行期 。动态绑定也称为动态多态。
通常,虚函数是动态绑定,非虚函数是静态绑定,缺省参数值也是静态绑定。
###虚表&对象模型
-
单继承
- 基类虚表:按照虚函数在基类中的声明次序添加到虚函数中
- 派生类虚表:
- 将基类的虚函数表拷贝一份
- 如果派生类重写了基类的某个虚函数,将虚表中相同偏移量的虚函数替换成派生类自己的虚函数
- 将派生类新增加的虚函数按照其在类中声明的先后次序放在虚表的最后位置
函数必须是虚函数,并且是通过基类对象的指针或者引用来调用虚函数,通过查找对应类对象的虚函数找虚函数的地址
void TestVirtualFun(Base &b)
{
b.TestFun1();
b.TestFun2();
b.TestFun3();
}
基类中的函数不是虚函数或者基类中的虚函数不是通过基类对象的指针或者引用来调用的,直接调用(call 根基类对应函数的地址)
void TestVirtualFun(Base b)
{
b.TestFun1();
b.TestFun2();
b.TestFun3();
}
-
多继承虚表:
派生类自己的虚函数按照其在类中的声明次序添加到第一个基类对应虚表的最后
class B1
{
public:
virtual void TestFun1()
{
cout << "B1::TestFun1()" << endl;
}
virtual void TestFun2()
{
cout << "B1::TestFun2()" << endl;
}
public:
int _b1;
};
class B2
{
public:
virtual void TestFun3()
{
cout << "B2::TestFun3()" << endl;
}
virtual void TestFun4()
{
cout << "B2::TestFun4()" << endl;
}
public:
int _b2;
};
class C :public B1, public B2
{
public:
virtual void TestFun1()
{
cout << "C::TestFun1()" << endl;
}
virtual void TestFun4()
{
cout << "C::TestFun4()" << endl;
}
virtual void TestFun5()
{
cout << "C::TestFun5()" << endl;
}
public:
int _c;
};
//打印虚表
typedef void(*PVFT)();
void PrintVFT(B1& b,const string& str)
{
cout << str << endl;
PVFT* pVFT = (PVFT*)(*((int*)&b));
while (*pVFT)
{
(*pVFT)();
pVFT++;
}
cout << endl;
}
void PrintVFT(B2& b, const string& str)
{
cout << str << endl;
PVFT* pVFT = (PVFT*)(*((int*)&b));
while (*pVFT)
{
(*pVFT)();
pVFT++;
}
cout << endl;
}
int main()
{
cout << sizeof(C) << endl;
C c;
c._b1 = 1;
c._b2 = 2;
c._c = 3;
B1& b1 = c;
PrintVFT(b1, "B1 VFT");
B2& b2 = c;
PrintVFT(b2, "B2 VFT");
system("pause");
return 0;
}
如果继承的顺序反过来
class C :public B2, public B1
{
public:
virtual void TestFun1()
{
cout << "C::TestFun1()" << endl;
}
virtual void TestFun4()
{
cout << "C::TestFun4()" << endl;
}
virtual void TestFun5()
{
cout << "C::TestFun5()" << endl;
}
public:
int _c;
};
- 菱形继承
class B
{
public:
virtual void TestFun1()
{
cout << "B::TestFun1()" << endl;
}
virtual void TestFun2()
{
cout << "B::TestFun2()" << endl;
}
public:
int _b;
};
class C1:public B
{
public:
virtual void TestFun1()
{
cout << "C1::TestFun1()" << endl;
}
virtual void TestFun3()
{
cout << "C1::TestFun3()" << endl;
}
public:
int _c1;
};
class C2:public B
{
public:
virtual void TestFun2()
{
cout << "C2::TestFun2()" << endl;
}
virtual void TestFun4()
{
cout << "C2::TestFun4()" << endl;
}
public:
int _c2;
};
class D :public C1, public C2
{
public:
virtual void TestFun1()
{
cout << "D::TestFun1()" << endl;
}
virtual void TestFun3()
{
cout << "D::TestFun3()" << endl;
}
virtual void TestFun4()
{
cout << "D::TestFun4()" << endl;
}
virtual void TestFun5()
{
cout << "D::TestFun5()" << endl;
}
public:
int _d;
};
typedef void(*PVFT)();
void PrintVFT(C1& b, const string& str)
{
cout << str << endl;
PVFT* pVFT = (PVFT*)(*((int*)&b));
while (*pVFT)
{
(*pVFT)();
pVFT++;
}
cout << endl;
}
void PrintVFT(C2& b, const string& str)
{
cout << str << endl;
PVFT* pVFT = (PVFT*)(*((int*)&b));
while (*pVFT)
{
(*pVFT)();
pVFT++;
}
cout << endl;
}
int main()
{
cout << sizeof(D) << endl;
D d;
//虚表
C1& c1 = d;
PrintVFT(c1, "C1 VTF");
C1& c2 = d;
PrintVFT(c2, "C1 VTF");
system("pause");
return 0;
}
- 菱形虚拟继承
#include <string>
class A
{
public:
virtual void TestFun1()
{
cout << "A::TestFun1()" << endl;
}
virtual void TestFun2()
{
cout << "A::TestFun2()" << endl;
}
public:
int _a;
};
class B1 :virtual public A
{
public:
virtual void TestFun1()
{
cout << "B1::TestFun1()" << endl;
}
virtual void TestFun3()
{
cout << "B1::TestFun3()" << endl;
}
public:
int _b1;
};
class B2 :virtual public A
{
public:
virtual void TestFun2()
{
cout << "B2::TestFun2()" << endl;
}
virtual void TestFun4()
{
cout << "B2::TestFun4()" << endl;
}
public:
int _b2;
};
class C :public B1, public B2
{
public:
virtual void TestFun1()
{
cout << "C::TestFun1()" << endl;
}
virtual void TestFun3()
{
cout << "C::TestFun4()" << endl;
}
virtual void TestFun4()
{
cout << "C::TestFun4()" << endl;
}
virtual void TestFun5()
{
cout << "C::TestFun5()" << endl;
}
public:
int _c;
};
typedef void(*PVFT)();
void PrintVFT(B1& a, const string& str)
{
cout << str << endl;
PVFT* pVFT = (PVFT*)(*((int*)&a));
while (*pVFT)
{
(*pVFT)();
pVFT++;
}
cout << endl;
}
void PrintVFT(B2& a, const string& str)
{
cout << str << endl;
PVFT* pVFT = (PVFT*)(*((int*)&a));
while (*pVFT)
{
(*pVFT)();
pVFT++;
}
cout << endl;
}
int main()
{
cout << sizeof(C) << endl;
C c;
//虚表
B1& b1 = c;
PrintVFT(b1, "C1 VTF");
B1& b2 = c;
PrintVFT(b2, "C2 VTF");
system("pause");
return 0;
}