多态概念
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态
构成多态的条件
那么在继承中要构成多态还有两个条件:
- 必须通过基类的指针或者引用调用虚函数
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。
什么是虚函数?
虚函数:即被virtual修饰的类成员函数称为虚函数。
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl;}
};
什么是重写?
构成重写的条件
- 基类和派生类的函数都是虚函数
- 函数名、参数、返回值要求全部一样。(区别重载、重定义的重要区别)
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
虚函数重写的例外
协变
派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变
class A{};
class B : public A {};
class Person {
public:
virtual A* f() {return new A;}
};
class Student : public Person {
public:
virtual B* f() {return new B;}
}
析构函数的重写
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。
建议将基类的析构函数都加上virtual,如果不加,特殊情况下,可能会内存泄漏。
class Person {
public:
// 各种开后门特例,本质都是为了析构函数构成重写
// 因为如果不构成重写,特殊情况下,可能会存在内存泄露
virtual ~Person() { cout << "~Person()" << endl; }
};
class Student : public Person {
public:
~Student() { cout << "~Student()" << endl; }
};
int main()
{
//Person p;
//Student s;
Person* p1 = new Person;
delete p1;
Person* p2 = new Student;
delete p2;
return 0;
}
只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。
构成多态的原理
先来看虚函数类中是怎样的表现形式?
虚函数表何时生成?存放在哪里?
单继承不重写时,单继承且重写(覆盖)
多态的原理:
还记得多态的条件吗?
用基类的指针或引用去调用。为什么呢?
- 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
- 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。
多继承下的多态
多继承下,类的虚表指针就作为类成员,会被继承下去。
如果派生类有没重写的虚函数,vs下会把这个未重写虚函数指针放在派生类自己,第一个声明的基类的虚表下。
面试难题
面试题1:以下程序输出什么?
class A
{
public:
A() :m_iVal(0){ test(); }
virtual void func() { std::cout << m_iVal << ' '; }
void test(){ func(); }
public:
int m_iVal;
};
class B : public A
{
public:
B(){ test(); }
virtual void func()
{
++m_iVal;
std::cout << m_iVal << ' ';
}
};
int main(int argc, char* argv[])
{
A*p = new B;
p->test();
return 0;
}
分析:new B时先调用父类A的构造函数,执行test()函数,在调用func()函数,由于此时还处于对象构造阶段,多态机制还没有生效,所以,此时执行的func函数为父类的func函数,打印0,构造完父类后执行子类构造函数,又调用test函数,然后又执行func(),由于父类已经构造完毕,虚表已经生成,func满足多态的条件,所以调用子类的func函数,对成员m_iVal加1,进行打印,所以打印1, 最终通过父类指针p->test(),也是执行子类的func,所以会增加m_iVal的值,最终打印2, 所以答案为 0 1 2。
面试题2:以下程序输出什么?
class A
{
public:
virtual void func(int val = 1){ std::cout << "A->" << val << std::endl; }
virtual void test(){ func(); }
};
class B : public A
{
public:
void func(int val = 0){ std::cout << "B->" << val << std::endl; }
};
int main(int argc, char* argv[])
{
B*p = new B;
p->test();
return 0;
}
答案:B->1。
是不是特别奇怪?
分析: