c++ virtual 关键字 override 关键字

1.什么是virtual

virtual是一个C++的修饰符,用于修饰函数,被修饰的函数称为虚函数。

2.为什么需要?

在C++中,我们都知道要实现多态有2种方法,一种是重写,一种是重载,重载是横向的,意思是只发生在同一个类中,根据函数的参数个数,类型不同从而实现重载,而重写则是纵向的,发生在继承中,子类函数覆盖父类函数,父类指针指向子类实体时,应该实现运行时多态。

3.通常用在什么情形?

1.作为基类的类的析构函数
如果一个类做为父类,然后它被别人继承,当用子类指针指向子类时不会出现任何的问题,但是如果用父类指针指向子类时,若没有加析构函数,那只会析构父类的析构函数,这时我们需要用virtual修饰父类的析构函数。

2.需要实现多态的函数
若一个函数需要实现多态,即运行时多态。

1、定义在函数中的对象,在函数调用结束时,在对象释放前调用析构函数
2、定义在函数中的static对象,在函数调用结束时,不调用对象析构函数,在mian函数结束时,会调用对象析构函数
3、定义全局对象或者static全局对象,程序执行流程离开其作用域时,调用对象析构函数
4、用new运算法生成的对象,调用delete运算法释放该对象时,先调用对象析构函数

重写 覆盖的几个函数必须函数名、参数、返回值都相同;

case 1

#include <iostream>
using namespace std;
 
class Base{
  public:
    ~Base() {cout<<"~B"<<endl;}
};
 
class Derived:public Base{
  public:
    ~Derived() {cout<<"~D"<<endl;}
};
 
int main (){
  Base *b = new Derived; //注意这里
  delete b;
}

子类指向父类,查看打印日志:

~B

可见删除子类指向父类的指针,代表delete只删除了父类,并没有删除子类的相关内存,会产生内存泄漏。所以在多态的使用过程中,delete(指向父类的子类对象)需要格外注意是否会没有释放子类或者父类。

case 2

#include <iostream>
using namespace std;
 
class Base{
  public:
    ~Base() {cout<<"~B"<<endl;}
};
 
class Derived:public Base{
  public:
    ~Derived() {cout<<"~D"<<endl;}
};
 
int main (){
  Derived *d = new Derived; //注意这里
  delete d;
}

子类指向子类指针,查看打印日志:

~D

~B

可以得知,delete子类的析构顺序:是先释放子类,在释放父类。

这是我们期望的结果,所以没啥问题。。。

case 3
对于case 1当中的情况,如何去规避呢? 需要将父类的析构函数定义为virtual

#include <iostream>
using namespace std;
 
class Base{
  public:
    virtual ~Base() {cout<<"~B"<<endl;}
};
 
class Derived:public Base{
  public:
    ~Derived() {cout<<"~D"<<endl;}
};
 
int main (){
  Base *d = new Derived; //注意这里
  delete d;
}

打印结果:

~D

~B

原理:delete d; //d虽然为Base*,但是它的析构函数是virtual的,所以它调用析构函数,其实是调用虚函数表中的函数。而Base* d指向的虚函数表在内存中其实是new Derived类的虚函数表。所以通过Base类的virtual函数 + Base* d指向派生类(Derived)对象,从而使得在调用delete d的时候实际上是调用的是delete Derived* d的意思,也就是第二种情况的效果。

4.延伸:虚函数/纯虚函数、override 关键字


虚函数分类:

  • 纯虚函数要求派生类必须重写
  • 普通虚函数有默认实现但可以被派生类重写

使用override和default关键字可以明确指示函数的重写和默认实现。

示例

virtual void Draw() const = 0; // 1) 纯虚函数
virtual void Error(const string& msg); // 2) 普通虚函数
virtual void Error(const string& msg) override = default;	// 错误,= default只能用在默认构造、拷贝构造等默认函数中
virtual void Error(const string& msg) override;

9.问题汇总

9.1 非虚函数和虚函数都可以重写,那区别是啥?

virtual void Error(const string& msg);
void Error(const string& msg);

