【二十二】【C++】继承基础和父子对象赋值转换

本文详细解释了C++中的继承概念,包括基类和派生类,以及公有、保护和私有继承的区别。讨论了访问修饰符如何影响成员的可见性,并通过示例展示了对象赋值的规则,包括子类对象赋值给父类、基类指针赋值给派生类指针的情况。
摘要由CSDN通过智能技术生成

继承之概念

C++中的继承是面向对象编程(OOP)的一个核心概念,它允许一个类(派生类)继承另一个类(基类)的属性和方法。这使得在保持代码重用性的同时,还能实现代码的逻辑分层和扩展。

基本概念

基类(Base Class):被继承的类,有时也称为父类或超类。

派生类(Derived Class):继承基类的类,有时也称为子类。

访问修饰符:C++中有三种访问修饰符,分别是publicprotectedprivate,它们决定了类成员(包括继承的成员)的访问权限。

继承类型

公有继承(Public Inheritance):派生类继承基类的公有成员和保护成员(以及私有成员,但不可见),基类的公有成员在派生类中保持公有,保护成员保持保护。

保护继承(Protected Inheritance):派生类继承基类的公有成员和保护成员(以及私有成员,但不可见),基类的公有和保护成员在派生类中都变成保护成员。

私有继承(Private Inheritance):派生类继承基类的公有成员和保护成员(以及私有成员,但不可见),基类的公有和保护成员在派生类中都变成私有成员。

继承类型之探究

基类的其他成员在子类的访问方式,等价于MIN(成员在基类的访问限定符,继承方式),public>protected>private

访问限定符public,表示类内、类外、派生类都可以访问。访问限定符protected,表示类内、派生类可以访问。访问限定符private表示类内可以访问。

因此我们对于访问限定符可访问的范围可定义public>protected>private

如果继承方式是公有继承,即用public修饰,那么基类的公有成员在派生类中保持公有,保护成员保持保护。

如果继承方式是保护继承,即用protected修饰,那么基类的公有和保护成员在派生类中都变成保护成员。

如果继承方式是私有继承,即用private修饰,那么基类的公有和保护成员在派生类中都变成私有成员。

C++中的三种访问限定符

C++中的访问修饰符publicprotectedprivate定义了类成员(包括属性和方法)的访问权限。这些访问权限控制了类成员在类的内部、派生类中以及类的对象中的可见性和可访问性。

public 访问修饰符

定义:public成员在任何地方都是可访问的,无论是类的内部还是外部或者派生类。

使用场景:通常用于定义类的接口部分,即那些可以被类的对象安全访问和使用的公共方法和属性。

protected 访问修饰符

定义:protected成员在类的内部和该类的派生类中是可访问的,但在类的对象(外部)中不可直接访问。

使用场景:用于那些允许在派生类中访问但不允许类的外部直接访问的成员。这在设计一个预期会被其他类继承的类时特别有用。

private 访问修饰符

定义:private成员只能被其所在类的成员函数访问,包括该类的友元函数,但不能在类的外部或者派生类中访问。

使用场景:用于隐藏类的实现细节和保护类的状态,避免外部直接访问。

继承之示例

 
/*继承*/
#if 1
#include <iostream>
using namespace std;

class Person {
    public:
        void Print() {
            cout << "name:" << _name << endl;
            cout << "age:" << _age << endl;
        }

    private:
        string _name = "peter";
        int _age = 18;
 };

class Student: public Person{
protected:
    int _stuid;
 };

class Teacher: public Person{
protected:
    int _jobid;
 };

int main(){
    Student s;
    Teacher t;
    s.Print();
    t.Print();
 }
#endif

Person

Person 类包含两个私有成员变量:_name_age,分别表示人的名字和年龄。这些成员变量被初始化为 "peter"18

提供了一个公有成员函数 Print,用于输出 Person 对象的名字和年龄。

Student

Student 类是 Person 类的派生类,使用 public 继承方式。

添加了一个受保护成员变量 _stuid,表示学生的学号。

Teacher

Teacher 类也是 Person 类的派生类,同样使用 public 继承方式。

添加了一个受保护成员变量 _jobid,表示教师的工号。

main 函数

main 函数中,创建了一个 Student 对象 s 和一个 Teacher 对象 t

调用了这两个对象的 Print 方法,这两个方法实际上是继承自 Person 类的。由于 StudentTeacher 类没有重写 Print 方法,所以调用的是 Person 类中定义的版本。

继承之探究(何为不可见?)

