当前位置: 首页 > news >正文

C++封装和继承特性

目录

    • 一、继承的概念与定义
      • 1.1 什么是继承?
      • 1.2 继承的访问控制
    • 二、基类与派生类对象的赋值转换
    • 三、继承中的作用域
    • 四、派生类的默认成员函数
    • 五、继承与友元
    • 六、继承与静态成员
    • 七、菱形继承与虚拟继承
      • 7.1 菱形继承的问题
      • 7.2 虚拟继承(Virtual Inheritance)
      • 7.3 虚拟继承原理

一、继承的概念与定义

1.1 什么是继承?

继承是面向对象程序设计中最核心的代码复用机制。它允许我们在不修改原有类的基础上,通过扩展来创建新的类(称为派生类),从而形成类的层次结构。之前我们可能使用到的服复用都是函数复用,而继承就是类的复用

例如:Person 类作为基类,Student 和 Teacher 类作为派生类,复用 Person 中的成员变量和函数。

class Person {
protected:string _name = "liming";int _age = 20;
public:void Print() {cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
};class Student : public Person { //继承Person类,此时Student类中也存在Person类中的成员属性和方法,可以调用基类中的方法
protected:int _stuid; // 学号
};
class Teacher : public Person{protected:int _jobid; //工号
}int main()
{Student st;Teacher te;st.Print();te.Print();}

继承后,基类视为派生类的一部分

1.2 继承的访问控制

C++ 提供了三种继承方式:

  • public 继承
  • protected 继承
  • private 继承

它们影响基类成员在派生类中的访问权限:

基类成员访问权限public 继承protected 继承private 继承
publicpublicprotectedprivate
protectedprotectedprotectedprivate
private不可见不可见不可见

总结

  • private 成员在派生类中不可见,但仍被继承。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上限制派生类对象不管在类里面还是类外面 都不能去访问它。

  • 访问权限 = 访问限定符合继承方式两者中优先级高的。

    优先级如下:

    private > protected > public

  • 默认继承方式:class 为 private,struct 为 public。在实际中基本都会显示写出继承方式,增加代码可读性

  • 实际开发中常用 public 继承。


二、基类与派生类对象的赋值转换

  • 派生类 → 基类:允许赋值(称为“切片”或“切割”),包括对象、指针、引用。可以通俗的认为:基类是派生类的一部分,派生类中一定包含基类的所有成员,因此可以构造出来一个完整的基类
  • 基类 → 派生类:不允许直接赋值。与上一种情况相反,基类中没有所有的派生类成员,总会有部分成员没有合适的值去赋值,因此无法赋值
  • 基类指针/引用 → 派生类指针/引用:可通过强制类型转换,但不安全,除非基类指针原本就指向派生类对象。
Student s;
Person p = s;        //  切片
Person* pp = &s;     //  派生类指针给基类指针赋值
Person& rp = s;      //  派生类对象初始化基类引用// s = p;            //  基类给派生类赋值错误
Student* ps = (Student*)pp; //  强制转换,算然可以,但是会存在越界访问

与隐式类型转换不同,隐式类型转换会用被转换的类型去构造出一个临时变量,然后用该临时变量去赋值或者拷贝构造出一个变量或者对象。而切片不会有临时变量的产生,他直接会将派生类成员拷贝或者调用拷贝构造去给基类成员赋值。

在这里插入图片描述


三、继承中的作用域

