自学内容网 自学内容网

C++ 多态 初学笔记

概念:
允许使用统一的接口来操作不同类型的对象。

多态的作用:
减少重复代码,提高代码扩展性

静态多态:
函数重载
函数模板

动态多态
继承
虚函数

虚函数:
动态绑定
静态绑定
个人记法(可能有误):动态绑定是调用谁的 就是谁的(被调用);静态绑定是谁调用的 就是谁的(被调用);

关键字记忆:
virtual
override 强制要求检查是虚函数的重写(继承的类中没有这个虚函数就报错)
final 终止函数的重载(此类被继承后,这个函数就不再是虚函数了)
= = = = = = = = = = = = = = = = = = = = = = = = = = = =
预备知识:从内存上来看,一个派生类创建时,会从基类开始构造,内存中先放继承自 基类的变量、再放派生类的变量,存到一块连续的内存中;

虚函数

关键字:virtual

在基类中的函数加上virtual关键字,表示其可以被派生类重写;
重写就是重新实现;
这不是覆盖,是动态的绑定;

当一个基类的指针指向派生类时,调用虚函数会有优先调用派生类重写的虚函数;

注意:这里如果说,只是在派生类中写了一个一样的函数,没有加virtual关键字,那么,调用的就会是基类中函数的实现;

这就实现了动态的绑定
虽然指针的类型是基类:
但根据其指向的改变,具体调用的函数也会改变;

注:如果派生类没有重写基类的虚函数,那么调用的就还是基类的虚函数

番外:内存切片
如果如果不是用指针,而是传递对象来调用,虚函数就会失效,

简单来说,派生类是继承了基类的,基类对象的内容一定比派生类对象的内容少
直接传递对象的时候,基类对象没法完全复制派生类的内容,就会切掉多出来的一部分,就会导致调用的虚函数都是基类的实现;

指针调用和对象调用的示例:
void usevir(base* obj){obj->虚函数}
void usevir(base obj){obj->虚函数}

虚函数的使用条件

(1)virtual只能写在类的内部声明或者 定义,不能把virtual写在类的外部定义中;

(2)调用 类的对象是无法使用虚函数的(内存切片问题),必须使用基类指针实现虚函数的调用

(3)虚函数在派生类和基类中必须具有相同的名称和参数列表

(4)虚函数在派生类和基类中返回值 的要求基本一致;
**注:**在函数重载中,单是返回类型不同是不算做重载的,但在虚函数这样,如果返回值类型不同,就不算是虚函数的重写
但是当返回类型为类类型的指针 和引用 时 例外

(5)虚函数不能是函数模板

虚函数详解

不讲底层,只看现象(不纠结原理,先理解逻辑,记下来):

(1)在构造、析构函数中调用虚函数

构造时: 在基类和派生类的构造函数中调用虚函数;
由于先构造基类,所以基类的构造函数调用的是基类中虚函数的实现(这时候派生类还没有构造,虚函数为静态绑定)
构造派生类时,虚函数动态绑定,调用的是派生类中虚函数的实现。
析构时: 在基类和派生类的析构函数中调用虚函数;
先析构派生类,此时调用的是派生类虚函数的实现;
析构基类时,调用基类的虚函数实现(此时派生类已经被析构了,虚函数回到静态绑定的状态)

(2)调用虚函数的基类版本
想要派生类对象调用基类的虚函数,需要用 作用域运算符::

(3)默认实参在虚函数中的错误
在基类中的虚函数有默认实参,比如(int a=10)
派生类在重写时,如果写上(int a=11)
此时依旧是基类中的默认实参有效,也就是默认a=10

以基类中的默认实参为准

(4)释放含有虚函数的派生对象;
用基类指针 指向 派生类对象
例如:Base *p = new Child();
由于p的类型是Base,所以就直接调用了基类的析构函数,

造成的影响是:内存泄露,
因为派生类的析构没有调用,派生类定义的变量 的内存没有释放;

解决方法是, 将基类的析构函数定义为虚函数

对象多态

概念:
对象多态
方法多态

基类和派生类转换时的问题:

(1)隐式类型转换,向下转型不允许 不允许的原因是风险不可控制 为什么呢?
简单来说,基类是不是会比派生类内容更少(因为派生类包含了基类的内容),那么基类指针所控制的范围就会比派生类小,
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
让基类指针指向派生类,应用时,访问都会是合法的,基类指针访问的就是派生类里面继承下来的内容
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
让派生类指针指向基类,但基类就不一定能满足你这个时候派生类指针的应用需求;
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

如有需要, 可以进行
强制类型转换(base*)_child、
静态类型转换static_cast<base*>(_child)
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
(2)向上转换时,如果派生方式是private,那么隐式转换也是不允许的;

