多态(动态联编)不包括重载(静态联编)!!!
多态/虚函数就想动态联编,基类指针指子类。
虚继承就想菱形的继承结构。
虚函数:因为基类指针或引用可以指向派生类,所以不能在编译期就确定调用了哪个函数。意思是不看指针或者引用声明的时候是什么类型,而要看实际指向了什么类型。
基类中声明为虚函数的,派生类中不显式声明也会自动变为虚函数。
class Human1{
public:
virtual void show(){cout<<"Human1"<<endl;}
};
class Human2{
public:
virtual void show(){cout<<"Human2"<<endl;}
};
class Human3:public Human1,public Human2{
public:
virtual void show(){cout<<"Human3"<<endl;}
};
int main() {
Human3 c;
Human1 &p1=c;//虽然声明为Human1类型,但是实际指向了Human3类型,所以调用的是Human3的函数
p1.show();//Human3
Human2 &p2=c;
p2.show();//Human3
Human3 &p3=c;
p3.show();//Human3
return 0;
}
静态联编和动态联编的区别
class Human1{
public:
void show(){cout<<"Human1"<<endl;}
//f是绑定到show的,show不是虚函数,静态联编到到是Human1的show
void f(){show();}
//如果在Human1的show是虚函数,那么运行的结果就是Human4了,show会动态绑定到真正被调用的对象Human4的对象的show上
//void virtual f(){show();}
};
class Human4:public Human1{
public:
void show(){cout<<"Human4"<<endl;}
};
int main() {
Human4 p;
p.f();//Human1
p.Human4::f();//Human1
return 0;
}
构造函数/析构函数
//构造函数不可以是虚函数,因为构造时信息必须是确定的
//析构函数可以是虚函数。
//如有指向动态分配的内存的数据成员时,基类指针指向派生类,此时析构函数由于静态联编已经绑了基类的析构函数,只调用了基类的析构函数,没调用派生类的。
//如果析构函数是虚函数,就能正确的先调用派生类的析构,再调用基类的析构。
重载/ 覆盖(重写override)/ 遮蔽(隐藏)
//重载:同一个命名空间中,函数名相同函数签名不同。编译期绑定
//遮蔽(隐藏)
//编译期绑定
//派生类的函数屏蔽了与其同名的基类函数,注意只要同名函数,不管参数列表是否相同,基类函数都会被隐藏。
//绑定到声明的类型的函数上(基类指针指派生类,会绑定到基类的函数上)
//用域作用符指示用哪个
//覆盖(遮蔽):
//基类一定virtual虚函数,多态。函数名和函数签名相同。运行期绑定
//派生类中和基类中虚函数重名的函数,会把基类中的虚函数覆盖掉
//如果基类中不是虚函数,那么就编译期绑定。
//(虚函数也能遮蔽(编译期绑定),而不多态(运行期绑定),只要虚函数函数签名不同)
抽象类
//抽象类中至少一个纯虚函数
//纯虚函数在虚函数后边加=0
virtual void show()=0;
//抽象类不能声明对象,但可以声明指针或引用,这时所指的派生类必须重载纯虚函数。
//从抽象类派生的新类,必须重新定义抽象类中的每一个纯虚函数
向上转型/向下转型
//向上转型:子类型转换为基类型,dynamic_cast可以成功
(本来基类指针和引用可以指向派生类)
class A{}
class B:public A{}
A *p = dynamic_cast<A*>(new B)//正确,向上转型
B *q = dynamic_cast<B*>(new A)//错误,是向下转型