目录
多态初阶
多态是指同一个行为或方法在不同情况下具有不同的表现形式或形态的能力。在面向对象编程中,多态性是指通过同一接口,实现不同类的对象对同一消息的响应。它可以分为静态多态和动态多态。静态多态在编译时就已经确定好了,动态多态在运行时才会确定。常见的静态多态是函数重载,动态多态则是通过对父类虚函数的重写,通过虚表,达到传入谁的指针就调用谁的函数的目的。多态的应用场景非常广泛,可以在修改或增加功能的时候,可以较少的改动代码,提高代码的可维护性和可扩展性。
多态的例子:买票窗口的成人票、儿童票、军人票、学生票。互联网中的新用户和老用户,大数据杀熟?
多态相当于你对着人就说人话,对着鬼就说鬼话!
多态的条件
语法规定:
1.虚函数重写
虚函数 在 函数类型名 前加上 virtual 关键字
虚函数的重写:重写函数内部内容,重写实现
派生类中可以不加virtual,而基类必须加。因为基类定义完虚函数后,派生类继承时也继承了virtual的这个函数,实现多态需要重写,可以标明也可以不标明。但是建议都加上
2.通过父类指针或者引用来进行调用虚函数
这里因为子类可以当做特殊的父类进入,但是多态能够区分并匹配到最合适的虚函数!指针(引用)指向父类进入父类,指向子类进入子类。
一个简单的多态代码!
class Person{
public:
virtual void buyTicket() { cout<< "Person买票 需要购买全票" <<endl; }
};
class Student{
public:
virtual void buyTicket() { cout<< "Student买票 需要购买半票" <<endl; }
};
// 传入参数需要为指针或者引用
void Func(Person& p){ // 传入的为Person这个引用根据实际可指代Student和非Student的Person
p.buyTicket();
}
// 测试函数
void test1(){
Person p1;
Student s1;
Func(p1);
Func(p2);
}
关于派生类不加virtual实现重写的场景!
class Person {
public:
Person() {
cout << "Person()" << endl;
}
~Person() {
cout << "~Person()" << endl;
}
};
class Student :public Person {
public:
Student() {
cout << "Student()" << endl;
}
~Student() {
cout << "~Student()" << endl;
}
};
我们再观察这个场景,原本的p指代的是Person对象,如今变为了Student对象,本质上就是想实现多态的 见人说人话,见鬼说鬼话,那么我们让析构函数作为虚函数
class Person {
public:
Person() {
cout << "Person()" << endl;
}
virtual ~Person() {
cout << "~Person()" << endl;
}
};
class Student :public Person {
public:
Student() {
cout << "Student()" << endl;
}
~Student() {
cout << "~Student()" << endl;
}
};
// 测试派生类省略virtual的场景
void test2(){
Person* p = new Person;
// 本质上调用 p->destructor() 和 operator delete;
delete p;
p = new Student;
// 本质上调用 p->destructor() 和 operator delete;
delete p;
}
所以派生类虚函数不加virtual,编译器通过继承关系,默认~Student()也为虚函数,这里我们也可以知道基类和派生类的析构函数也符合重写关系
重载、重写、重定义
在类与对象中
重载一般出现在同一个类中的同名函数,参数不同 ,比如若干个构造函数
重写一般出现在基类和派生类中的同名函数,且为虚函数,函数名/参数/返回值都需要一致,协变即为返回值分别为基类对象和子类对象
重定义就是在基类和派生类的同名函数,但是不为虚函数,并且函数参数和返回值不做要求
下面是3个例子
初步
稍微修改一下传入参数!
final、override关键字
final是用修饰基类虚函数,使其无法被重写!
override用于检查派生类的重写!
这里因为参数类型不同所以无法视为是:重写了派生类虚函数
抽象类
// 抽象类 比如抽象类为动物,派生类:人,狗,猫等等实例化为对象,动物就不需要实例化为对象
class Car {
public:
// 纯虚函数
// 简洁强制去派生类重写
// 抽象类Car
virtual void Brand() = 0;
};
class Audi:public Car {
public:
void Brand() { cout << "Car Brand is Audi" << endl; }
};
class Benz :public Car {
public:
void Brand() { cout << "Car Brand is Benz" << endl; }
};
// 实现外部调用
void Func(Car* c) {
c->Brand();
}
// 测试抽象类
void test4() {
// Car c;
// 实例化对象调用
Audi a6L;
a6L.Brand();
// 通过匿名函数实现
Func(new Benz);
Func(new Audi);
// Func(new Car);
}
ps:普通函数的继承是实现继承,子类也可以用父类的。虚函数的继承是接口继承 ,目的为了重写,达成多态,实现不同子类之间形成多接口来面对不同的对象与场景
多态的原理
虚函数表
任何一个类中只要有虚函数,那么成员变量中默认会多出一个指针,用于指向虚函数表。一个虚函数的类中都至少有一个指向 指向虚函数表的指针。而虚函数表放的是 函数指针的 数组。
同一个类的对象,共用一张虚表,不同的类不同虚表。
虚函数表存放在代码段中,他跟常量的区域基本一致。
所有的虚函数都会放在虚表里
虚表的打印
多态的原理
父类和子类有各自的虚表
class Person {
public:
virtual void buyTicket() { cout << "Person买票 需要购买全票" << endl; }
virtual void func(){}
};
class Student :public Person {
public:
virtual void buyTicket() { cout << "Student买票 需要购买半票" << endl; }
};
//
void Func(Person* ptr) {
ptr->buyTicket();
}
void test1() {
Person p1;
Student s1;
Func(&p1);
Func(&s1);
}
如上面代码,仅仅重写了buyTicket函数,我们知道重写其实是覆盖,观察对象p1的_vfptr中第一个函数指针的地址跟p2的_vfptr不同,也就是buyTicket的函数指针不同,会导致进入不同的函数体,恰好符合了重写的目的,形成了覆盖
那么讲到这里我们大概知道了多态的原理,通过虚函数这个语法引申出虚函数表的概念,存放函数指针的数组,通过不同的对象在对应重写后的不同虚函数接口(也就是通过同一个函数名但是其函数指针不同)进入,实现多态
又因为子类对象可以通过切片成为父类,所以传入应该为父类的指针与引用