【C++】继承,菱形继承,虚拟继承,组合详解

news/2024/9/28 23:11:09 标签: c++

目录

1. 继承概念与定义

1.1 概念

1.2 定义

2. 父类与子类的赋值规则

3. 继承的作用域

4. 子类的默认成员函数

5. 继承与友元

6. 继承与静态成员

7. 菱形继承

7.1 继承关系

7.2 菱形继承的问题

7.3 虚拟继承

8. 继承与组合


1. 继承概念与定义

1.1 概念

1. 继承:保持原有类特性的基础上进行扩展,增加功能,这样产生新的类(派生类),本质是类的复用。

2. 子类会继承父类的成员变量和成员函数。

class Person
{
public:
     void Print()
     {
        ...
     }

protected:
     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();
     return 0;
}

1.2 定义

【格式】

【继承方式】

1. 有三种继承方式。

【继承后成员访问限定符的变化】

1. 父类私有成员继承后子类不可见不可用,无论子类类内类外。

2. 父类公有或保护成员继承后与继承方式比较,谁小就是谁,public > protected > private。

3. 父类的保护成员,类外不能访问,但是可以继承给子类类内访问。

 

2. 父类与子类的赋值规则

1. 子类可以赋值给父类/父类指针/父类引用。

2. 父类不可以赋值给子类。

3. public继承中,子类可以看作是一个特殊的父类,is-a的关系。

class Person
{
protected :
    string _name; // 姓名
    string _sex;  // 性别
    int _age; // 年龄
};

class Student : public Person
{
public :
     int _No ; // 学号
};

void Test ()
{
 Student s;
 
 Person p = s;
 Person* pp = &s; //指向子类的父类部分
 Person& rp = s; //父类部分的引用
}

 

3. 继承的作用域

1. 父类和子类都有自己的独立作用域。

2. 父类和子类允许有同名成员,默认访问子类的,因为隐藏了父类的同名成员,想访问父类的同名成员使用基类::基类成员显示访问。

class Person
{
protected :
     int _num = 111;   
};

class Student : public Person
{
public:
     void Print()
     {
         cout << Person::_num << endl; //显示访问父类
         cout << _num << endl; //默认访问子类同名
     }

protected:
     int _num = 999; 
};

【注意】

1. 重载必须是相同作用域。

2. 成员函数的函数名相同就会构成隐藏。

class A
{
public:
     void fun()
     {
         cout << "func()" << endl;
     }
};

class B : public A
{
public:
     void fun(int i)
     {
         A::fun();
         cout << "func(int i)->" <<i<<endl;
     }
};

 

4. 子类的默认成员函数

1. 我们把子类的成员变量看作三部分,内置类型,自定义类型,继承的父类成员变量,把继承的父类看作一个整体。

2. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员,子类变量的构造自己实现。

3. 子类的拷贝构造同理,派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化,自己实现子类的拷贝构造。

4. 派生类的operator=必须要调用基类的operator=完成基类的复制。

5. 子类的析构函数结束后会自动调用父类的析构函数,析构的顺序是先子后父,因为先父后子的子可能会访问父。

//父类
class Person
{
public:
     //父类构造
     Person(const char* name = "peter")
     : _name(name)
     {
         cout<<"Person()" <<endl;
     }
    
     //父类拷贝构造  
     Person(const Person& p)
     : _name(p._name)
     {
        cout<<"Person(const Person& p)" <<endl;
     }
    
     //父类赋值重载
     Person& operator=(const Person& p )
     {
        cout<<"Person operator=(const Person& p)"<< endl;
        if (this != &p)
            _name = p ._name;
        
        return *this ;
     }
        
     //父类析构    
     ~Person()
     {
         cout<<"~Person()" <<endl;
     }

protected:
     string _name; // 姓名
};

//子类
class Student : public Person
{
public:
     //子类构造
     Student(const char* name, int num)
     :Person(name) //父类部分的构造交给父类构造
     ,_num(num)
     {
         cout<<"Student()" <<endl;
     }
 
     //子类拷贝
     Student(const Student& s)
     :Person(s) //父类部分的拷贝交给父类拷贝
     ,_num(s._num)
     {
         cout<<"Student(const Student& s)" <<endl ;
     }
     
     //子类赋值重载   
     Student& operator=(const Student& s)
     {
         cout<<"Student& operator= (const Student& s)"<< endl;
         if (this != &s)
         {
             Person::operator=(s); //这里显示调用因为构成了隐藏
             _num = s._num;
         }
         return *this ;
     } 
     
     //子类析构   
     ~Student() 
     {
         cout<<"~Student()" <<endl;
     }

protected:
     int _num ; //学号
};

 

5. 继承与友元

1. 友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。

 

6. 继承与静态成员

1. 基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子 类,都只有一个static成员实例。

2. 父类的静态成员不属于某个对象,属于整个类,继承的子类也有使用权。

 

7. 菱形继承

7.1 继承关系

【单继承】

一个子类只有一个直接父类时称这个继承关系为单继承

【多继承】

