一、对象的类型
对象的类型:(1)静态类型:对象声明时的类型,是在编译时确定的;
(2)动态类型:目前所指的对象的类型,是在运行时确定的。
二、多态
(1)静态多态:函数重载和泛型编程
(2)动态多态:虚函数
三、多态类型
1、静态多态:编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
int Add(int left, int right)
{
return left + right;
}
float Add(float left, float right)
{
return left + right;
}
int main()
{
cout << Add(10, 20) << endl;
cout << Add(13.12, 24.45) << endl;
return 0;
}
class Animal
{
public:
void shout()
{
cout << "发出叫声" << endl;
}
};
class Dog :public Animal
{
public:
void shout()
{
cout << "汪汪" << endl;
}
};
class Cat :public Animal
{
public:
void shout()
{
cout << "喵喵" << endl;
}
};
class Bird :public Animal
{
public:
void shout()
{
cout << "叽喳" << endl;
}
};
template <typename T>
void animalShout(T & t)
{
t.shout();
}
int main()
{
Animal anim;
Dog dog;
Cat cat;
Bird bird;
animalShout(anim);
animalShout(dog);
animalShout(cat);
animalShout(bird);
return 0;
}
在编译之前,函数模板中t.shout() 调用的是哪一个接口并不确定。在编译期间,编译器推断出模板参数,因此确定调用的shuot是哪个具体的类型的接口。不同的推断结果调用不同的函数,这就是编译器多态。这类似于重载函数在编译器进行推导,以确定哪一个函数被调用
2、 动态多态:在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法
使用virtual关键字修饰函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。
#include<iostream>
#include<windows.h>
using namespace std;
class CWashRoom
{
public:
void GoToManWashRoom()
{
cout << "Man--->Please Left" << endl;
}
void GoToWomanWashRoom()
{
cout << "Woman--->Please Right" << endl;
}
};
class CPerson
{
public:
virtual void GoToWashRoom(CWashRoom & _washRoom) = 0;
};
class CMan :public CPerson
{
public:
virtual void GoToWashRoom(CWashRoom & _washRoom)
{
_washRoom.GoToManWashRoom();
}
};
class CWoman :public CPerson
{
public:
virtual void GoToWashRoom(CWashRoom & _washRoom)
{
_washRoom.GoToWomanWashRoom();
}
};
void FunTest()
{
CWashRoom washRoom;
for (int iIdx = 1; iIdx <= 10; ++iIdx)
{
CPerson* pPerson;
int iPerson = rand() % iIdx;
if (iPerson & 0x01)
{
pPerson = new CMan;
}
else
{
pPerson = new CWoman;
}
pPerson->GoToWashRoom(washRoom);
delete pPerson;
pPerson = NULL;
Sleep(1000);
}
}
int main()
{
FunTest();
getchar();
return 0;
}
动态绑定条件:1、必须是虚函数
2、通过基类类型的引用或者指针调用
class Animal
{
public:
virtual void shout() = 0;
};
class Dog :public Animal
{
public:
virtual void shout()
{
cout << "汪汪" << endl;
}
};
class Cat :public Animal
{
public:
virtual void shout()
{
cout << "喵喵" << endl;
}
};
class Bird :public Animal
{
public:
virtual void shout()
{
cout << "叽喳" << endl;
}
};
int main()
{
Animal * anim1 = new Dog;
Animal * anim2 = new Cat;
Animal * anim3 = new Bird;
anim1->shout();
anim2->shout();
anim3->shout();
return 0;
}
运行时多态的实现依赖于虚函数机制。当某个类声明了虚函数时,编译器将为该类对象安插一个虚函数表指针,并为该类设置一张唯一的虚函数表,虚函数表中存放的是该类虚函数地址。运行期间通过虚函数表指针与虚函数去确定该类虚函数的真正实现
四、运行期多态和编译期多态的优缺点
1、运行期多态
优点:(1)是设计中的重要特性,对客观世界直觉认知
(2)能够处理同一个继承体系下的异质类集合
缺点:(1)运行期间进行虚函数绑定,提高了程序的运行开销
(2)庞大的类继承层次,对接口的修改易影响类继承层次
(3)虚表指针增加了对象体积,类也多了一张虚函数表,造成了资源消耗
2、编译期多态
优点:(1)它带来了泛型编程的概念,使得c++拥有泛型编程和STL这样的强大武器
(2)在编译器完成多态,提高运行期效率
(3)具有很强的适配性与松耦合性,对于特殊类型可由模板特化、全特化处理
缺点:(1)程序可读性降低,代码调试带来困难
(2)无法实现模板的分离编译,当工程很大时,编译时间不可忽略
(3)无法处理异质对象集合
五:继承体系同名成员函数的关系
1、重载
(1)在同一作用域;(2)函数名相同,参数不同(参数类型不同或参数个数不同);(3)返回值可以相同,也可以不同
2、重写(覆盖)
(1)不在同一作用域(分别在基类和派生类)
(2)函数名相同,参数相同,返回值相同(协变例外)
(3)基类函数必须有virtual关键字
(4)访问修饰符可以不同
3、重定义(隐藏)
(1)在不同作用域中(分别在基类和派生类)
(2)函数名相同
(3)在基类和派生类中只要不构成重写就是重定义
六、纯虚函数
在成员函数的形参后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
class Person
{
virtual void Display() = 0; // 纯虚函数
protected:
string _name; // 姓名
};
class Student : public Person
{};
总结:
1、派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)
2、基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。
3、只有类的成员函数才能定义为虚函数,静态成员函数不能定义为虚函数。
4、如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。
5、构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆
6、不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为。
7、最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)
8、虚表是所有类对象实例共用的