静态类型, 在 程序中声明时, 就已经确定了, 而且用于不会改变
动态类型, 他只针对指针, 可以变化 通过赋值来改变
(非指针)变量, 他只有 (静态类型) Foo f; 则f 的静态类型为Foo, 没有动态类型
(指针)变量, 他 既有(静态), 也有 (动态)类型
Fa * fa;
(此时, fa的静态类型为: Fa *
, 动态类型为 无 (或者说, 没有动态类型)
)
fa = new Son1;
(此时, fa 的动态类型为 Son1 *
)
fa = new Son2;
(此时, fa 的动态类型为 Son2 *
)
fa = nullptr;
(此时, fa 又没有动态类型了)
一般, virtual函数分为: (pure virtual
纯虚函数, 即子类必须实现)
(impure virtual
非纯虚函数, 若子类实现 而覆盖, 否则使用 父类实现的版本)
Son s;
Fa * fa = &s;
fa->func(); ' 会调用到: s.func() '
Son s;
Fa & fa = s; ' 或: const Fa & fa = Son(); <只有const引用, 可以绑定 临时对象> '
fa.func(); ' 会调用到: s.func() '
对于, dynamic_cast (即获取他的子类对象), 也有2个方式:
Fa * fa = new XX;
Son * son = dynamic_cast<Son *>( fa);
if( son != nullptr){
cout << "success cast";
}
try{
Fa * fa = new XX;
Son & son = dynamic_cast< Son &>( *fa);
}
catch( const std::bad_cast &){
cout << "failed cast";
}
与指针方式不同, (指针可以通过返回值是否是nullptr来判断, 因为如果转换失败则返回nullptr)dynamic_cast
的效率很慢!!!
dynamic_cast
的原理是: 基于class名称
的 字符串比较
即, 他会将内存对象
和 继承体系里的每个子类
, 进行strcmp
匹配
Fa * fa = new Son
我们令, 这个new Son
对象 为 son
这个fa
son->Fa::data
, 而不是son->data
!!!fa->func
会调用son->func()
) (否则: fa->func()
会调用son->Fa::func()
)即, 我们可能会误以为: 多态, 只是针对func
的多态; 对于数据data, 并无法使用多态
简单来说, 这种说法是正确的;
但是, 借助dynamic_cast
, 可以实现 对data
的多态!!!
Fa * fa = new Son
; 我们令, 这个new Son
对象 为 son
, 且: son->data = 1, son->Fa::data = 0
fa->data
可以来获取: son->Fa::data
son->data
如何通过 fa
来获取呢??Son * s = dynamic_cast< Son *>( fa);
, 此时, 这个s
对象, 和上面的son
对象, 是完全相同的!!s->data
就是 son->data
如何判断, 一个类是否是(多态类)呢
>=1
个 虚函数, 虚函数: 虚成员函数 或 虚析构函数
, 则该类是多态类如果一个类有虚成员函数, 则你必须显式的 将他的 析构函数, 也写成 虚函数!!! 下面会解释为什么
即, 只要是多态类,则你必须显式的 将他的 析构函数 写成 虚函数
虚函数, 在类内 最好是声明
, 不要在类中实现出来!!!
dynamic_cast< A *>( B *)
要求:
B
类, 必须是 多态类, 否则报错!!!dynamic_cast
对A
类, 没有任何限制!!! A 和 B
, 可以是完全毫无任何关系!!! 至少编译通过; 只是会强转失败而已
' 继承关系: SSON -> SON -> FA '
FA * ptr = new SSON;
using T = FA / SON / SONN;
T * ptr = new SSON;
using TO = ?;
TO * p = dynamic_cast< TO *>( ptr);
bool ret = (p == nullptr);
首先, ptr
一定是 多态类的 指针, 否则编译报错; 因为dynamic_cast<>( ?)
参数?, 必须是多态类指针.
T
的类型, 可以是FA / SON / SSON
, 都可以.
TO
的类型, 任意!! ST
也可以; 编译不报错.
比如T = FA, 即ptr
是一个 FA*
的指针. dynamic_cast
完全不关注, ptr
的类型!!! 所以`T 是 这3种的任意都可以
因为, dynamic_cast的作用, 就是 显现原型; 不管你这个ptr, 是哪位牛鬼蛇神, dynamic都会将他 显现原形
什么是 (原形)呢? 即, 他最初new的类型; 即(SSON)
即, dynamic 会将这个 ptr, 转换为 SSON* 类型. (因为你最初就是new SSON)
然后, 看TO类型, 是否是 SSON的 父类(含自己)!!! 这一点, 是完全根据 C++的强转规则的!!!
因为, 子类(指针/对象) 转换为 父类; 这是可以的; 但是反过来, 是绝对不可以的!!! 在任何情况下都是 违反c++强转规则的!!!
即, 只要TO是(FA / SON / SSON), 转换就是成功的; ret = true
举个例子:
using T = FA / SON;
T * ptr = new SON;
' T绝对不可以是SSON, 这和多态没有关系; 父类(指针/对象) 绝不可能转换为 子类!!! '
' 其实, 你的FA * ptr = new SON;(标准的多态代码), 这个代码 也和多态 没有关系!!! '
' 这只是一个(指针强转), 即将 子类(指针/对象) 转换为 父类; 这是可以的 '
' 这与多态没有关系, 多态也得符合C++的强转规则; 这里, 完全是C++的强转技巧 '
using TO = ?;
TO * p = dynamic_cast< TO *>( ptr);
bool ret = (p == nullptr);
此时, 只有TO = (FA / SON)时, ret = true; 因为, ptr 不管是FA/SON, dynamic将它还原成 SON类型
那么, SON, 根据 C++强转规则, 只能强转给 SON/FA *;
所以, 如果TO = SSON, ret = false !!!
dynamic一个场景是: 给你一个FA * ptr
的指针, (你不知道 他当初是怎么new的), 且FA是多态类
问, 他是否可以 强转到 SON类型;
好了, 比如说, 这个ptr 最初是 new SSON;
那么, SON * my = dynamic_cast< SON *>( ptr);
这就是可行的.
然后多说一句, my->func()
会调用 (SON的) 还是 (SSON的) 呢???
当然, 这个问题 和dynamic没有任何关系; 这完全是 多态的知识;
虽然SON 和 SSON, 都是多态类;
但是, 如果func 不是虚函数!!! 则my->func()调用 SON自己的
否则, func是虚函数, 则my->func()调用 SSON多态的
多态的操作是: FA * fa = new SON()
;
这样,即使这个FA,派生出 超级超级多的子类, 我们/工厂 只需要保存一根FA*
的指针即可。
这样,调用FA里的某个虚函数,就会优先调用他子类的实现。
思考: 当delete fa
时,fa肯定是调用的FA的析构函数。
但是,我们毕竟调用了 new SON()
,他里面有很多 他自己的data。 这就造成了: 内存泄漏(即,只释放了FA的data,而没有释放SON的data)
我们知道,虚函数的重写,是对 父类的同名、同参、同返回值
的函数。
即:virtual void FA::f(int); 和 virtual void SON::f(int);
这样,fa.f(int),他会优先调用: SON实现的。
但是,为了解决上面的内存泄漏问题。
虚函数的重写,不仅是对同名、同参、同返回值
的函数,他在处理析构函数时,也是有效的!(虽然函数名不同,但也属于重写)
首先,我们先回顾: 不带vitual的 继承时, SON的析构函数。
class FA{
~FA();
}
class SON : FA{
~SON();
}
SON * s = new SON();
delete s; ' 先调用~SON,后调用~FA() '
delete (FA *)s; ' 只调用~FA() '
则与虚函数无关,这就是c++对class的 构造和析构的定义。
SON()
:虽然只调用了SON,但其实在他执行前 会先调用FA()构造!! 只是你看不到,其实在初始化列表里。
~SON();
:虽然只调用了~SON,但其实他执行完 会再调用~FA()
!!这是你看不到。
因此,在虚函数这里 也是同理。 FA * fa = new SON();
我们只要保证: fa这个指针,他可以调用到~SON
即可!!! 不用让fa再调用~FA()
,因为子类的析构,一定会调用到父类的析构(与虚函数无关)
因此,虚函数的重写,不仅可以对同名、同参、同返回值
的函数 的重写,也可以对 析构函数的重写!!!(可以说,这是个例外)
但道理是一样的。 FA * f = new SON();
你的delete f
; 虽然看似调用的是:f.~FA()
,但因为他是虚函数,所以会调用子类的析构!! 即,其实是:f.~SON()
~SON(): 即调用了~SON,也调用了~FA()
class FA{
virtual ~FA(){}
};
class SON : FA{
virtual ~SON() override {}
};
假如说,不涉及到虚函数的 继承,有必要把析构 也变成virtual吗??
即使没有虚函数:FA * f = new SON()
,delete f
同样会导致:只调用~FA
!!
因此,只要有多态,父类的析构 必须是virtual!!
但是,当不涉及到 多态的继承时,不要对父类析构 设置成虚函数。
这样增大该类的sizeof
!!!!
虚函数开销是很大的。
总结:
polymorphic
时,父类的析构函数 必须声明为:virtualstring, vector...
,他们里面都是没有虚函数的!!!class FA{
virtual void f(){}
FA(){
f();
}
};
class SON : FA{ ... }
当SON s
时,由于FA的构造 是优先于 SON的构造,当FA的构造里 使用了f()
这个虚函数。
那么,此时的FA构造函数里的 这个f虚函数,他是指的:FA的、还是SON的呢?
从虚函数的使用来看,用户肯定是想让他 代指 SON实现的版本,毕竟虚函数的作用就在于此。
但是,此时SON还没有构造出来!!!
FA构造函数里 所使用的虚函数,一定是FA实现的版本,不会是子类版本!!
其实最本质的原因是: SON s
,这个s,会经历两个阶段:(先是FA父类的构造函数)(然后是SON子类的构造函数)
在第一个阶段,其实这个s
对象,他的类型是: FA,而不是SON!!!
到了第二个阶段,s
才是SON类型。
所以,妄想在 父类构造里,调用子类的虚函数,这是不可能的!!!
同样,当在析构函数里 调用虚函数:
SON s;
当s销毁时,会经历两个阶段:(先是SON子类的析构函数)(然后是FA子类的析构函数)
在第一个阶段,s
的类型是 SON。
但到了第二个阶段,s
的类型是:FA!!! 你在FA析构函数里,调用虚函数,他肯定不会是子类的,因为当前s
就是父类类型!
即,不要在构造/析构函数里,调用虚函数; 这个虚函数,并不会调用“子类”的实现!!!
一旦一个类有 virtual void func() = 0;
纯虚函数, 这个类, 就是抽象类abstract
class Fa{
virtual void func() = 0;
}
Fa是抽象类, 则: Fa f;
和 new Fa
都是不允许的!!!
如果 子类不实现 纯虚函数, 则子类 也是抽象类
class Son : Fa{
};
此时, Son也是抽象类; 因为他没有实现func
class FA{
virtual void func(){ DE<< "im fa..."; ED; }
};
class SON : public FA{
virtual void func(){ DE<< "im son..."; ED; }
};
我们知道,最简单的多态是:
SON * son = new SON;
FA * fa = son;
此时fa->func()
会调用 子类SON的func
函数!!
这种情况是: 子类的 指针, 强转为: 父类的 指针
但注意:
SON son;
FA fa = son;
这不是多态!!! fa.func()
还是会调用父类的func函数,这是普通继承情况下的 “强转”
(即,将son.FA::data
赋值给 fa.data
)
FA fa = *(new SON);
这也不是多态!!
虽然,FA里有虚函数,但这些写法 都不是多态!!
还有一种多态:
SON * son = new SON;
FA & fa = *son;
此时fa.func()
会调用 子类SON的func
函数!!
这种情况是: 子类的 指针的 对象,强转为:父类的 对象
即,一共有2种 多态的形式。
形式1: FA * fa = new SON; (等价于: SON s; FA * fa = &s; )
形式2: FA & fa = *( new SON ); (等价于: SON s; FA & fa = s; )
注意一种特殊情况: FA fa = *(new SON);
,这不是多态!!!
总结是:
fa
必须是指针!! (引用,本质也是一种指针!)FA * fa = new SON
,* fa
的类型 依然是FA
类型!!!分析这个原因。
FA t
: t的类型,一定是唯一的
什么意思呢? 不管你代码怎么写,t
的类型 一定是FA
类型!!
这种类型,也称为: 编译期 就可以 确定的类型(不管编译期/运行期,他的类型 都是唯一固定的)
即使你:FA t = *(new SON)
,这并不是多态!! !
因为,t的类型是:FA
。 他的类型FA
就决定了:t的类型 是固定唯一的。
FA * t
:虽然t的类型,肯定是:FA *
类型。这肯定是不变的!!符合上面的规则
(不管你代码怎么写,t的类型 肯定是:FA *
)
但是,唯一不同的是: * t
,即他所指向的内存的 类型。* t
的类型,是可以有 多种情况的
(当然,同一时刻 * t
的类型是固定唯一的。但* t
的类型,是可以动态的一直变化!)
什么意思呢?
FA * t = new SON1;
t = new SON2;
t = new SON3;
* t
的类型typeid(* t)
,他是一直在变化的!!! * fa
先是SON1类型,然后是SON2类型,最后是SON3类型。
即使到了运行期,*t
的类型 也不是固定不变的!!
但注意,以上说法的前提是:FA里有虚函数!!!如果FA里没有虚函数,*t
不管是new SON
,*t
的类型 始终为FA!!!
FA * t;
if( rand() & 1 ) t = new SON2;
else t = new SON3;
甚至这种写法,每次运行,*t
的类型 还说不准,可能是SON2,可能是SON3(当然,前提是:FA里有虚函数。否则,*t
始终是FA类型)
FA & fa = *(new SON);
其实从上面的说明,也可以推导出 引用的情况
(因为:引用的本质,就是指针!!!)
既然,指针所指的对象 不一定是该指针的类型,自然引用所引用的对象 也不一定是该引用的类型。
比如上面这行代码,(如果FA里有虚函数,则fa
类型是SON。否则,fa
类型是FA)
当出现(FA * fa
中 * fa
所指对象的 类型 不唯一、FA & fa
中 fa
所引用的对象 类型 不唯一)的情况时(我们暂称这种情况为:动态类型)
当出现 “动态类型”时,一定有: FA里面 肯定有:虚函数!!!
即,只有“多态” 只有“FA里有虚函数”,才有 “动态类型”。否则,即使是FA * fa = new SON
,* fa
也是 静态类型。
Run Time Type Identification
(其实就是我们上面定义的: “动态类型”,他只有在“多态”的情况下 会存在这种现象)
由于他的类型,是在动态变化的。所以,我们如何得到他 此时此刻的类型呢? 有2种方式
dynamic_cast
typeid
比如我们有个需求(当然是在“多态”的环境下,即FA里有虚函数) dynamic_cast
的对象, 必须要有 (虚函数)!!!
FA * fa;
if( rand() ) fa = new SON1;
else fa = new SON2;
我们想要确定,最终这个fa,到底是指向SON1,还是指向SON2呢???
这是第1个需求
第2个需求(重要)是:
FA * fa = new SON
new SON
这块内存里有: (FA::data)(SON::data)(FA:: func)(SON::func<非虚函数>)
而我们仅通过fa
,无法访问:SON::data
和 SON::func<非虚函数>
这不太好,毕竟,我们确实是new的这块内存,这块内存里 确实是有这些东西的!!!
即, 按理说,此时 将fa
从FA *
类型 转换为 SON *
类型,是合乎情理的!
虽然,直接SON * son = (SON *) fa
后,son是可以访问SON里独有的数据/函数的!!
但有一点: 由于fa可以指向很多对象SON1, SON2, ...
如果你并不知道 这个fa 具体指向哪种类型,那么,显然你无法写出: (SON *)fa
这样的代码。
这些写法也很危险,(假如,fa实际指向SON1,那么(SON *)fa
是会报错的! 不同子类 不能强转,数据等都不一样)
基于以上的两个需求,引入:dynamic_cast
他的作用是: 判断fa
这个指针,具体是指向哪个类型
FA * fa = new SON;
SON1 * son1 = dynamic_cast< SON1 * >( fa );
if( son1 == nullptr ) ' 说明:fa指向的内存类型,并不是SON1 '
SON * son = dynamic_cast< SON * >( fa );
if( son != nullptr ){ ' 说明:fa指向的内存类型,是SON类型 '
son->son_data;
son->son_func();
' 调用son里,自己独有的数据/函数。这是通过fa无法做到的。'
}
----------------------------
SON s;
FA * fa = &s; ' 这个是多态!(FA里有虚函数) '
SON * ss = dynamic_cast< SON* >( fa ); ' 可以转换回去! (毕竟SON s,本来就是SON *) '
注意这种强转,与以往都不同。
以往的强转是: (从大)可以 往(小的)转换。
因为,我很大(子类) 本来就包含(父类),自然可以往小的(父类) 去转换。
但这里是: 从(小的)FA*
,转换为: SON*
(大的)
这很危险。 显然,只能发生在: 多态的情况下。
而且,虽然他是FA*
类型 很小,但其实他指向的内存 是SON
很大
所以,这块内存 本来就是SON
类型,再转换到SON
类型,这显然是合理的。(有点,物归原主的感觉)
FA * fa = new SON;
if( typeid( *fa ) == typeid( SON ) )
' 说明,fa指向的内存 是SON类型 '
只要类中有 >=1个 虚函数,这个类就会产生 指向各个虚函数的指针。
有X个虚函数,就有X个指向各虚函数的指针。
这X个指针,会被放到 虚函数表里(virtual table)
这个虚函数表,保存在最后生成的exe文件里,程序执行时 载入内存
系统,会自动 往类/对象中,添加一个 “指向 虚函数表”的 指针。
这个 指向虚函数表的指针,不是static的,每个对象都有 他是占用类内存的
’ 只要有 虚函数, 则sizeof(ST)里 一定有8字节, 为 指向虚函数表的 指针 ’
X个虚函数 (不占用 类内存)
-> 产生 X个虚函数指针 (不占用 类内存)
-> 产生 1个 虚函数表 (不占用 类内存)
-> 产生 1个 指向该虚函数表的 指针 (占用 类内存)