对于上述两个函数声明:

  1. virtual void Error(const string& msg); 是一个虚函数的声明。虚函数是在基类中声明并带有 virtual 关键字的函数,它可以被派生类重写(覆盖)。虚函数可以通过基类指针或引用来实现动态绑定,即在运行时根据对象的实际类型来调用相应的函数。

  2. void Error(const string& msg); 是一个非虚函数的声明。非虚函数是在类中声明但没有 virtual 关键字修饰的函数。非虚函数在派生类中也可以被重写,但它们不具备动态绑定的特性。当通过基类指针或引用调用非虚函数时,将根据指针或引用的类型来确定调用的函数,而不考虑指向的对象的实际类型。

因此,虚函数和非虚函数的区别在于它们的调用方式和多态性的支持。虚函数可以通过基类指针或引用实现多态性,而非虚函数的调用始终与指针或引用的类型相符,不会发生动态绑定。

9.2 基类虚函数/纯虚函数、子类有没有 override 的区别


override 的作用:用于显式地标识子类中的函数重写父类的虚函数(包括纯虚函数)的一种方式。

使用 override 关键字有以下几个好处:

  1. 明确表明意图:使用 override 关键字可以明确表示子类中的函数是对父类虚函数的重写,可以增强代码的可读性和可维护性

  2. 静态检查错误:使用 override 关键字可以让编译器在子类中发现对父类虚函数的重写错误,例如函数签名不匹配等。

  3. 避免意外创建新函数:如果没有使用 override 关键字,子类可能会意外地创建一个新的函数,而不是重写父类的虚函数。

虽然使用 override 关键字可以提高代码的可读性和安全性,但在某些情况下,如果没有使用 override 关键字,编译器仍然可以进行正确的函数匹配。所以使用 override 关键字是一种良好的编程实践,但不是强制要求。

注意:如果基类不是虚函数,则无法使用override修饰。

9.2 override 和 override = default

在C++中,只有特殊成员函数(默认构造函数、拷贝构造函数、移动构造函数、析构函数和赋值运算符)才能使用 = default; 语法进行默认。普通的成员函数不能使用 = default; 进行默认。

class Shape {
   public:
      Shape(){}
      virtual ~Shape(){  cout << "Parent class ~Shape :" <<endl; }
};
class Rectangle: public Shape{
   public:
      Rectangle(){ }
      virtual ~Rectangle() override = default; 

10.综合实例

#include <iostream> 
using namespace std;
 
class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      virtual ~Shape(){  cout << "Parent class ~Shape :" <<endl; }
      int area()
      {
         cout << "Parent class area :" <<endl;
         return 0;
      }
      virtual void test()       // 普通虚函数
      {
         cout << "Parent class test :" <<endl;
      }
      virtual void test2() = 0; // 纯虚函数:子类必须实现
};
class Rectangle: public Shape{
   public:
      Rectangle( int a=0, int b=0):Shape(a, b) { }
      virtual ~Rectangle() override = default; 
      int area ()
      { 
         cout << "Rectangle class area :" <<endl;
         return (width * height); 
      }
      void test() override 
      {
         cout << "Rectangle class test :" <<endl;
      }
      void test2() override
      {
         cout << "Rectangle class test2 :" <<endl;
      }
};
class Triangle: public Shape{
   public:
      Triangle( int a=0, int b=0):Shape(a, b) { }
      virtual ~Triangle(){  cout << "Parent class ~Triangle :" <<endl; }
      int area ()
      { 
         cout << "Triangle class area :" <<endl;
         return (width * height / 2); 
      }
      void test()   //可以不写override,但是写了一目了然
      {
         cout << "Triangle class test :" <<endl;
      }
      void test2()  //可以不写override,但是写了一目了然
      {
         cout << "Triangle class test2 :" <<endl;
      }
};
// 程序的主函数
int main( )
{
   Shape *shape;
   Rectangle rec(10,7);
   Triangle  tri(10,5);
 
    //普通函数:基类、子类:按类型
   shape = &rec;
   shape->area();//基类
 
   shape = &tri;
   shape->area();//基类
   
   //虚函数
   shape = &rec;
   shape->test();//子类
 
   shape = &tri;
   shape->test();//子类
   
   shape->Shape::test();//指定父类
   
   //纯虚函数约定的是子类必须实现、override约定的是重写基类
   
   return 0;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值