上期,我们已经说完继承了,那么多态现在就闪亮登场啦。
目录
多态的概念
概念
不同的对象做同一件事情效果不一样。例如:买票。学生买票半价,军人买票免排队且特殊票,普通人买票全价票。
多态要满足的条件
(1).父子类的虚函数重写。
函数前面加virtual就是虚函数,且只能是类成员函数,全局函数会报错。虚函数的重写,重写的是实现。父类有虚函数是,子继承了父类后,要完成虚函数的重写。(虚函数的重写只是实现的重写)
虚函数重写要求三同:函数名、参数、返回值相同。
(2).父类的指针或者引用去调用虚函数。
class person
{
public:
virtual void Buyticket()
{
cout << "全价票" << endl;
}
private:
int id;
};
class Student :public person
{
public:
virtual void Buyticket()
{
cout << "半价票" << endl;
}
private:
int id;
};
void Func(person& p)
{
p.Buyticket();
}
int main()
{
person p;
Student s;
Func(p);
Func(s);
return 0;
}
虚函数重写的两个例外
协变
重写要求三同:函数名,参数,返回值,但是协变又允许返回类型不同。返回值不同要求:父子关系的指针和引用。别的类的也可以,自己类的也可。
(1).协变返回类型不同,一个是父类,一个子类。
(2).返回类型必须是父子关系。
析构函数
使用的时候期望指向谁调用谁,就可以virtual+析构函数,进行析构多态用法。
看下图,我们利用多态new了student的对象,我的夙愿是去析构student的对象,而不是person的,这个时候我们就绪要将析构函数加上virtual。
加上之后发现子类果然正常析构了。建议析构函数定义为虚函数,防止内存泄漏。
问题:为什么析构函数会有这种情况?
答:因为历史原因,析构函数会被处理成destrucor,就会导致和父类同名。就会导致不满足多态,导致去调用父类。
final和override
final
final是指父类不能被继承如果你不希望你的父类被继承就可以用final修饰父类。
不能被继承的方法还有一个:私有构造函数。
override
override可以检测子类是否完成父类的重写。加到子类的重写虚函数后。
重写、重载、隐藏
重载
重载指在同一作用域,函数名相同,参数类型不同的函数。
重写
重写是指在父子类中,返回类型,函数名,参数名相同(协变除外),必须是虚函数。
隐藏
隐藏是指父子类中,函数名和变量相同**。基类和派生类同名函数不构成重写**,就是隐藏。
虚函数表指针
虚函数这么多,况且我们有重写等等操作,编译器是怎么辨别的呢?其实编译器是通过虚表指针来找虚函数的。
只要类中有虚函数,那么他会多一个虚表指针。虚表指针是一个函数指针数组,里面装的都是虚函数指针。
会发现这两张表不是同一张,因为他们的地址不一样,所以是两张表。每个对象都会有属于自己的续表。但是同类型对象的共用一张虚表。
如果满足多态,那么就会指向对象的虚表中找对应虚函数进行调用,指向谁的调用谁的虚函数,运行去虚表中找对应的虚函数调用,指向父类调用父类的虚函数,指向子类调用子类的虚函数。
打印虚表
当我子类有属于自己的成员函数时,他是如何存储的呢?你会发现有我们重写的func和继承父类的func2,但是自己func3的呢?但是我们查内存的时候是有一个类似func3的函数指针,为了证明我们可以打印虚表试试看。
class person
{
public:
virtual void func()
{
cout << "person::func" << _name<<endl;
}
virtual void func2()
{
cout << "person::func2()" << _name << endl;
}
private:
string _name="xxx";
};
class student: public person
{
public:
virtual void func()
{
cout << "student:" << _name << endl;
}
virtual void func3()
{
cout << "student:: func3()" << endl;
}
private:
int _id;
string _name;
};
int main()
{
person* p= new person;
person* s= new student;
return 0;
}
经过我们验证,我们发现子类自己的函数是存在虚函数表中的,知识监视窗口并没有显示而已。
抽象类
纯虚函数
纯虚函数指在函数结尾加“=0”就是纯虚函数,有纯虚函数的类就是抽象类。
注意:抽象类是实例化不处对象的。并且如果子类有没有对父类的纯虚函数进行重写也是不能实例化出对象的。
作用
抽象类其实间接强制子类完成虚函数的重写。
区分虚表指针和虚拟继承中的表
虚表指针是指存有虚函数的表的指针。
虚拟继承中的表是为了解决二义性和冗余问题,存的当前位置距离虚基类部分的偏移量。
多继承
我们上述说的都是单继承,现在讲讲多继承。
多继承且不是虚拟继承
继承的派生类如果两个基类都是同样的函数,重写的话,是重写两个表的。派生类独有的成员函数函数指针会存在第一个继承的虚表中。
下述代码会发现d有两张表,一张表是B一张表是C,d重写了B,但没有重写C,但是D本身在哪呢?我们通过看内存地址发现B中有两个指针,所以D中的成员函数存在了B的虚表中。
class B
{
public:
virtual void func2()
{
cout << "B:: func2()" << endl;
}
};
class C
{
public:
virtual void func3()
{
cout << "C:: func3()" << endl;
}
};
class D :public B, public C
{
public:
virtual void func2()
{
cout << "D:: func2()" << endl;
}
virtual void func4()
{
cout << "D:: func1()" << endl;
}
};
int main()
{
B b;
C c;
D d;
return 0;
}
多继承但是虚拟继承
菱形虚拟继承会导致既有虚表又有虚拟继承表。由内存图,我们可以看出来第一个指针是虚表指针,第二个指针是偏移量指针。
B的虚函数和C的虚函数能不能放在A虚表中放?不能,因为虚拟继承虚基类A是共享的。菱形虚拟继承基类只有一份,所以只初始化一次。先继承的先声明。