一、多态
1、 概念:同一事物表现出的多种形态,同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
2、 举例子:
#include<windows.h>
class WashRoom
{
public:
void GoToManWashRoom()
{
cout << "Man ---> Please Left" << endl;
}
void GoToWomanWashRoom()
{
cout << "Woman ------> Please Right" << endl;
}
};
class Person
{
public:
virtual void GoToWashRoom(WashRoom& wc) = 0;
};
class Man :public Person
{
public:
virtual void GoToWashRoom(WashRoom& wc)
{
wc.GoToManWashRoom();
}
};
class Woman :public Person
{
public:
virtual void GoToWashRoom(WashRoom& wc)
{
wc.GoToWomanWashRoom();
}
};
void TestWashRoom()
{
WashRoom wc;
Person* p = NULL;
for (int i = 0; i <10; ++i)
{
if ((rand()) & 0x01)
p = new Man;
else
p = new Woman;
p->GoToWashRoom(wc);
delete p;
Sleep(1000);
}
}
int main()
{
TestWashRoom();
return 0;
}
3、 多态的分类
(1)静态多态:编译器在编译期间来确定程序的行为(确定具体调用哪个函数)
A:函数重载
B:泛型编程
(2)动态多态:程序运行时来确定程序的行为(确定具体调用哪个函数)
4、 动态多态实现条件
(1) 基类中必须包含虚函数,在派生类中必须对基类的虚函数进行重写
(2) 必须通过基类指针或引用调用虚函数
//1、基类中必须包含虚函数,且派生类一定要对基类中的虚函数进行重写(原型一致)
class Base
{
public:
virtual void TestFunc1()
{
cout << "Base::TestFunc1()" << endl;
}
virtual void TestFunc2()
{
cout << "Base::TestFunc2()" << endl;
}
void TestFunc3()//基类无虚拟关键字,派生类有
{
cout << "Base::TestFunc3()" << endl;
}
virtual void TestFunc4(int)//派生类中是char,参数类型不一样
{
cout << "Base::TestFunc4()" << endl;
}
/*virtual int TestFunc5()
{
cout << "Base::TestFunc5()" << endl;
return 0;
}*/
//协变—基类(派生类)虚函数返回基类(派生类)指针或引用
virtual Base* TestFunc5()
{
cout << "Base::TestFunc5()" << endl;
return this;
}
virtual Base& TestFunc6()
{
cout << "Base::TestFunc6()" << endl;
return *this;
}
~Base()//析构函数
{
cout << "Base::~Base()" << endl;
}
};
class Derived : public Base
{
public:
virtual void TestFunc1()
{
cout << "Derived::TestFunc1()" << endl;
}
void TestFunc2()//派生类无虚拟关键字,基类有
{
cout << "Derived::TestFunc2()" << endl;
}
virtual void TestFunc3()
{
cout << "Derived::TestFunc3()" << endl;
}
//没有构成重写(参数类型不同)
virtual void TestFunc4(char)//基类中是int 参数类型不同
{
cout << "Derived::TestFunc4()" << endl;
}
//virtual char TestFunc5()//不能重写,返回类型不同
//{
// cout << "Derived::TestFunc5()" << endl;
// return 0;
//}
//协变—基类(派生类)虚函数返回基类(派生类)指针或引用
virtual Derived* TestFunc5()
{
cout << "Derived::TestFunc5()" << endl;
return this;
}
virtual Derived& TestFunc6()
{
cout << "Derived::TestFunc6()" << endl;
return *this;
}
virtual ~Derived()
{
cout << "Derived::~Derived()" << endl;
}
};
//2、通过基类对象的指针或引用调用虚函数
void TestVirtualFunc(Base& b)
{
b.TestFunc1();
b.TestFunc2();
b.TestFunc3();
b.TestFunc4(10);
b.TestFunc4('a');
}
int main()
{
Base* pb = new Derived;//基类的指针,指向派生类的对象
delete pb;//如果调用基类里的析构,则没有重写;反之
Base b;
Derived d;
//如果引用的是基类的对象,调用基类里对应的虚函数
//如果引用的是派生类的对象,则调用派生类里对应的虚函数
TestVirtualFunc(b);
TestVirtualFunc(d);
return 0;
}
返回值不同不能重写
5、 重写
(1)基类中的函数必须为虚函数
(2)派生类中重写的虚函数必须与基类的虚函数类型保持一致(返回值、函数名字(参数列表))
例外:
协变:基类中虚函数返回基类对象的指针或引用,派生类中虚函数返回派生类对象的指针或引用——返回值类型不同
析构函数——基类和派生类中函数的名字不同
(3)基类中虚函数和派生类虚函数的访问限定符可以不同
——重载、重写、重定义
6、代码实现多态
class Base
{
public:
virtual void TestFunc1()
{
cout << "Base::TestFunc1()" << endl;
}
virtual void TestFunc2()
{
cout << "Base::TestFunc2()" << endl;
}
virtual void TestFunc3()
{
cout << "Base::TestFunc3()" << endl;
}
int _b;
};
class Derived :public Base
{
public:
virtual void TestFunc2()
{
cout << "Derived::TestFunc2()" << endl;
}
virtual void TestFunc3()
{
cout << "Derived::TestFunc3()" << endl;
}
int _d;
};
void TestVirtualFunc(Base& b)
{
b. TestFunc1();
b. TestFunc2();
}
int main()
{
Base b;
Derived d;
TestVirtualFunc(b);
TestVirtualFunc(d);
return 0;
}
7、动态多态调用原理
多态的条件已经完全满足
1)从对象的前4个字节中取虚表地址
2) 传参(虚函数形参+this)
3) 从虚表中取虚函数地址
4) 调用该虚函数
二、抽象类
1、概念
在成员函数(必须为虚函数)的形参列表后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
class WC;
class Person
{
public:
virtual void GoToWC(WC& c) = 0;//纯虚函数
};
class Man :public Person
{
public:
virtual void GoToWC(WC& c)
{
//
}
};
int main()
{
Person* p;
Man m;
return 0;
}
2、总结
(1)派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同(协变除外)
(2)基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性
(3)只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数
(4)如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加
(5)构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆(?)
(6)不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为
(7)最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)
(8)虚表是所有类对象实例共用的
class Base
{
public:
virtual void TestFunc1()
{
cout << "Base::TestFunc1()" << endl;
}
int _b;
};
int main()
{
cout << sizeof(Base) << endl;//8
Base b;
b._b = 1;
return 0;
}