游戏开发之纯虚函数、虚析构函数及抽象类(C++)

503 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

游戏开发之纯虚函数、虚析构函数及抽象类(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;
	}
  1. 当我们未在A类书写虚析构函数的时候,运行结果如下: 效果图 可以发现,当程序运行结束的时候,并未调用B类和C类的析构函数。 原因是因为:基类的基类对象指针指向派生类对象,而基类中的析构函数却是非virtual的,即不是虚函数,而虚函数是动态绑定的,不是虚函数无法发生动态绑定。且指针的类型为基类的数据类型(符合就近原则),所以程序结束的时候只调用了基类A的析构函数,而不会调用派生类的析构函数。

  2. 当我们在A类书写虚析构函数的时候,运行结果如下: 效果图 可以发现,此时当基类的析构函数变成虚析构函数的时候,B类和C类在继承的时候,动态绑定了基类的析构函数。且在析构的时候,先析构子类的析构函数,然后再析构基类的析构函数。

版本声明:本文为CSDN博主[ufgnix0802]的原创文章。
原文链接:(blog.csdn.net/qq135595696…)