提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
提示:这里可以添加本文要记录的大概内容:
在学习C++的过程中,对于虚函数的概念有一定的疑惑,因此写一篇文章帮助自己记忆和理解。
提示:以下是本篇文章正文内容,下面案例可供参考
一、虚函数是什么?
1.前言
实际写代码的过程中经常会出现类继承的现象。而类继承中往往又容易出现函数重写或者重定义的情况(可以理解为函数重名)。如下,此时若我们定义了一个输入类型为Entity的指针(即基类指针)的函数,而给他传入的是继承类的实例,则可能无法达到我们所需要的要求。
#include <iostream>
class Entity{
public:
int x,y;
Entity(){
x=0;
y=0;
}
void function1()
{std::cout<<"entity"<<std::endl;}
};
class Player:public Entity//继承了Entity类
{
public:
const char*Name;
void function1(){//与entity类同名的函数
std::cout<<"player"<<std::endl;}
};
void function(Entity *e){//参数为基类指针
e->function1();
}
int main(){
Player *a=new Player();
function(a);//运行结果为——"entity",而不是"player";
Entity *b=new Entity();
function(b);
}
运行结果如下
entity
entity
出现第一次输出的结果的原因:因为我们虽然使用的是Player类的指针调用函数function(),但编译器无法知道这一事实(直到运行的时候,程序才可以根据用户的输入判断出指针指向的对象),它只能按照调用Entity类的函数来理解并编译,所以我们看到了第一行的结果。
为了解决这样的问题,引入虚函数的概念。
2.定义
虚函数的作用是允许我们使用一个基类指针指向或者引用一个继承类对象时,调用对应的虚函数,实际调用的是继承类的函数版本。它允许派生类重用和扩展基类的功能,而无需完全重写函数。
虚函数的关键字是vitrual。
二.实际应用
对上述代码修正一下,即可得到如下代码。
主要的改动部分就是在第一个function1()函数前加上virtual关键字,表示该函数为虚函数。
#include <iostream>
class Entity{
public:
int x,y;
Entity(){
x=0;
y=0;
}
virtual void function1()
{std::cout<<"entity"<<std::endl;}
};
class Player:public Entity
{
public:
const char*Name;
void function1() override//可以使用override关键字表示该函数是重名函数
{std::cout<<"player"<<std::endl;}
};
void function(Entity *e){
e->function1();
}
int main(){
Player *a=new Player();
function(a);
Entity *b=new Entity();
function(b);
}
运行结果如下:
player
entity
三.纯虚函数是什么?
上文的虚函数主要是应用于具有两个特点的场景:
1.当存在基类指针指向继承类对象时(这种情况也时常发生)。
2.有重载函数时(可以简单的理解有重名函数)。
但细心的读者可能会发现,无论是虚函数还是函数,在调用的过程中都要求需要有对应的函数实现。如上文中的Entity类中的function1()的功能是输出“entity”,而Player类中的function1()的功能是输出“player”。
那么存不存在一种情况,即基类虽然定义了一个函数,但实际上并没有实现它,却可以在子类中强制执行该函数?
这就是纯虚函数的作用,我们可以用一个更大众的词汇去解释他的作用——“接口”。
举个例子:还是上文的代码 但我在基类中定义一个名为function2()的函数但并不实现它。
#include <iostream>
class Entity{
public:
int x,y;
Entity(){
x=0;
y=0;
}
virtual void function1()
{std::cout<<"entity"<<std::endl;}
virtual void function2()=0;//定义纯虚数的方法
};
class Player:public Entity
{
public:
const char*Name;
void function1() override//可以使用override关键字表示该函数是重名函数
{std::cout<<"player"<<std::endl;}
};
void function(Entity *e){
e->function1();
}
int main(){
Player *a=new Player();
function(a);
a->function2();
}
此时如果我选择运行,则会报错。会得到一个链接错误(LNK),原因是因为在编译的过程中,无法找到对应的函数实现。
LNK2001 无法解析的外部符号 "public: virtual void __cdecl Entity::function2(void)" (?function2@Entity@@UEAAXXZ)
此时,则需要在继承类中去具体写出该对应函数的实现。如下,我在Player类中实现了function2()。我假设他的功能为输出“player+entity”。
#include <iostream>
class Entity{
public:
int x,y;
Entity(){
x=0; y=0;
}
virtual void function1()
{std::cout<<"entity"<<std::endl;}
virtual void function2()=0;
};
class Player:public Entity
{
public:
const char*Name;
void function1() override//可以使用override关键字表示该函数是重名函数
{std::cout<<"player"<<std::endl;}
void function2(){std::cout<<"player+entity"<<std::endl;
}
};
void function(Entity *e){
e->function1();
}
int main(){
Player *a=new Player();
function(a);
a->function2();
}
输出结果如下所示:
player
player+entity
再进一步思考运用到多个类中,使得多个类虽然都有function2()这个函数,但是所实现的具体细节却不同,这就是封装的思想。
举个例子,我再定义一个新类start,同样实现一个叫做function2()的函数。
#include <iostream>
class Entity{
public:
int x,y;
Entity(){
x=0; y=0;
}
virtual void function1()
{std::cout<<"entity"<<std::endl;}
virtual void function2()=0;
};
class Player:public Entity
{
public:
const char*Name;
void function1() override//可以使用override关键字表示该函数是重名函数
{std::cout<<"player"<<std::endl;}
void function2(){std::cout<<"player+entity"<<std::endl;
}
};
class start :public Entity
{
public:
void function2() {
std:: cout<< "game start" << std::endl;
}
};
void function(Entity *e){
e->function1();
}
int main(){
Player *a=new Player();
start *b=new start();
a->function2();
b->function2();
}
此时运行结果如下:
player+entity
game start
需要注意的点是,由于基类Entity采用了纯虚函数的定义,因此此时无法再实例化基类(因为编译器没法在基类中找到对应的函数实现)。即如果在main函数中采用如下的定义方式:
Entity *c=new Entity();
程序会报错:
C2259“Entity”: 无法实例化抽象类