多态
多态的含义:不同对象,行为不同。
一.多态的使用
三个要点:
1.要在继承关系中才能使用
2.必须是虚函数
3.调用函数的类型必须是引用/指针
虚函数的覆写:
1.必须是虚函数
2.子类中有相同的函数:与父类函数完全相同(函数名、参数、返回值)
例外:1)协变——返回值类型构成继承关系的指针/引用(也可以)
2)析构函数——基类析构函数定义成虚函数时,子类析构函数只要定义无论是否加virtual关键字都构成重写。
例:以买票为例,学生半票,老师买全票
class Person {
public:
//声明成纯虚函数,此时该类就是一个抽象类,
//只是相当于一个接口,不实现,接口的实现在其子类内部实现
//virtual void BuyTicket() = 0;
virtual void BuyTicket() {
cout << "全票!!!" << endl;
}
virtual ~Person(){
cout << "~Person()" << endl;
}
};
class Student :public Person {
public:
virtual void BuyTicket() {
cout << "半票!" << endl;
}
virtual ~Student() {
cout << "~Student()" << endl;
}
};
class Teacher :public Person {
public:
virtual void BuyTicket() {
cout << "全票!" << endl;
}
virtual ~Teacher() {
cout << "~Teacher()" << endl;
}
};
void Buy(Person& p) {
p.BuyTicket();
}
void test1() {
Teacher t;
Student s;
Buy(t);
Buy(s);
}
纯虚函数:定义函数的形式为 :virtual 返回值类型 函数名()= 0;
当一个函数被定义成纯虚函数以后,其实现就在子类中。该函数就只是一个接口而已。
抽象类:存在纯虚函数的类
抽象类的存在与纯虚函数类似,此时该类就成为了一个接口类,各种接口的实现都在其子类中。
抽象类不能实例化对象。
final / override关键字
final:加在函数名后,使该函数不能被子类重写。——实现继承
例:
此时,在子类中覆写该函数时,就会编译报错。
override:加在函数名后,使得该函数必须被子类重写 ——接口继承
例:
此时,也会编译报错:
二.多态的实现原理
class Person {
public:
virtual void BuyTicket() {
cout << "全票!!!" << endl;
}
virtual void fun1(){}
void fun2(){}
virtual ~Person(){
cout << "~Person()" << endl;
}
};
class Student :public Person {
public:
virtual void BuyTicket() {
cout << "半票!" << endl;
}
virtual void fun1(){}
virtual ~Student() {
cout << "~Student()" << endl;
}
};
在该继承关系中,对象模型如图:
我们发现父类与子类都有一个虚表,但是虚表中的内容却是不尽相同的。
基类存放的是父类的虚函数的地址——虚函数指针;子类存放的是子类的虚函数指针。
至于它的调用过程,我们可以这样来处理:
void Buy(Person& p) {
p.BuyTicket();
}
typedef void(*VFPTR)();
void test() {
Person p;
Student s;
VFPTR p1 = *(VFPTR*)*(int*)&p;
//&p 取对象的地址
//(int*)&p 将对象的地址转换成int*类型,
//*(int*)&p 只读前四个字节,拿到虚函数表指针
//(VFPTR*)*(int*)&p 将虚函数表指针转换成函数指针类型
//*(VFPTR*)*(int*)&p 读出虚函数表的第一个元素,即第一个函数指针
p1();
}
函数指针执行的函数的内容就是该函数指针所指的对象的虚表中的函数。
总结:
多态的实现过程:
1.函数参数为对象的引用/指针。
2.访问对象的前4/8个字节的内容,获取虚表首地址——虚表指针
3.通过虚表指针获取虚表,调该对象对应的函数。
所以我们可以发现,多态注重的是对象而非类型。
未完待续……