我们之前说的“派生类继承基类的公有成员和保护成员”,这种说法似乎有一点问题。我们从继承示例代码运行的结果得出,StudentTeacher 类都可以正常使用Print()函数,而Print()函数输出了_name_age成员变量。因此StudentTeacher 类对象中,是存在_name_age成员变量的。因此派生类应该是继承了基类的公有成员和保护成员和私有成员

但是在派生类中继承得到的私有成员,没有办法访问,是不可见的。这并不是意味着继承得到的私有成员访问限定符为private 。而是存在但不可见不可见的意思是即使是访问this指针也没办法进行访问。

派生类没有办法用this指针访问从基类继承得到的私有成员_name_age

基类可以通过this指针访问私有成员变量_name_age

基类和派生类对象的赋值转换

基类指针或引用指向派生类对象

允许:派生类对象可以赋值给基类的指针或引用。这是多态的基础,允许使用基类指针或引用来指向派生类对象,并通过基类的接口来操作派生类对象。

 
class Base {};
class Derived : public Base {};

Derived d;
Base* bp = &d; // 基类指针指向派生类对象
Base& br = d;  // 基类引用指向派生类对象

派生类指针或引用指向基类对象

不允许:基类对象不能直接赋值给派生类的指针或引用,因为基类对象可能不包含派生类所添加的成员。这样的赋值需要显式的类型转换,但是这样做是不安全的,除非通过某种机制(如动态类型识别)确保这种转换的有效性。

 
Base b;
Derived* dp = &b; // 错误:不能将基类对象的地址赋值给派生类指针
Derived& dr = b;  // 错误:不能将基类对象赋值给派生类引用

派生类对象赋值给基类对象

允许:派生类对象可以赋值给基类对象。这种赋值会导致对象切片(Object Slicing),即派生类对象中超出基类部分的成员将被切除,只剩下基类部分的成员部分被赋值过去。这意味着赋值后,基类对象不会保有派生类特有的信息。

 
Derived d;
Base b = d; // 派生类对象赋值给基类对象,发生对象切片

基类对象赋值给派生类对象

不允许:通常情况下,基类对象不能直接赋值给派生类对象,因为派生类可能包含更多的信息,而基类对象无法提供这些额外信息的初始化。

基类和派生类对象的赋值转换之示例

子类对象可以赋值给父类对象/指针/引用

 
/*子类对象可以赋值给父类对象/指针/引用*/
#if 1
#include<iostream>
using namespace std;
class Person{
protected:
    string _name;
    string _sex;
    int _age;
 };
class Student: public Person{
public:
    int _No;
 };
int main(){
    Student sobj;
    Person pobj=sobj;
    Person* pp=&sobj;
    Person& rp=sobj;    
}
#endif

基类对象不能赋值给派生类对象

 
/*基类对象不能赋值给派生类对象*/
#if 1
#include<iostream>
using namespace std;
class Person{
protected:
    string _name;
    string _sex;
    int _age;
 };
class Student: public Person{
public:
    int _No;
 };
int main(){
    Student sobj;
    Person pobj;
    sobj=pobj;    
}
#endif

基类的指针可以通过强制类型转换赋值给派生类的指针---派生类指针存放相符合地址

 
/*基类的指针可以通过强制类型转换赋值给派生类的指针*/
#if 1
#include<iostream>
using namespace std;
class Person{
protected:
    string _name;
    string _sex;
    int _age;
 };
class Student: public Person{
public:
    int _No;
 };
int main(){
    Student sobj;
    Person* pp=&sobj;
    Student* ps1=(Student*)pp;
    ps1->_No=10;
 }
#endif

基类的指针可以通过强制类型转换赋值给派生类的指针---派生类指针存放不相符合地址---越界访问

 
/*基类的指针可以通过强制类型转换赋值给派生类的指针---越界访问*/
#if 1
#include<iostream>
using namespace std;
class Person{
protected:
    string _name;
    string _sex;
    int _age;
 };
class Student: public Person{
public:
    int _No;
 };
int main(){
    Person pobj;
    Person* pp=&pobj;
    Student* ps2=(Student*)pp;
    ps2->_No=10;
 }
#endif

基类和派生类对象的赋值转换之探究

指针和引用

对象

小结论

针对于指针和引用,看他指向的是什么数据类型的信息。允许在大范围内通过小范围的方式寻找信息,不允许在小范围内通过大范围的方式寻找信息。(除非你可以保证信息安全)

针对于对象,看他存储的信息多少,基类存储的信息少,派生类存储的信息多。允许信息多的对象赋值给信息少的对象,不允许信息少的对象赋值给信息多的对象。

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妖精七七_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值