多态:一种调用语句有多种表现形态
错误示例
class parent{
public:
parent(int a){
this->a=a;
cout<<"parent a:"<<a<<endl;
}
void print(){
cout<<"hello"<<endl;
}
private:
int a;
};
class child: public parent{
public:
child(int b){
this->b=b;
cout<<"child b:"<<b<<endl;
}
void print(){
cout<<"hello"<<endl;
}
private:
int b;
};
int main(){
parent p(10);
child c(20);
parent *base=NULL;
base=&p;
base->print(); 执行parent的成员函数
base=&c;
base->print(); 执行parent的成员函数
parent &base=p;
base.print(); 执行parent的成员函数
parent &base=c;
base.print(); 执行parent的成员函数
不管是指针还是引用做函数参数,都执行parent的成员函数
}
解决方案:用virtual关键字
注意:如果使用了virtual关键字,但是两个类中的函数原型不一样,也不会发生多态。
函数原型指的是函数的头。
class parent{
public:
parent(int a){
this->a=a;
cout<<"parent a:"<<a<<endl;
}
virtual void print(){ 父类必须要写virtual
cout<<"hello"<<endl;
}
private:
int a;
};
class child: public parent{
public:
child(int b){
this->b=b;
cout<<"child b:"<<b<<endl;
}
virtual void print(){ 如果父类写了virtual,子类可以不写
cout<<"hello"<<endl;
}
private:
int b;
};
int main(){
parent p(10);
child c(20);
parent *base=NULL;
base=&p;
base->print(); 执行parent的成员函数
base=&c;
base->print(); 执行child的成员函数
parent &base=p;
base.print(); 执行parent的成员函数
parent &base=c;
base.print(); 执行child的成员函数
}
面向对象三大思想:封装、继承、多态
实现多态三个条件:
1 要有继承
2 要有函数重写virtual
3 用父类指针或引用指向子类对象
虚析构函数
构造函数不能是虚函数,建立一个派生类对象时,必须从类层次的根开始,
沿着继承路径逐个调用基类的构造函数
析构函数可以是虚的,虚析构函数用于指引delete运算符正确析构动态对象
class A{
public:
A(){
p=new char[20];
strcpy(p, "abc");
}
~A(){ virtual~A()构成虚析构函数
delete []p;
}
private:
char *p;
};
class B: public A{
public:
B(){
p=new char[20];
strcpy(p, "def");
}
~B(){ virtual~B()构成虚析构函数
delete []p;
}
private:
char *p;
};
class C: public B{
public:
C(){
p=new char[20];
strcpy(p, "xyz");
}
~C(){
delete []p;
}
private:
char *p;
};
void how_to_delete(A *base){ 注意形参是A类型
delete base;
不会表现出多态的属性,只执行A的析构函数,需要在所有基类的析构函数前加virtual
}
int main(){
C *p=new C;
delete p; 这句话的效果和执行函数的效果不同,直接通过子类对象析构不需要写virtual
how_to_delete(p);
只会执行A的析构函数
}
重载、重写、重定义
重载必须在一个类的内部;子类无法重载父类的函数,父类同名函数被覆盖;
重载实在编译期间根据参数类型和个数调用。
重写发生字父类和子类的两个类之间。
重写分为两类
1 虚函数重写,使用virtual关键字,发生多态;多态是在运行期间根据对象具体的类型决定调用
2 非虚函数重写 不使用virtual关键字(叫重定义)
class parent{
public:
void abc(){
cout<<"abc"<<endl;
}
public:
三个函数是重载的关系
virtual void func(){
cout<<"func() do"<<endl;
}
virtual void func(int i){
cout<<"func(int i) do"<<endl;
}
virtual void func(int i, int j){
cout<<"func(int i, int j) do"<<endl;
}
};
class child: public parent{
public:
两个函数时重载关系
其中parent和child中的(int i, int j)两个函数为重写中的虚函数重写-->多态
virtual void func(int i, int j){
cout<<"func(int i, int j) do"<<endl;
}
virtual void func(int i, int j, int k){
cout<<"func(int i, int j, int k) do"<<endl;
}
public:
void abc(){
cout<<"abc"<<endl;
}
两个类中的abc函数为重写中的非虚函数重写(重定义),覆盖;但可用域作用符调用父类函数
};
int main(){
child c;
c.func(); err:没有重载函数接受0个参数,子类含有和父类相同的函数,覆盖掉
因为子类中已经有了func名字的函数,编译器不会在父类中找
c.parent::func(); ok
}
多态原理研究
多态成立的三个条件:1、继承。2、虚函数重写virtual。3、父类指针(或引用)指向子类对象。
class parent{
public:
parent(int a){
this->a = a;
}
virtual void print(){
cout << "parent..." << endl;
}
private:
int a;
};
class child : public parent
{
public:
child(int a = 0, int b = 0) : parent(a){
this->b = b;
}
virtual void print(){
cout << "child..." << endl;
}
private:
int b;
};
void how_to_play(parent *base){
base->print();
效果:传来父类对象,执行父类对象的函数;传来子类对象,执行子类对象的函数
父类对象和子类对象分别与vptr指针-->虚函数表-->函数的入口地址
}
int main(){
parent t;
child c;
how_to_play(&p);
how_to_play(&c); 多态发生
}
多态的实现原理
当类中声明虚函数时,编译器会在类中生成一个虚函数表
虚函数表是一个存储类成员函数指针的数据结构
虚函数表由编译器自动生成和维护
virtual成员函数会被编译器放在虚函数表中
存在虚函数时,每个对象都有一个直线跟虚函数表的指针(vptr指针)
加入virtual函数的类定义的对象,会多一个vptr指针,其所占内存比没有
virtual的多4个字节
对象在创建时,有编译器对vptr指针进行初始化,只有当对象的构造函数完全结束时,vptr指针才最终确定
父类对象的vptr指针只能指向父类虚函数表,子类的只能指向子类虚函数表
class parent{
public:
parent(int a){
this->a=a;
}
virtual void print(){
cout<<"hello"<<endl;
}
};
class child: public parent{
public:
child(int b){
this->b=b;
}
virtual void print(){
cout<<"hello"<<endl;
}
};
int main(){
child c;
初始化c的vptr指针,初始化是分步的
当执行父类的构造函数时,把c的vptr指针指向父类的虚函数表
当执行子类的构造函数时,把c的vptr指针指向子类的虚函数表
}
纯虚函数和抽象类
纯虚函数是在基类中说明的虚函数,在基类中没有定义,要求任何派生类都定义自己的版本
纯虚函数为派生类提供了一个公共的界面
纯虚函数说明形式:virtual 类型 函数名(参数表)=0
一个具有纯虚函数的基类为抽象类
#include <iostream>
using namespace std;
class parent{
public:
virtual void get_area()=0; 纯虚函数
};
class circle: public parent{
public:
circle(int r){
this->r=r;
}
virtual void get_area(){
cout<<"圆面积"<<3.14*r*r<<endl;
}
private:
int r;
};
class tringle: public parent{
public:
tringle(int a, int h){
this->a=a;
this->h=h;
}
virtual void get_area(){
cout<<"三角形面积"<<a*h/2<<endl;
}
private:
int a;
int h;
};
void play(parent *base){
base->get_area();
}
int main(){
circle c(10);
tringle t(3, 4);
play(&c);
play(&t);
}