C++多态

1.虚函数

这个地方的虚函数的virtual和前面虚拟继承的关键字virtual没有任何关联

只有成员函数才可以变成虚函数

虚函数可以完成重写(覆盖)

虚函数就是在函数前面+一个virtual

 我们先来看这串代码 很神奇

func接受Person匿名对象和Student的匿名对象的切片(接收类型是Person)

我们前面讲过

派生类的切片是不能调用派生类的函数  也就是不能调用 Student::BuyTicket

也就是这个地方基类和派生类都只能调用基类的Person::BuyTicket;

但是调用的确是不同的BuyTicket

正常来说这个地方是p去调用BuyTicket()

但是这个地方p是Person类型 所以按照隐藏来算  调用的也应该是Person::BuyTicket;

但是这个地方我们不+Student::它也会自动调用Student::BuyTicket

2.多态

多态的条件 

1.调用函数重写的虚函数

2.基类指针或者引用

多态 :不同对象传过去 调用不同函数

多态调用的是看的指向的对象  

普通对象 看到是它的类型!

 

 重写的条件本来是虚函数+三同(返回值相同 函数名相同  参数相同) 但是有一些例外

这个地方的三同不包括this指针

1.派生类的函数前面可以不+virtual  (但是最好还是加上)

2.协变 返回值可以不同 但是要求返回值必须是父子关系和引用

(1)必须同时是父子关系 不可颠倒过来()

比如: 父类的virtual函数返回值子类 子类的virtual函数返回值是父类 不可以

(2)要同时为指针和引用 不可以一个是指针 一个是引用

(3)可以不是本身类的父子类 比如 如下图

void func(const Person& p)
{
	p.Person::BuyTicket();
}

指定调用也会导致多态不满足 

 3.析构函数

 析构函数可以是虚函数吗?为什么要是虚函数?

析构函数+virtual 是不是虚函数重写?

是因为析构函数都被处理成destructor这个统一的名字了

为什么要构成重写呢?看下面这种场景

这个地方我们为什么不会去调用派生类的析构函数?

 delete 本质上是调用p的析构函数 再调用 operator delete(p)  

但是这个地方p是普通Person 类型 所以它只会调用Person的析构函数

不会调用Student析构函数 如果Student自己特有的指针没有通过Student析构函数释放 就会导致内存泄漏

我们期望上面两个delete  p能够调用正确的析构函数 而不是像普通类型一样

去调用其类型所属的析构函数

我们期望Person类和Student类的析构函数是一个多态调用 而不是普通调用

所以这个地方要构成多态  就要构成重写

要构成重写 原本的析构函数可不可以?

当然不可以(要求三同 函数名不同) 所以要统一处理成destructor

4.C++ 11   override和final

1.final  :修饰虚函数不能再被重写

修饰类 +final的类叫最终类 最终类不能被继承

2.override:检查派生类虚函数是否重写了某个虚函数,如果没有重写编译器会报错

5.重载,覆盖(重写),隐藏(重定义)的对比

6.虚函数表

这个地方为什么sizeof(Base)是8? 

因为这个地方有虚函数指针+对齐

这个地方和虚继承无关

虚表里面存的是虚函数的地址

虚函数本质上还是被放在代码段的

我们来看一下重写!

如果被重写了Johnson的虚函数指针就变成重写的虚函数指针(也就是把原来的虚函数指针覆盖了)

原本Johnson(子类)应该是父类继承过来的虚表地址 应该指向父类的虚函数

但是被重写覆盖成新的虚表地址 指向的是子类的虚函数!

因此调用的是不同的虚函数!

 

多态的本质就是根据不同的虚表去调用不同的虚函数 

子类就是把它的切片 也就是子类中父类那一部分

无论是父类还是被切片的子类 看起来都是一样的

普通调用 在编译的时候确定地址  

多态则在运行时到指向对象的虚函数表中找调用的函数地址

7. 设计不能被继承的类

如何设计出一个无法被继承的类?

思路1:

把构造函数/析构函数  私有化(这里以构造函数为例)

class A
{
public:
	static A Creat()
	{
		return A();
	}
private:
	A()
	{
	}
};
int  main(void)
{
	A::Creat();
	return 0;
}

 但是这个地方就引发了一个问题 就是先有鸡还是先有蛋的问题?

这个地方是现有对象 才能用Creat函数 但是你要有对象就要先构造先调用Creat

静态的可以解决这个问题(上图和下图但是代码都是静态的方法)

#include <iostream>
using namespace std;
class A {
public:
    static A Creat() {
        return A();
    }
private:
    A() {}
};

class B {};

int main() {
    // 使用静态函数创建非匿名对象
    A nonAnonymousObj = A::Creat();
    return 0;
}

析构函数也可以

#include<iostream>
using namespace std;

class A {
public:
    static void Destroy(A* obj) {
        delete obj;
    }

private:
    ~A() {
        cout << "析构" << std::endl;
    }
};

int main() {
    A* p = new A;
    A::Destroy(p);
    p = nullptr;
    return 0;
}
#include <iostream>

class A {
public:
    void Destroy() {
       // delete this;  这两种方法本质上是一致的
        this->~A();
        operator delete(this);
    }
private:
    ~A() {
        std::cout << "析构" << std::endl;
    }
};

int main() {
    A* p = new A;
    p->Destroy();
    p = nullptr;
    return 0;
}

思路2:

最终类 +final

8.虚函数重写的是什么?

虚函数重写的是实现!

我们来看这个题目

 A是基类   B是派生类

p是B类型的指针   p首先访问test()

test的函数体内是func()

这个时候func构成虚函数重写吗?

重写是虚函数+协同/三同

首先 虚函数没问题

三同也没问题(三同是参数类型  返回值 函数名相同  和参数名 缺省值无关

多态的条件是

虚函数重写+基类的指针或引用

这个地方test传的参数是this指针   this指针是A类型的(指向的是B类中A类的那部分)

这个地方的test函数不是拷贝一份给派生类   而是在公共代码段

A类型的指针  也就是基类的指针 加上虚函数重写

所以构成多态

函数重写的是实现

函数头用的都是父类的(无论是基类还是派生类的指针/引用)

这个地方指针指向的是派生类的对象

所以调用的是

但是函数体却是派生类的(原本继承的是基类的函数体  但是被重写/覆盖了!)

 所以这题最后选A

这个地方派生类调用重写的虚函数  函数头用的是父类的   但是其this指针还是子类的

因为派生类重写的虚函数里面可能会用到派生类特有的成员

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值