多态:向不同的对象发送相同的消息,而产生不同的动作。
静态类型:对象(引用,指针)声明时的类型。
动态类型:当前的对象实际指向的类型。
在继承关系中,指针或引用的静态类型和动态类型是可以不同的。
class A{};
class B:public A{};
B mb;
A *pa = &mb;
//此时,pa的静态类型是A*,动态类型是B.
在继承关系中,对象(引用,指针除外)的静态类型和动态类型总是相同的。子类对象向父类对象赋值,会发生切除,其实就是抽出子类的父类子对象对父类进行赋值。
绑定:确定调用哪一个具体对象的行为。
静态绑定:绑定到对象的静态对象的行为。
动态绑定:在程序执行时(运行时)绑定到对象的动态类型的行为。
简述多态和虚函数:虽然在继承关系中父类指针或引用可以实现动态绑定,但是由于父类和子类的作用于不同,加之子类到父类的自动转换,导致父类指针或引用只能静态绑定,不能访问子类中的成员(鬼刀一开看不见),那么,c++实现多态的本质就是为了实现“使用指向子类对象的父类引用(或指针)能够访问子类的成员函数”。
实现多态条件:
在c++中只有虚函数才有可能实现动态绑定,非虚函数只能实现静态绑定。
再之,动态绑定的前提是指针或引用。
多态类:包含虚函数的类称之为多态类。
要重写虚函数,必须返回值类型,存储说明符,限定修饰符等完全一致,当然返回值不同有一个特例。
#include<iostream>
class A {
public:
virtual void func() {
std::cout << "A" << std::endl;
}
virtual void func(int a) {
std::cout << "A" << std::endl;
}
};
class B : public A {
public:
virtual void func(int a) {
std::cout << "B" << std::endl;
}
};
class C: public B {
public:
static void func(int c) {
std::cout << "C" << std::endl;
}
};
int main() {
A a;
C c;
A* pa = &c;
B* pb = &c;
pa->func();
pb->func(20);
return 0;
}
在子类中将同名虚函数定义为static,const,这不是重写虚函数,他们也不是虚函数,这不会改变继承而来的虚表。
关于返回值特例:
也称之为返回类型协变。
类型协变也和继承中子类指针或引用能自动转换为父类指针或引用相关。
定义难得打,借鉴一个哇
虚函数的默认实参:因为虚函数的形参是静态绑定的,其默认实参类型是由其形参的静态类型决定而不是动态类型,所以,无论子类的虚函数是否有默认实参,都会被父类的虚函数的默认实参覆盖。即子类的虚函数的默认实参不起作用。
有虚函数的名字解析:
1. 首先确定调用虚函数的对象的静态类型。
2. 然后再在该静态类型的类中查找该函数,若未能找到,则在其直接父类中查找该名字,并重复此过程。(名字的查找和访问级别无关哟)
3. 若找到该名字,则按照常规检查该调用是否合法。
4. 若调用合法,则查看该函数是否是否是虚函数且是否是通过指针或引用的调用,若是,则编译器会生成代码并根据对象的动态类型确定调用
那一版本的虚函数,调用虚函数时还应注意其层次关系,即从指针所指向的动态类型开始向其父类查找。
虚析构函数:
虚析构函数存在的意义,其意义在于如果B是A的子类, A *pa = new B(); delete pa, 会正确的调用析构函数~B();
虚析构函数会自动继承,父类析构函数定义为虚函数,子类的析构函数自动定义为虚函数
析构函数也可以声明称纯虚函数,但纯虚析构函数必须定义,不然在子类析构调用父类的纯虚析构函数时可能发生链接错误。