C++学习笔记--继承的赋值兼容和函数重写

首先说说父类子类间的赋值兼容问题,什么意思呢,就是子类对象可以当做父类对象使用的兼容性问题。

子类对象可以直接赋值给父类对象。

子类对象可以直接初始化父类对象。

父类指针可以直接指向子类对象。

父类引用可以直接引用子类对象。

我们通过小例子说明这几个规则,首先定义一个父类一个子类,他们之间有继承关系。

class Parent
{
public:
    int mi;
    
    void add(int i)
    {
        mi += i;
    }
    
    void add(int a, int b)
    {
        mi += (a + b);
    }
};

class Child : public Parent
{
public:
    int mv;
    
    void add(int x, int y, int z)
    {
        mv += (x + y + z);
    }
};
按照我们前面说的,子类中有与父类中的同名函数,所以子类中的同名函数会覆盖父类中的同名函数,除非使用作用域分辨符来引用。

首先测试前面的规则是否可用:

    Parent p;
    Child c;
    
    p = c;           //子类对象直接赋值给父类对象
    Parent p1(c);    //子类对象初始化父类对象
    Parent& rp = c;  //父类引用可以直接引用子类对象
    Parent* pp = &c; //父类指针可以直接指向子类对象
我们发现编译器是能够通过编译的,表明规则是可用的,父类和子类之间是存在赋值兼容性的。

问题就是在不断的探索中发现的,我们执行下面的代码;

    rp.mi = 100;//使用父类引用
    rp.add(5);           
    rp.add(10, 10);    
看看编译器能否通过编译,惊讶的发现编译竟然没错,那么问题来了,子类中并没有带有一个参数和带有两个参数的add函数,说好的会同名覆盖呢?为什么不报错?

再看看执行这几条代码情况会怎么样?

    pp->mv = 1000;//使用父类指针
    pp->add(1, 10, 100);
失望的是,编译结果报错了,这究竟是为什么?

通过了解终于知道了原因:

当父类指针(引用)指向(替代)子类对象时:

子类对象退化成父类对象。

此时父类指针(引用)只能访问父类中定义的成员。

因此也可以直接访问子类中被覆盖的父类成员。


在继承中有一个特殊的同名函数,因为在子类中可以重定义父类中已经存在的成员函数,这种重定义发生在继承中,叫做函数重写,函数重写是同名覆盖的一种特殊情况。
但是有必要定义函数重写吗?当然有必要,当我们定义了一个子类对象并需要打印子类对象特有的信息时,就可以对函数重写,要是不进行重写,那定义的是子类对象,可打印的却是父类信息。当函数重写遇上赋值兼容后会发生什么呢?

我们在父类和子类里各自定义一个打印自身信息的print函数。

    void print()
    {
        cout << "I'm Parent." << endl;
    }
    void print()
    {
        cout << "I'm Child." << endl;
    }
定义一个父类对象p和一个子类对象c,分别执行:

p.print();

c.print();

输出了:

I'm Parent.
I'm Child.

我们注释子类中的print函数后再次调用同样的语句看看,

输出了:

I'm Parent.
I'm Parent.

这就是函数重写存在的必要了,函数重写只发生在父类间

我们又来写一个全局的函数:

void how_to_print(Parent* p)
{
    p->print();
}
调用:

how_to_print(&p);   
how_to_print(&c);
语句。

结果打印了:

I'm Parent.

I'm Parent.

这是什么情况?程序向表达什么?在全局作用域中,有一个函数,参数是父类指针,函数体为调用父类指针的print函数,分别使用父类对象和子类对象来作为参数,结果打印的都是父类信息,这就是因为父类指针指向的子类对象退化成了父类对象。

编译期间,编译器只能根据指针的类型判断所指向的对象,,根据兼容赋值原则,编译器认为父类指针指向的就是父类对象,,因此编译器只能调用父类的成员函数。

当编译器在对这个函数进行编译时,由于程序没有运行,编译器不可能知道指针p究竟指向的是谁,但是由于对这个函数又没理由报错,所以编译器需要选择一种合理的方式进行继续编译,由于当p为子类对象时编译器也能够通过赋值兼容性的原则将子类对象退化成父类对象,所以编译器就选择调用父类中的print函数,因为这是最安全的做法,因为子类继承父类所有代码,所以子类父类至少都有同一个print函数,这样就保证了不会出错。

这就是由于重写一个函数后同名覆盖带来的问题,做法虽然合理,但是却不是我们期望的,那么怎么解决呢?答案是多态。







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++中的类继承和派生是面向对象编程中的重要概念。在C++中,可以使用公有继承、保护继承和私有继承来实现类的继承和派生。 公有继承是最常见的一种继承方式,它可以使得基类的公有成员在派生类中仍然是公有的,保护成员在派生类中变为保护的,私有成员在派生类中不可访问。\[1\] 保护继承是一种特殊的继承方式,它可以使得基类的公有和保护成员在派生类中变为保护的,私有成员在派生类中不可访问。\[2\] 私有继承是一种特殊的继承方式,它可以使得基类的公有和保护成员在派生类中变为私有的,私有成员在派生类中不可访问。私有继承主要用于实现"实现继承",即派生类通过继承基类的实现来实现自己的功能。\[3\] 在派生类中,可以使用基类的成员函数和成员变量,但是访问权限受到继承方式的限制。公有继承和保护继承可以访问基类的成员函数和成员变量,私有继承只能在派生类内部访问基类的成员函数和成员变量。 总结起来,C++中的类继承和派生可以通过公有继承、保护继承和私有继承来实现,不同的继承方式决定了派生类对基类成员的访问权限。 #### 引用[.reference_title] - *1* *2* [C++ 面向对象 - 类的继承与派生](https://blog.csdn.net/m0_62598965/article/details/124610795)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [C++面向对象-继承和派生](https://blog.csdn.net/D23333A/article/details/116640148)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值