(3)静态类型转换时,会进行检测,当派生方式是private时、或者虚基类时,不允许转换

重点:多重继承时,类型转换要小心出错;

多重继承时,类型转换的练习

此练习的目的在于 认识到胡乱使用类型转换 会带来无尽的折磨。。。

(1)情况1:

class T
{
public:
int a = 100;
};

class T1 :public T{};

class T2 : public T{};

class TT : public T1, public T{};
TT test;

void* p{ &test };//直接把TT类的对象转型为T类型,可能会报错:转换不明确

T* ptrT{ (T*)p };

ptrT->a = 998;

std::cout << test.::T1::a << std::endl;//输出998

std::cout << test.::T::a << std::endl;//输出100

T1* test1{ (T1*)&test };

std::cout << test1->a << std::endl;//输出998

T2* test2{ (T2*)&test }; //注意这里是用强制类型转换;TT类没有继承T2类
//如果用静态类型转换,这就是不被允许的(类型转换无效)

std::cout << test2->a << std::endl;//输出998

在这个多重继承的示例中,通过向上转型修改a的值时,修改的是继承自T1中a的值(T1::T::a)
那如果把继承时的前后顺序交换呢?请看(2)

从这里也可以看出,强制类型转换并不安全,用错但不报错

(2)情况2:

class TT : public T,public T1{};
TT test;

void* p{ &test };

T* ptrT{ (T*)p };

ptrT->a = 998;

std::cout << test.::T1::a << std::endl;//输出100

std::cout << test.::T::a << std::endl;//输出998

T1* test1{ (T1*)&test };

std::cout << test1->a << std::endl;//输出100

T2* test2{ (T2*)&test }; 

std::cout << test2->a << std::endl;//输出998

总结,反正就挺乱的,实际项目中遇到多重继承的情况一定要理清楚啊!!!;

都写到这里了,我在放两个示例吧;和上面大差不差,已经难得分析了

(3)情况3:

class TT : public T1,public T2{};
TT test;

void* p{ &test };

T* ptrT{ (T*)p };

ptrT->a = 998;

std::cout << test.::T1::a << std::endl;//输出998

std::cout << test.::T::a << std::endl;//输出998

T1* test1{ (T1*)&test };

std::cout << test1->a << std::endl;//输出998

T2* test2{ (T2*)&test }; 

std::cout << test2->a << std::endl;//输出100

(4)情况4:

class TT : public T2,public T1{};
TT test;

void* p{ &test };

T* ptrT{ (T*)p };

ptrT->a = 998;

std::cout << test.::T1::a << std::endl;//输出100

std::cout << test.::T::a << std::endl;//输出998

T1* test1{ (T1*)&test };

std::cout << test1->a << std::endl;//输出100

T2* test2{ (T2*)&test }; 

std::cout << test2->a << std::endl;//输出998

对象多态 动态强制转换 dynamic_cast

在前面的示例练习中,我使用的时强制类型转换,
也提到如果使用static_cast就会被编译器检查出错误;

那如何在对象多态中安全的进行类型转换呢
就是用dynamic_cast

dynamic_cast只能用于支持方法多态类型的指针,如果转换成功,返回指针,转换失败,返回nullptr

一般在两种情况中要使用dynamic_cast
1》向下转换downcast
2》跨类转换crosscast

注意点:
1》不要转换this指针
2》当dynamic_cast用于转换引用时,转换失败会抛出异常,所以也不推荐转换引用

typeid

typeid操作符号用来获取类型信息,在多态中使用的注意事项:
1》对于对象指针,要用间接运算符 解除指针
2》支持 对象的引用
3》继承关系中的类需要支持多态,typeid才有效果(就是写得有虚函数)

抽象类

1》拥有纯虚函数的类 称为抽象类
2》抽象类不能创建实例,但可以用抽象类的指针或引用作为返回、参数;

3》抽象类的构造函数 不能实际使用(因为不能创建实例嘛),所以推荐把抽象类的构造函数定义为protected

4》抽象类的派生类如果没有定义纯虚函数,则这个派生类依然是抽象类

纯虚函数:
只有声明没有定义的虚函数,写法:
virtual void _function() = 0;

接口类:
类中大部分函数 定义为纯虚函数,这种类被称为接口类

类的成员函数的函数指针

提示:
类的普通成员函数 的函数指针

类的静态成员函数 的函数指针

区别在于函数有没有this指针

声明:
typedef void (MyClass::*funcPtr)();//如果是类的静态成员变量就不用加 作用域
用法:
funcPtr = &MyClass::printHello;


原文地址:https://blog.csdn.net/weixin_44050362/article/details/145169426

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!