预习和提问
什么是多态? / 为什么使用多态?
什么是虚函数?
特点,注意?
什么是虚函数表?
纯虚函数和抽象类又是什么?
面试怎么问?
虚函数使用方法
定义 函数返回类型之前使用virtual 只在成员函数的声明中添加,实现中不需要 继承 某个成员函数被声明为虚函数,那么它的子类,以及子类的子类中,所继承的这个成员函数,也自动是虚函数 子类中重写这个虚函数,可以不用在写virtual,但建议写,可读性更好
多态性的解释
(1)体现在具有继承关系的父类和子类之间.子类重新定义(覆盖/重写)父类的成员函数,同时父类和子类中又把这个函数声明为了virtual函数 (2)通过父类的指针,只有到了程序运行时期,根据具体执行到的代码行,才能找到动态绑定到父类指针上的对象(new的是哪个),这个对象是某个子类对象,或者父类对象,系统内部实际上是要查类的虚函数表,根据虚函数表找到函数的入口地址,从而调用父类或者子类的虚函数
理解多态
简单说就是想要实现多态这种效果, 就需要使用虚函数 多态理解为一种效果,形式,而虚函数是实际功能,手段,机制
示例
Main
#include "Son.h"
#include "Father.h"
#include <iostream>
int main()
{
Father father;
Son son;
Father* tmp;
tmp = &father;
tmp->play();
tmp = &son;
tmp->play();
return 0;
}
Father.h
#pragma once
#include <iostream>
using namespace std;
class Father
{
public:
virtual void play();
};
Father.cpp
#include "Father.h"
void Father::play()
{
cout << "Father---play()"<< endl;
}
Son.h
#include "Son.h"
void Son::play()
{
cout << "Son---play()" << endl;
}
Son.cpp
#pragma once
#include "Father.h"
class Son : public Father
{
public:
void play();
};
示例中是写死的一个&father或&son,实际项目中可以根据用户的输入,实现过程中的各种状态来创建对象,根据调用时的指针 根据执行时子类对象进来就调用子类方法,父类对象进来就调用父类方法
总结:形式上,原本调用父类的方法,但是,实际上会调用子类的同名方法
纯虚函数和抽象类
纯虚函数:在基类中声明的虚函数,但是他在基类中没有函数体,或者说没有实现,只有一个声明,但它要求任何派生类都要定义该虚函数自己的实现方法 定义 在该虚函数的函数声明末尾的分号之前加"=0" 示例 Father.cpp
class Father
{
public:
virtual void play2() = 0;
};
Main.cpp
int main()
{
return 0;
}
注意 (1)纯虚函数没有函数体,只有函数声明 (2)一旦一个类中有纯虚函数,那就不能生成这个类的对象了(有纯虚函数的类就叫抽象类) (3)抽象类不能用来生成对象,主要目的是用来统一管理子类对象;换句话说,就是主要用于当做父类来生成子类用的. (4)子类中必须实现该父类中的纯虚函数,那上面例子说就是,定义了一个play2纯虚函数,必须在子类中定义play2函数体,如下
class Son : public Father
{
public:
void play2(){};
};
父类的析构一般写成虚函数
先说结论:用基类指针new子类的对象,在delete的时候系统不会调用子类的析构函数 示例
Son son;
Son* tmp_son = new Son;
delete tmp_son;
到这里,还都是正常释放,看下面示例
Father* tmp_father = new Son;
delete tmp_father;
但是在用父类new子类对象的时候,没有释放"Son"的析构,这段示例可以自己尝试一下. 这肯定就有问题了,只删除了一半 解决 把父类的析构写成虚函数
virtual ~Father();
完美解决这个问题,只需要在父类的析构上加了一个virtual ,这个问题就不存在了,子类的析构成功执行 结论 (1)delete tmp_father时,肯定是要调用的父类的析构函数, 但是父类析构函数中,要想调用子类(Son)的析构函数,父类(Father)的析构就要声明成virtual, 也就是说C++中为了获得运行时的多态行为,所调用的成员函数必须得是virtual的. 因为虚函数特性,只有虚函数才能做到用父类指针调用子类的虚函数
(2)父类中的析构函数的虚属性会继承给子类,这意味着子类的析构函数也是虚析构,就算不用virtual修饰也可.虽然名字不同
面试常问
为什么父类的析构一定要写成虚函数? 只有这样,当delete一个指向子类对象的父类指针时,才能保证系统能够依次调用子类的析构函数和父类的析构函数,从而保证对象(父类指针指向子对象)的内存被正确释放
多态的原理? 多态是用虚函数表实现的。 有虚函数的类都会生成一个虚函数表,这个表在编译时生成。
动态绑定与静态绑定? 静态绑定是程序编译时确定程序行为。 动态绑定是程序运行时根据具体的对象确定程序行为。
静态成员可以是虚函数吗?
不能, 因为静态成员函数没有this指针, 因为有this指针才能访问到虚表指针,有虚表指针才能找到虚表从而调用实际应该调用的函数。
构造函数可以是虚函数吗?/虚函数指针在什么时候生成的的? 不能,因为对象中的虚表指针是在构造函数初始化列表阶段才初始化的
对象访问普通函数快还是虚函数更快? 首先如果是普通对象,是一样快的,如果是指针对象或者是引用对象,则调用的普通函数快,因为普通对象在编译时就确定地址了,虚函数构成多态,运行时调用虚函数需要到虚函数表中去查找
注: 对象布局,对象模型,虚函数表,后续会用大篇幅补充…
|