本文已参与「新人创作礼」活动,一起开启掘金创作之路。
游戏开发之纯虚函数、虚析构函数及抽象类(C++)
1.纯虚函数
- 纯虚函数是一个特殊的函数,在一个类的内部有纯虚函数之后,该类就不可以直接声明为实体类,必须在子类继承该类之后,子类可以间接声明其实体类。
- 纯虚函数在子类是必须要实现的,否则C++编译器会报错。
- 纯虚函数的定义语法:
- virtual 数据类型 函数名(形参列表) = 0;
class A
{
public:
//纯虚函数定义语法:
//virtual 数据类型 函数名(形参列表) = 0;
virtual void Print() = 0;
};
//纯虚函数必须要在派生类中实现,派生类才可以定义使用纯虚函数
class B :public A
{
public:
int a;
void Print() override
{
//__FUNCTION__ 、__LINE__ 是C++编译器自带的宏。
std::cout << __FUNCTION__ << " " << __LINE__ << std::endl;
}
};
class C :public A
{
public:
void Print() override
{
std::cout << __FUNCTION__ << " " << __LINE__ << std::endl;
}
};
int main()
{
//A a;无法直接使用有纯虚函数的类。
A* p1 = new B;
p1->Print();//B
sizeof(B);//32位操作系统下为8,可以看出纯虚函数跟虚函数一样,有虚函数表指针。纯虚函数在子类实现之后就变成了虚函数,子类的子类可以继承该虚函数,也就有了虚函数表指针以及虚函数表。
return 0;
}
2.虚函数和纯虚函数的区别
- 虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义。
- 虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。
- 虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的接口。
- 在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。
- 纯虚函数必须实现,如果不实现,编译器将报错。
- 对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。而纯虚函数只有子类有自己的版本,父类只用于规范统一接口。
- 实现了纯虚函数的子类,该纯虚函数在子类中就变成了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。
3.抽象类
- 抽象类,即纯虚类。含有纯虚函数的类叫做抽象类,只含有虚函数的类不能叫做抽象类。
- 1.抽象类不能直接被实例化。
- 2.抽象类不与具体的事物相联系,而只是表达一种抽象的概念。
- 使用抽象类,不需要关心子类的具体实现,只需要知道该接口函数调用后,会达到怎样的预期效果即可。
4.虚析构函数
- 当一个抽象类有子类时,该抽象类的析构函数必须是虚函数,也就是被子类用于动态绑定。不然只是静态绑定,在抽象类对象析构的时候不会调用派生类的析构函数。
- 虚析构函数可以解决子类资源释放不完全的问题。
- 语法:virtual ~类名(){函数体}
class A
{
public:
virtual void Print() = 0;
//虚析构函数定义语法:
virtual ~A()
{
std::cout << __FUNCTION__ << " " << __LINE__ << std::endl;
}
};
//纯虚函数必须要在派生类中实现,派生类才可以定义使用纯虚函数
class B :public A
{
public:
void Print() override
{
std::cout << __FUNCTION__ << " " << __LINE__ << std::endl;
}
~B()
{
std::cout << __FUNCTION__ << " " << __LINE__ << std::endl;
}
};
class C :public A
{
public:
void Print() override
{
std::cout << __FUNCTION__ << " " << __LINE__ << std::endl;
}
~C()
{
std::cout << __FUNCTION__ << " " << __LINE__ << std::endl;
}
};
int main()
{
A *p1 = new B;
A *p2 = new C;
p1->Print();
p2->Print();
delete p1;
delete p2;
return 0;
}
-
当我们未在A类书写虚析构函数的时候,运行结果如下:
可以发现,当程序运行结束的时候,并未调用B类和C类的析构函数。 原因是因为:基类的基类对象指针指向派生类对象,而基类中的析构函数却是非virtual的,即不是虚函数,而虚函数是动态绑定的,不是虚函数无法发生动态绑定。且指针的类型为基类的数据类型(符合就近原则),所以程序结束的时候只调用了基类A的析构函数,而不会调用派生类的析构函数。
-
当我们在A类书写虚析构函数的时候,运行结果如下:
可以发现,此时当基类的析构函数变成虚析构函数的时候,B类和C类在继承的时候,动态绑定了基类的析构函数。且在析构的时候,先析构子类的析构函数,然后再析构基类的析构函数。
版本声明:本文为CSDN博主[ufgnix0802]的原创文章。
原文链接:(blog.csdn.net/qq135595696…)