  • 基类和派生类有独立的作用域
  • 如果派生类中有与基类同名的成员,会隐藏(也叫重定义) 基类的成员。
  • 可使用 基类::成员 显式访问被隐藏的成员。
class Person {
public:void Show(){cout << "Person::Show()" << endl;}
protected:int _num = 111; // 身份证号
};class Student : public Person {
public:void Print() {cout << Person::_num << endl; // 显式访问基类成员cout << _num << endl;         // 访问派生类成员}
protected:int _num = 999; // 学号,隐藏基类的 _num
};class Teacher : public Person {
public:void Show( int a) //与基类中成员函数同名,即使参数不同也构成隐藏{cout << "Teacher::Show(), a = " a << << endl;}
protected:int _name = "wanglaoshi"; 
};int main()
{Student s;s._num; //访问派生类自身的成员_num(学号)s.Person::_num; //访问基类中的_num(身份证号)Teacher t;t.Show(); //默认调用派生类成员方法t.Teacher::Show(5); //指定调用基类成员方法
}

注意:只要函数名相同也构成隐藏(不要求参数相同)。


四、派生类的默认成员函数

派生类的6个默认成员函数(构造、析构、拷贝构造、赋值、取地址)需注意:

  1. 构造函数:必须调用基类构造函数(可在初始化列表中调用)。
  2. 拷贝构造:必须调用基类拷贝构造。
  3. 赋值操作符:必须调用基类赋值操作符。
  4. 析构函数:先执行派生类析构,再自动调用基类析构。
  5. 析构函数会被特殊处理为destructor(),因此与基类析构函数构成隐藏关系。
class Student : public Person {
public:Student(const char* name, int num)  //构造函数,必须调用基类构造: Person(name), _num(num) {}Student(const Student& s)  //拷贝构造也必须调用基类拷贝构造: Person(s), _num(s._num) {}Student& operator=(const Student& s) { //赋值拷贝也必须调用基类赋值拷贝,但是*this指针是派生类的,也有没基类的任何表示,所以需要指定类作用域 + 显示调用基类赋值拷贝构造if (this != &s) {Person::operator=(s);_num = s._num;}return *this;}~Student() {// 自动调用 ~Person()}
};

在派生类中,直接调用 ~Person() 会调用到派生类的析构,因为析构函数在底层都将析构函数重命名为destructor(),形成了函数隐藏

需要指定作用域在调用。Person::~person() ,这样就可以显示调用到基类析构

注意:在析构函数中,如果我们显示调用基类的析构函数,那么在最后还是会自动的再次调用析构函数,导致重复析构的问题。这是因为要保证先调派生类析构,在调基类析构。

那么为什么要保证这个顺序呢?

如果我们在派生类中显示调用基类析构,此时我们依然在派生类作用域里,还是可以调用基类的属性和方法,若是在我们显示调用析构后我们还使用基类的属性和方法,则会造成一系列问题,为了防止这个问题,编译器强制在派生类析构执行完之后自动调用基类派生,无需我们手动调用。

除了析构函数的顺序,构造函数也有顺序,不过是先基类,在派生类,因此派生类在整个生命周期调用函数顺序如下图:

请添加图片描述

五、继承与友元

友元关系不能继承。基类的友元函数不能访问派生类的私有或保护成员。

class Person {friend void Display(const Person& p, const Student& s);
protected:string _name;
};class Student : public Person {
protected:int _stuNum;
};void Display(const Person& p, const Student& s) {cout << p._name << endl;     // ✅cout << s._stuNum << endl;   // ❌ 错误,不能访问 Student 的 protected 成员
}

六、继承与静态成员

基类中定义的 static 成员在整个继承体系中只有一份实例,所有派生类共享。

class Person {
public:static int _count;
};
int Person::_count = 0;class Student : public Person { /* ... */ };
class Graduate : public Student { /* ... */ };// 所有类共享同一个 _count

七、菱形继承与虚拟继承

7.1 菱形继承的问题

菱形继承指一个类继承自两个具有共同基类的类,导致:

  • 数据冗余:共同基类的成员在最终派生类中有两份。
  • 二义性:访问共同成员时需指定路径。
class Person { string _name; };
class Student : public Person { int _num; };
class Teacher : public Person { int _id; };
class Assistant : public Student, public Teacher { };Assistant a;
a._name = "peter"; // ❌ 二义性
a.Student::_name = "xxx"; // ✅ 指定路径

7.2 虚拟继承(Virtual Inheritance)

使用 virtual继承可解决菱形继承问题:

class Student : virtual public Person { };
class Teacher : virtual public Person { };
class Assistant : public Student, public Teacher { };

7.3 虚拟继承原理

  • 虚基类(如 Person)在最终派生类中只保留一份
  • 派生类通过虚基表指针偏移量来访问共享的基类成员。

virtual继承可解决菱形继承问题:

class Student : virtual public Person { };
class Teacher : virtual public Person { };
class Assistant : public Student, public Teacher { };
http://www.dtcms.com/a/430551.html

相关文章:

  • Linux(操作系统)文件系统--对打开文件的管理
  • 【Unity笔记】Unity XR 模式下 Point Light 不生效的原因与解决方法
  • 图片设计网站推荐wordpress下载的主题怎么安装
  • 分布式存储分片核心:从哈希取模到Redis哈希槽,从哈希类到非哈希类
  • C++ 操作 Redis
  • 旅游网站开发文献综述沈阳做网站大约要多少钱
  • 精美个人网站wordpress设置网站主题
  • PyCharm保姆级详细使用手册(Python新手快速上手篇)
  • 3.springboot-容器功能-@注解
  • python开发手机网站开发今天时政新闻热点是什么
  • 【网络编程】深入 HTTP:从报文交互到服务构建,洞悉核心机制
  • java面试0119-java中创建对象的方式?
  • 线程中互斥锁和读写锁相关区别应用示例
  • 网站开发logo绍兴网页设计
  • 2017主流网站风格win7 iis配置网站 视频教程
  • wordpress同步微信公众号seo外包是什么
  • 如何评价一个网站做的好不好展厅网站
  • wordpress站点克隆vip影视建设网站官网
  • 网站免费申请注册软件开发人员犯罪
  • 优秀个人网站设计模板互联网技术发展现状
  • 云南做网站价格网站的策划书
  • 做本地网站要服务器吗自动化毕设题目网站开发
  • 网站后端技术有哪些文学网站做编辑
  • 做淘客应该知道的网站咸阳学校网站建设费用
  • 适合女生做的网站投资公司网站设计
  • 专业网站维护如何免费建立自己的网页
  • 做社交网站的预算怎样查询网站空间
  • 网站重购出行南宁app软件下载
  • html怎么做成网站打开免费百度啊
  • 网站建设的时候如何上传图片如何建设一个个人网站