一个子类有两个或以上直接父类时称这个继承关系为多继承

【菱形继承】

菱形继承是多继承的一种特殊情况。

7.2 菱形继承的问题

菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。

在Assistant的对象中Person成员会有两份。

1. 两个_name会造成数据二义性,这个可以显示调用解决。

2. 数据冗余无法解决。

class Person
{
public :
     string _name ; // 姓名
};

class Student : public Person
{
protected :
     int _num ; //学号
};

class Teacher : public Person
{
protected :
     int _id ; // 职工编号
};

class Assistant : public Student, public Teacher
{
protected :
     string _majorCourse ; // 主修课程
};

7.3 虚拟继承

1. 虚拟继承可以解决菱形继承的二义性和数据冗余的问题。

2. vircual关键字要加在继承冗余的类的位置,比如这里冗余的是person类,那么谁继承person就要用虚拟继承。

class Person
{
public :
     string _name ; // 姓名
};

class Student : virtual public Person
{
protected :
     int _num ; //学号
};

class Teacher : virtual public Person
{
protected :
     int _id ; // 职工编号
};

class Assistant : public Student, public Teacher
{
protected :
     string _majorCourse ; // 主修课程
};

【原理】

1. 冗余的变量会被放到最下面。

2. 原本的位置变成一个地址,这个地址指向一张表(虚基表),表里有这个冗余变量的偏移量,通过偏移量可以找到它。

8. 继承与组合

1. public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。

2. 组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

3. 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。在继承方式中,基类的内部细节对子类可见。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。

4. 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复(black-box reuse),因为对象的内部细节是不可见的。组合类之间没有很强的依赖关系,耦合度低。

//组合
class A
{
public:
    int _a;
}

class B 
{
private:
    A a;
}

http://www.niftyadmin.cn/n/5682034.html

相关文章

open-resty 服务安装redis插件

从github下载 作者&#xff1a;程序那点事儿 日期&#xff1a;2023/11/16 22:04 lua-resty-redis-cluster cd /usr/local/openresty/modules #进入到modules目录git clone https://github.com/cuiweixie/lua-resty-redis-cluster.git #下载插件mv lua-resty-redis-cluster/ …

使用 Spring Boot 和 EasyExcel 进行动态表头导出 Excel

引言 在企业级应用中&#xff0c;经常需要将数据导出为 Excel 文件&#xff0c;以便用户进行分析和查看。Spring Boot 结合 EasyExcel 可以非常方便地实现这一需求。本文将详细介绍如何使用 Spring Boot 和 EasyExcel 进行动态表头的 Excel 导出。 环境准备 1. 添加依赖 首…

【球形空间产生器】

题目 代码 #pragma GCC optimize(3) #include <bits/stdc.h> using namespace std; const double eps 1e-6; const int N 12; double g[N][N]; double ss[N]; int n; void gauss() {int c, r, t;for(c 1, r 1; c < n; c){int t r;for(int i r1; i < n; i)i…

Java---异常及处理

一.异常 1.概念 程序的非正常执行。高级语言都有异常处理机制&#xff08;C&#xff0c;Java&#xff09; 2.一般处理异常的方法 Scanner sc new Scanner(System.in);System.out.println("请输入一个数字:");String s sc.nextLine();if (s.matches("[0-9]&qu…

传知代码-基于图神经网络的知识追踪方法(论文复现)

代码以及视频讲解 本文所涉及所有资源均在传知代码平台可获取 1.论文概述 论文链接提出了一种基于图神经网络的知识追踪方法&#xff0c;称为基于图的知识追踪&#xff08;GKT&#xff09;。将知识结构构建为图&#xff0c;其中节点对应于概念&#xff0c;边对应于它们之间的…

用通义灵码如何快速合理解决遗留代码问题?

本文首先介绍了遗留代码的概念&#xff0c;并对遗留代码进行了分类。针对不同类型的遗留代码&#xff0c;提供了相应的处理策略。此外&#xff0c;本文重点介绍了通义灵码在维护遗留代码过程中能提供哪些支持。 什么是遗留代码 与过时技术相关的代码&#xff1a; 与不再受支持的…

【linux进程】深度理解进程--什么是进程什么是pcb进程创建

目录 前言一&#xff0c;对PCB的理解二&#xff0c;CPU对进程列表的处理三&#xff0c;进程标识符:pid1. 查看系统进程1: ps axj2. 查看系统进程2: /proc 四&#xff0c;系统调用函数:getpid五&#xff0c;父进程和子进程的概念六&#xff0c;创建子进程--fork函数的使用1. 创建…

胤娲科技:AI界的超级充电宝——忆阻器如何让LLM告别电量焦虑

当AI遇上“记忆橡皮擦”&#xff0c;电量不再是问题&#xff01; 嘿&#xff0c;朋友们&#xff0c;你们是否曾经因为手机电量不足而焦虑得像个无头苍蝇&#xff1f;想象一下&#xff0c;如果这种“电量焦虑”也蔓延到了AI界&#xff0c; 特别是那些聪明绝顶但“耗电如喝水”的…