多态:当不同的对象去完成同一种行为会产生不同的状态。比如普通人买票全价,而学生买票半价。
多态条件:
1.必须通过基类的指针或者引用调用虚函数
2.被调用的函数必须是虚函数。且派生类必须对基类的的虚函数进行重写
虚函数:virtual修饰的类成员函数称为虚函数,子类可以不加virtual,父类必须加。
虚函数表:存放虚函数的地址,虚函数本质是在代码段
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数
的返回值类型,函数名字,参数列表完全相同),称子类的虚函数重写了基类的虚函数。
class Person {
public:
virtual void BuyTicket() const //虚函数
{
cout << "买票-全价" << endl;
}
};
class Student : public Person {
public:
virtual void BuyTicket() const //重写
{
cout << "买票-半价" << endl;
}
};
//多态,不同对象传递过去,调用不同函数,多态调用看指向的对象
//普通调用看当前调用者的类型
void func(const Person& p) //引用
{
p.BuyTicket();
}
int main()
{
func(Person()); //person()为匿名对象(临时对象)具有常性
func(Student());
return 0;
}
//void func(const Person* p) //指针
//{
// p->BuyTicket();
//}
//int main()
//{
// Person pp;
// func(&pp);
//
// Student st;
// func(&st);
//
// return 0;
//}
协变:返回值可以不同,但要求返回值必须是父子关系指针和引用。
class A
{};
class B : public A
{};
class Person {
public:
virtual A* BuyTicket() const {
cout << "买票-全价" << endl;
return 0;
}
};
class Student : public Person {
public:
virtual B* BuyTicket() const {
cout << "买票-半价" << endl;
return 0;
}
};
析构函数重写:析构函数同样要进行虚函数重写,类析构函数名会被处理成destructor这个统一的名字。如下场景必须使用析构函数重写,才能避免内存泄漏。
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
virtual ~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
~Student() {
cout << "~Student()" << endl;
delete[] ptr;
}
protected:
int* ptr = new int[10];
};
int main()
{
Person* p = new Person;
p->BuyTicket();
delete p;
p = new Student; //切片,父类指针可以指向子类
p->BuyTicket();
delete p; //如果析构函数没重写,这里还是根据调用者的类型,而非指向的对象,去调用基类的析构函数~Person(),导致内存泄漏。
// 这里我们期望p->destructor()是一个多态调用,根据P指向的对象去调用~Student()。
return 0;
}
override、final
不能被继承的类:使基类构造函数私有,因为派生类构造必须调用基类构造。
class A
{
public:
static A CreateObj() //加static,因为非静态成员引用必须与特定对象相对
{
return A();
}
private:
A()
{}
};
class B : public A
{};
int main()
{
A::CreateObj();
return 0;
}
重载、覆盖(重写)、隐藏(重定义)的对比
多态原理:基类指针指向父类,会在父类的虚函数表找到父类的地址,指向子类,切片以后还是父类格式(子类中的父类),去子类的虚函数表找到子类的地址。如果符合多态,运行时到指向对象的虚函数表中找调用(重写)函数的地址,从而同一个函数接收不同的类对象,出现不同的结果。
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
int _a = 1;
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
int _b = 1;
};
void Func(Person& p)
{
// 符合多态,运行时到指向对象的虚函数表中找调用函数的地址
p.BuyTicket();
}
int main()
{
Person Mike;
Func(Mike);
Student Johnson;
Func(Johnson);
return 0;
}
public继承是一种is - a的关系。也就是说每个派生类对象都是一个基类对象。
组合是一种has - a的关系。假设b组合了a,则每个b对象中都有一个a对象。