cast函数_C++中的“父类指针”和dynamic_cast

e207387425b503c68a31efaefcb940ef.png

这是通过知乎自动图文视频生成的本文视频,没想到效果还不错

知乎视频​www.zhihu.com

什么是父类指针

父类指针也可以称为基类指针,当父类(基类)指针指向派生类(子类)指针的时候,可以触发“多态的效果”。不过本文的重点不在“多态”,而是聊聊当父类指针和子类指针互相赋值时需要注意的问题。

915cc73175f811782c2cd83ad926ad64.png

废话不多说,直接看代码~

假设我们有两个类,一个是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()

但是需要注意,将子类指针赋予父类之后,是不能够访问到子类自己的成员函数的:

80b7062e79316162944b00b66850551e.png

如果你尝试通过父类指针调用子类自己的方法则编译无法通过。关于这点,较为直观的解释借用了知乎Shiyang Ao的回答:

19301d05370e277f29eba0239db4d094.png

派生类的数据结构显然与基类在结构上是有关联的。

7624e6b17e23c32ceb5452b7441e2d5f.png

子类指针能指向父类对象吗?

正如上文所说,派生类指针原则上不能指向基类对象,但其实我们仍然可以强行转换:

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_funderived_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

c60508f5e265085b4c96727df432ad17.png

不过话说回来,如果这个基类指针是由派生类转换过来的,那么是可以正常转换的,即使派生类包含了非虚函数以及需要解引用,下面的代码是没有任何问题的:

Base* base_ptr = new Derived;
base_ptr->fun();

Derived* derived_ptr = static_cast<Derived*>(base_ptr);
derived_ptr->fun();
derived_ptr->derived_fun();
derived_ptr->derived_fun_fun();

从上面的示例代码可以得到一些结论:

  • 子类实例指针转型为父类实例指针,不需要显式转换;
  • 父类指针转换为子类指针是不建议的,如果确实需要则建议使用dynamic_cast

那啥是dynamic_cast

dynamic_cast

dynamic_cast是啥,dynamic_cast这个操作运算符主要是用来执行安全向下转型,如果我们的转换目标是指针类型且转换失败,那么得到的结果为nullptr,如果转换目标是引用类型而且失败了,则会抛出std::bad_cast异常。

也就是说,我们如果将Base类型的对象,通过dynamic_cast转换为Derived类型的对象时,我们可以看到下图中from_base_ptr这个指针是NULL,这个NULL用来来通知调用者对指针做 dynamic_cast失败了。

f5d2a4bc1f4e159717191fd6f71658e3.png

不过关于dynamic_cast运算符啊,我们最好还是少用,毕竟谷歌爸爸的Google Style Guides里头可是说,当你使用了dynamic_cast运算符,代表着你的设计不合理,需要重新设计~

另外dynamic_cast是无法用于非多态的对象的,这点需要注意。总而言之,我们为什么要使用dynamic_cast呢,那就是将这个脏活交给程序去处理,这显然是不好的。

0cac507a35218936bcf675a91ae269e6.png

因此,在实际应用中,能在编译时解决掉的问题不要留到运行时、能用多态搞定的事情也没必要使用dynamic_cast或typeid哦。

作者水平有限,文章如有错误请及时告知哦。

欢迎访问 ”oldpan博客“ 公众号 和 Oldpan博客一起交流 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值