C++中的“父类指针”和dynamic_cast
这是通过知乎自动图文视频生成的本文视频,没想到效果还不错
什么是父类指针
父类指针也可以称为基类指针,当父类(基类)指针指向派生类(子类)指针的时候,可以触发“多态的效果”。不过本文的重点不在“多态”,而是聊聊当父类指针和子类指针互相赋值时需要注意的问题。
废话不多说,直接看代码~
假设我们有两个类,一个是Base父类,另一个是Derived子类。
class Base //父类
public:
virtual int fun() ; // 虚函数
int Base::fun() // virtual 字段不用在函数体时定义
std::cout << "Base::fun()" << std::endl;
class Derived: public Base //子类
public:
int fun();
int derived_fun();
int derived_fun_fun();
private:
int dummy = 1;
// 继承父类的函数
int Derived::fun() {
std::cout << "Derived::fun()" << std::endl;
// 子类独有函数
int Derived::derived_fun() {
std::cout << "Derived::derived_fun()" << std::endl;
std::cout << this->dummy << std::endl;
// 子类独有函数 且调用父类继承的函数
int Derived::derived_fun_fun() {
std::cout << "Derived::derived_fun_fun()" << std::endl;
fun();
好了,类的定义完了,我们来执行看下结果:
Base base;
base.fun();
Derived derived;
derived.fun();
很简单可以预料到输出结果,其中子类Derived重载了父类Base的fun()函数:
Base::fun()
Derived::fun()
然后我们尝试执行将子类的指针(或者地址)赋予给父类指针:
Base* base_ptr = new Derived; // 赋予指针
base_ptr->fun();
base_ptr = &derived; // 赋予地址
base_ptr->fun();
输出也很容易可以预料到,这里其实就是多态,如果我们将不同的子类指针赋予父类指针,那么也将会执行不同子类重载后的函数:
Derived::fun()
Derived::fun()
但是需要注意,将子类指针赋予父类之后,是不能够访问到子类自己的成员函数的:
如果你尝试通过父类指针调用子类自己的方法则编译无法通过。关于这点,较为直观的解释借用了知乎Shiyang Ao的 回答 :
派生类的数据结构显然与基类在结构上是有关联的。
子类指针能指向父类对象吗?
正如上文所说,派生类指针原则上不能指向基类对象,但其实我们仍然可以强行转换:
Base base;
base.fun();
Derived* from_base_ptr = static_cast<Derived *>(&base);
from_base_ptr->fun();
from_base_ptr->derived_fun();
from_base_ptr->derived_fun_fun();
那么这样会不会出错呢,base这个对象由基类创造,是不可能包含 derived_fun 和 derived_fun_fun 的定义,但如果我们执行上述代码…
竟然打印出来了!
Derived::derived_fun()
Derived::derived_fun_fun()
Derived::fun()
为什么会有这种情况,其实这段代码在编译的时候触发了C++的静态绑定,也就是说类中的非虚函数,是在编译的时候绑定函数地址,即在编译器阶段就已经确定了函数的地址。实际上下面的这段代码在编译后是这个样子的:
// 编译前
class Derived: public Base //子类
public:
int fun();
int derived_fun();
int derived_fun_fun();
// 编译后
public:
int fun(Derived* this);
int derived_fun(Derived* this);
int derived_fun_fun(Derived* this);
而我们实际调用的时候,即使 derived_fun(Derived* this) 中Derived*参数传过来的不对,但在该函数内部并没有使用该参数,所以其不影响函数的运行(derived_fun函数内部仅仅执行了打印函数而已),当然也不会报错了。
但如果Derived的成员函数用到了其成员变量,亦或是这个函数是虚函数,我们再试试看:
class Derived: public Base //子类
public:
int fun();
int derived_fun();
int derived_fun_fun();
private:
// 这里加一个成员变量 初始化为1
int dummy = 1;
// 子类独有函数中还需要打印 this->dummy
int Derived::derived_fun() {
std::cout << "Derived::derived_fun()" << std::endl;
std::cout << this->dummy << std::endl;
这样的话,再执行上述的演示代码。
Base base;
base.fun();
Derived* from_base_ptr = static_cast<Derived *>(&base);
from_base_ptr->fun();
from_base_ptr->derived_fun();
from_base_ptr->derived_fun_fun();
打印结果:
Base::fun()
Derived::derived_fun()
175771685
Derived::derived_fun_fun()
仍然可以执行,但dummy变成了一个随机数(类中初始化为1)!
并且如果 derived_fun 函数设为虚函数 virtual int derived_fun() ,那么上述代码执行到 from_base_ptr->derived_fun() 时会触发**EXC_BAD_ACCESS (code=1, address=0x0)**的错误!
所以说,我们通过 static_cast 强行将父类对象转换为子类会导致一些奇怪的现象(不使用static_cast则无法进行转换),而且编译器是禁止我们这么做的(使用static_cast),会建议我们使用 dynamic_cast :
不过话说回来,如果这个基类指针是由派生类转换过来的,那么是可以正常转换的,即使派生类包含了非虚函数以及需要解引用,下面的代码是没有任何问题的:
Base* base_ptr = new Derived;
base_ptr->fun();