面试小卡片--虚函数/纯虚函数
虚函数
1、定义:基类可以有定义,子类可以没有定义
2、示例:EX: virtual void run();
3、目的:
纯虚函数
- 定义:用于定义接口,只进行函数声明而不能定义,需要子类重写接口方法
- 示例:EX: virtual void run() = 0;
- 目的:实现“运行时多态”,同一操作作用于不同对象产生不同结果
在运行时,通过指向基类的指针,创建子类对象,从而调用子类方法
以上,可以联想到java中抽象类于接口的区别:
- 抽象类:可以有非抽象方法
- 接口:实现接口需要重写所有接口方法
接下来,可以看看代码实际走走,
下面两个类分别是接口声明和抽象类定义。
class MediaBaseInterface {
public:
//纯虚函数在基类中无定义
virtual void foo() = 0;
virtual void setName(string name) = 0;
virtual string getName() = 0;
};
//这是一个抽象类-抽象类中写虚析构函数,不能创建该类实例
class DummyMediaBaseAbstract {
public:
DummyMediaBaseAbstract() { cout << "===>Media: Constructot Called!\n"; }
//父类析构是否为虚
virtual ~DummyMediaBaseAbstract() { cout << "===>Media Destructor Called!\n"; }
//虚函数可以在基类中有定义
virtual void foo() { cout << "Media: virtual foo\n"; }
virtual void setName(string name) { m_name = name; }
virtual string getName() { return m_name; }
//纯虚函数在基类中无定义
virtual void bar() = 0; // { cout << "virtual bar\n"; }
private:
string m_name;
};
接下来创建子类实现父类方法
//book class
class DummyBookDerived : public DummyMediaBaseAbstract {
public:
DummyBookDerived() { cout << "===>Book: Constructor Called!\n"; }
~DummyBookDerived() { cout << "===>Book: Destructor Called!\n"; }
void foo() { cout << "Book: foo\n"; }
void bar() override { cout << "Book: implement pure virtual bar function\n"; }
};
//video class
class DummyVideoDerived : public DummyMediaBaseAbstract {
public:
~DummyVideoDerived() { cout << "===>Video Destructor Called!\n"; }
//可以不重载虚函数,必须重载纯虚函数
void bar() override { cout << "Video: implement pure virtual bar function\n"; }
};
//class music
class DummyMusicDerived : public DummyMediaBaseAbstract {
public:
DummyMusicDerived() {cout << "===>Music: Constructor Called!\n"; }
~DummyMusicDerived() { cout << "===>Music Destructor Called!\n"; }
void foo() { cout << "Music: foo\n"; }
//必须重写纯虚函数
void bar() override { cout << "Music: implement pure virtual bar function\n"; }
void setName(string name) { m_music = name; }
string getName() { return m_music; }
private:
string m_music;
};
最后,在主函数中定义父类指针,指向子类对象,看看会发生什么奇妙的事
前面代码中有一个疑问,不知你有没有注意到
int main() {
list<DummyMediaBaseAbstract*> m_medias;
//指向基类的指针,只调用基类析构函数
DummyMediaBaseAbstract* p_music = new DummyMusicDerived();
p_music->setName("Love Story");
cout << p_music->getName() << endl;
//p->foo();
DummyMediaBaseAbstract* p_book = new DummyBookDerived();
DummyMediaBaseAbstract* p_video = new DummyVideoDerived();
m_medias.push_back(p_book);
m_medias.push_back(p_music);
m_medias.push_back(p_video);
for(auto x : m_medias) {
x->foo();
x->bar();
}
delete p_music;
printf("============================\n");
delete p_book;
printf("============================\n");
delete p_video;
return 0;
}
看看不同的输出结果,你能看出何种差别


发现1:构造对象时,先调用父类构造函数再调用子类构造函数,析构顺序正好相反。
发现2:当父类析构函数为virtual时,创建的父类指针会释放子类对象,没有的话,则只会释放父类对象。
发现3:静态与动态,若子类重写父类方法,则运行时调用子类方法,否则,调用父类方法。

浙公网安备 33010602011771号