C++多态的构成条件与使用特例

本文详细介绍了C++中的多态性,包括基本条件、虚函数、重写及两个例外情况。多态需要通过基类指针或引用调用虚函数,并且派生类必须重写基类虚函数。重写时即使不加virtual关键字仍可构成重写,因为派生类继承了虚函数接口。此外,讨论了协变和析构函数的特殊情况,析构函数名在编译后统一为destructor以实现多态调用。
摘要由CSDN通过智能技术生成

(1)基本条件

多态有两个必要的构成条件:

  1. 必须通过基类的指针或者引用调用虚函数 (不能用对象直接调用)
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
(2)虚函数

virtual 修饰的类成员函数称为虚函数

  1. virtual关键字只在声明时加上,在类外实现时不能加
  2. 友元函数不属于成员函数,不能成为虚函数
  3. 静态成员函数就不能设置为虚函数。没有this指针无法拿到虚表,就无法实现多态,因此不能设置为虚函数
  4. 只有在需要实现多态时,才需要将成员函数设置成虚函数,否则没有必要
class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl;}
};
(3)重写

​  重写也叫覆盖。当派生类中有一个跟基类完全相同虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同) ,我们称子类的虚函数重写了父类的虚函数。

class Person {
public:
	virtual void BuyTicket() { cout << "买票-全价" << endl; }
};

class Student : public Person {
public:
	virtual void BuyTicket() { cout << "买票-半价" << endl; }
}

[特例]:
 派生类的虚函数不加virtual关键字时仍然可以构成重写(不建议这样写)

[原因]:

普通函数的继承是一种实现继承,即派生类继承了基类函数的具体实现。而虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写父类虚函数的具体实现
 派生类继承了基类的函数接口声明,与父类虚函数具有相同的属性,所以自动带有虚函数的属性。

[面试题]:下列程序输出的输出结果是 B→1

class A
{
public:
	virtual void func(int val = 1)
    { 
        std::cout<<"A->"<< val <<std::endl;
    }
    virtual void test(){ func();}
};
class B : public A
{
public:
	void func(int val = 0)
    { 
        std::cout<<"B->"<< val <<std::endl; 
    }
};

int main(int argc ,char* argv[])
{
    B*p = new B;
    p->test();
    return 0;
}

[分析]:

  1. p 调用 test() 不符合多态的构成条件,因此只是对父类函数的继承。
  2. test() 函数里对 func() 函数的调用,实际上是 (&A)→func(),因此满足多态的两个构造条件——即通过基类的指针调用虚函数,并且派生类构成重写
  3. 重写只是对父类虚函数具体实现的重写,但仍然保留父类虚函数的属性。因此子类 func 函数仍然是虚函数,且缺省值仍然是父类虚函数的缺省值。
(4)虚函数重写的两个例外
  1. 协变(基类与派生类虚函数返回值类型不同)

    ​ 返回值为父子类的指针或引用时,称为协变。

    class Person {
    public:
    	virtual Person* Person() 
    	{
    		cout << "~Person()" << endl;
    		return this;
    	}
    };
    
    class Student : public Person {
    public:
    	virtual Student* Student()
    	 {
        	  cout << "~Student()" << endl;
        	  return this;
    	 }
    };
    
  2. 析构函数的重写(基类与派生类析构函数的名字不同)

    ​ 编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。为什么要这样处理呢?我们来看下面的这个例子 :

    class Person{
    public:
    	~Person()
    	{cout << "~Person()" << endl;}
    };
    
    class Student:public Person{
    public:
    	~Student()
    	{cout << "~Student()" << endl;}
    };
    
    int main()
    {
    	Person* ptr = new Person();
    	delete ptr;
    
    	ptr = new Student();
    	delete ptr;
    
    	return 0;
    }
    

    image-20220904142526344
    [分析]:
    ​  正常情况下,调用的析构函数类型取决于变量或指针的类型,但是这样可能存在内存泄漏的风险,正如上面这个例子。所以我们期待实现析构函数的多态调用。

    ​  为了实现多态调用,派生类的虚函数必须与基类的虚函数名字相同。所以编译后,将析构函数的名称统一处理成destructor

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

罅隙`

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值