多态是函数的不同实现形式。
多态是面向对象的泛型编程的一种方式,所谓的泛型编程就是试图使用不变的代码来实现可变的算法。
我们现在的多态是一种基于继承关系 + 虚函数而存在的多态。 它也被称之动态多态,也被称之为绑定多态。
实现多态的必要条件:
在类中使用virtual
关键字修饰的成员函数被成为虚函数。
虚函数具有虚属性
所谓虚属性就是这个函数可以在子类中进行重写(override)。
重写:即在子类中定义和父类原型相同的函数。
原型相同:返回值 函数名 形参列表 const属性 都要相同,函数体可以不同。
重写之后,当使用父类指针指向子类对象的时,如果使用父类指针调用这个虚函数时,则将执行的是子类重写的逻辑。这种方式也被成为动态多态。
C++11新特性:override
作用是检验这个子类中同名函数的返回值和形参列表是否与父类之中同名的虚函数的返回值和形参列表相同,如果不相同则直接报错。
是一种安全检查机制。
虚函数函数体:
virtual + 类中成员函数 //这个函数就是一个虚函数
{
//虚函数的函数体。
}
代码示例:是否加virtual
进行对比
#include
using namespace std;
class person
{
public:
void action()
{
cout << "正在写博客" << endl;
}
};
class stu:public person//继承
{
public:
void action()//继承有两个同名函数,父类的同名函数就应该被隐藏
{
cout << "正在学习C++" << endl;
}
};
int main()
{
person *p=new stu();
p->action();
return 0;
}
#include
using namespace std;
class person
{
public:
virtual void action()//父类加了virtual修饰后,成员函数成为虚函数,如果在子类之中出现同名函数将会被重写
{
cout << "正在写博客" << endl;
}
};
class stu:public person//继承
{
public:
void action()override//C++11新特性,
//override:检验这个子类中同名函数的返回值和形参列表是否与父类之中同名的虚函数的返回值和形参列表相同,
//如果不相同则直接报错。安全检查机制。
{
cout << "正在学习C++" << endl;
}
};
int main()
{
person *p=new stu();
p->action();//继承有两个同名函数,父类的同名函数就应该被隐藏
return 0;
}
结果展示:
为什么第一个代码结果是父类的action呢?
因为当继承有两个同名函数时,通过父类的指针或引用只能访问父类的函数,父类的同名函数就应该被隐藏。
总结:
对比代码结果可知当父类加了virtual
修饰后,成员函数成为虚函数,如果在子类之中出现同名函数将会被重写。如果不加virtual
修饰,那么通过父类的指针或引用只能访问父类的函数 。
函数重载和函数重写有什么区别?
函数重载:要求函数名相同,形参列表必须不同。
函数重写:要求函数原型必须相同,且函数重写只能发生在父子类之间。
用上述代码求出在父类void action()
不加virtual
时和加上时的sizeof()
大小为多少。
cout << sizeof(person) <<endl;
cout << sizeof(stu) <<endl;
不加virtual
时都为1;
加virtual
时都为8;
由此可知:加virtual
时编译器为我们默默安插了一根虚指针。子类继承时也拥有了这个虚指针。
这跟虚指针指向了这个类中编译器在他的rodate段生成一个虚函数表,虚函数表中就保存了这个virtual
修饰的这个虚函数的地址。
重写前:
重写后:
当子类有同名函数对父类中的同名函数进行重写时,在子类的虚表中产生了一个新的函数地址,这个新的函数地址就把原来的父类中的函数地址给覆盖掉了。
所以执行的是stu的逻辑。
总结:编译器帮我们做什么好事?
virtual
声明一个成员函数为虚函数时,在编译时,编译器会自动在基类中默默地安插一个虚函数表指针,同时.rodata段为这个类生成一张虚函数表,用来保存类中的虚函数的地址。让我们在看看底层逻辑
底层逻辑:
没有加virtual
时:
加了virtual
时:
这就是动态多态实现的原因,即加了virtual
后,定义重名函数被重写覆盖之后放到了寄存器上,call指令是call的寄存器,即重写函数。
虚函数表的结构:
对上图的解释:
构造函数,拷贝构造函数,拷贝赋值函数等都不能是虚函数。
但是析构函数可以是虚函数,称之为虚析构函数。
多态在使用时会遇到巨大的问题,我们采用的解决方案就是虚析构。
虚析构的作用:指引delete关键字,正确释放空间。
C++的语法规定:当父类的析构函数为虚析构时,那么子类的析构将是对父类析构的重写。
如果使用多态,请务必把最远端父类中的析构函数设定为虚析构,这样可以避免子类对象的内存泄漏问题。
代码示例:
#include
using namespace std;
class A
{
public:
A()
{
cout << "A的构造" << endl;
}
virtual ~A()//在析构函数前加virtual进行修饰,此时这个析构就是一个虚析构。
{
cout << "A的析构" << endl;
}
virtual void show()
{
cout << "学习C++" << endl;
}
};
class B: public A
{
private:
int *p;
public:
B()
{
cout << "B的构造" << endl;
p=new int[20];
}
~B()//在析构函数前加virtual进行修饰,此时这个析构就是一个虚析构。
{
cout << "B的析构" << endl;
delete []p;
}
void show()override
{
cout << "我是野猫徐" << endl;
}
};
int main()
{
A *p=new B;
p->show();
delete p;
return 0;
}
结果展示:
不加虚析构
加了虚析构
如果析构函数不是虚函数,此处的delete p
只会调用父类的析构函数会造成子类的指针成员泄漏。
如果析构函数是虚函数,此处的 delete p
就会先调用子类的析构,然后调用父类的析构。
代码示例:
#include
using namespace std;
class A
{
public:
A()
{
cout << "A的构造" << endl;
}
virtual ~A()
{
cout << "A的析构" << endl;
}
};
class B
{
public:
B()
{
cout << "B的构造" << endl;
}
virtual ~B()
{
cout << "B的析构" << endl;
}
};
class C : public A, public B
{
public:
C()
{
cout << "C的构造" << endl;
}
~C()
{
cout << "C的析构" << endl;
}
};
int main()
{
//A* a = new C;
B* b = new C;
delete b;
return 0;
}
结果展示:
不使用虚析构
使用虚析构
类中的成员函数由virtual
进行修饰且没有函数体的函数(只有声明,没有定义),就称之为纯虚函数,也被称之为接口函数。
在虚函数的基础上,将{}和函数体用 =0 替换,那么该虚函数就是一个纯虚函数了。
class 类名
{
virtual 返回值 函数名 (形参列表) = 0; //这种语法形式就是纯虚函数。
}
有些场景下,我们想使用的是子类的对象,而基类本身实例化出来的对象是没有意义的。
只是做为顶级父类中的一种功能描述,而没有具体的实现,这个函数实现是要在子类之中完成的。
例如:动物类派生了 狮子、老虎、长颈鹿、企鹅、大象…等,我们想用的是老虎、狮子等,而动物类本身实例化出来的对象是没有用的。
由于父类中没有纯虚函数的定义,只有声明,所以要求子类中必须重写父类的纯虚函数。
类中有纯虚函数的类,就叫抽象类,这种类一般做为顶级父类使类使用。这种类也称为接口类。
抽象类必须继承,且抽象类中纯虚函数必须重写。如果子类没有重写父类的纯虚函数,那么该子类也即成为抽象类。
抽象类不能定义实例,因为因为其成员函数不完整(没有函数体)。只是做为一个最远端父类的指针或引用,来指向或引用子类实例 。
代码示例
#include
using namespace std;
class A
{
public:
A()
{
cout << "A的构造" << endl;
}
virtual~A()
{
cout << "A的析构" << endl;
}
virtual void show()=0;
};
class B:public A
{
public:
B()
{
cout << "B的构造" << endl;
}
~B()
{
cout << "B的析构" << endl;
}
void show() override
{
cout << "正在学习C++" << endl;
}
};
int main()
{
A* a = new B;
a->show();
delete a;
return 0;
}
结果展示:
总结: