移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——10.继承

news2024/12/22 9:24:54

1.继承的概念及定义

1.1继承的概念

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保 持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象 程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继 承是类设计层次的复用

class Person
{
public:
 void Print()
 {
 cout << "name:" << _name << endl;
 cout << "age:" << _age << endl;
 }
protected:
 string _name = "peter"; // 姓名
 int _age = 18;  // 年龄
};


class Student : public Person
{
protected:
 int _stuid; // 学号
};                         //student类public继承person类

class Teacher : public Person
{
protected:
 int _jobid; // 工号
};                         //student类public继承person类



int main()
{
 Student s;
 Teacher t;
 s.Print();  //在类外调用继承的person的print函数
 t.Print();
 return 0;
}

1.2 继承定义 

1.2.1定义格式

下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类。

1.2.2继承关系和访问限定符 

1.2.3继承基类成员访问方式的变化

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

2. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问但需要在 派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。

3. 实际上面的表格我们进行一下总结会发现,基类的私有成员在子类都是不可见。基类的其他 成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),

public > protected > private。

4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过 最好显示的写出继承方式。

5. 在实际运用中一般使用都是public继承,几乎很少使用protetced/private继承,也不提倡 使用protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里 面使用,实际中扩展维护性不强。 

主要使用public继承:

// 实例演示三种继承关系下基类成员的各类型成员访问关系的变化  
class Person
{
public :
 void Print ()
 {
 cout<<_name <<endl;
 }
protected :
 string _name ; // 姓名
private :
 int _age ; // 年龄
};




//class Student : protected Person   原public变成protected,private不可见

//class Student : private Person     原public,protected全变成private,不可见

class Student : public Person       //不变
{
protected :
 int _stunum ; // 学号
};

2.基类和派生类对象赋值转换 

1.派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片 或者切割。寓意把派生类中父类那部分切来赋值过去。

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

3.基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类 的指针是指向派生类对象时才是安全的。

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

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

void Test ()
{
 Student sobj ;
 // 1.子类对象可以赋值给父类对象/指针/引用
 Person pobj = sobj ;
 Person* pp = &sobj;
 Person& rp = sobj;
    
 //2.基类对象不能赋值给派生类对象
    sobj = pobj;
    
    // 3.基类的指针可以通过强制类型转换赋值给派生类的指针
    pp = &sobj
    Student* ps1 = (Student*)pp; // 这种情况转换时可以的。
    ps1->_No = 10;
    
    pp = &pobj;
 Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问
题
    ps2->_No = 10;
}

3.继承中的作用域 

1. 在继承体系中基类和派生类都有独立的作用域。(父子类的同名成员函数不构成函数重载,函数重载要求两个函数需要在同一作用域)!!!!!!!!!!!!!!!!

2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问!!!!!!!,这种情况叫隐藏, 也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问

3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。

4. 注意在实际中在继承体系里面最好不要定义同名的成员。

// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person
{
protected :
 string _name = "小李子"; // 姓名
 int _num = 111;   // 身份证号
};

class Student : public Person
{
public:
 void Print()
 {
 cout<<" 姓名:"<<_name<< endl;
 cout<<" 身份证号:"<<Person::_num<< endl;  //这里使用了显示调用父类的_num,如果没有Person::,会因为隐藏,调用子类的_num:999

 cout<<" 学号:"<<_num<<endl;
 }
protected:
 int _num = 999; // 学号,定义了同名对象
};


void Test()
{
 Student s1;
 s1.Print();
};
// B中的fun和A中的fun不是构成重载,因为不是在同一作用域
// B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。
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;
 }
};

void Test()
{
 B b;
 b.fun(10); //这里先调用B的fun(int i),然后里面显示调用了A的fun()
};

4.派生类的默认成员函数 

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类 中,这几个成员函数是如何生成的呢?

1. 派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认 的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。

2. 派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化

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

4. 派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能 保证派生类对象先清理派生类成员再清理基类成员的顺序

5. 派生类对象初始化先调用基类构造再调派生类构造//先父后子!!!!!!

6. 派生类对象析构清理先调用派生类析构再调基类的析构。//先子后父!!!!!!

7. 因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同(这个我们后面会讲 解)。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加 virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。

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)      //这里使用了父类的拷贝构造,并使用了隐式类型转换,(因为父类拷贝构造函数只有一 个参数,s会先被隐式类型转换为person类型,产生临时变量,临时变量再来通过父类的拷贝构造完成构造

 , _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);   //显示调用父类的operator=()
 _num = s ._num;
 }
 return *this ;
 } 
 

 ~Student()
 {
 cout<<"~Student()" <<endl;
 }// 调完后会自动调用父类的析构函数
protected :
 int _num ; //学号
};


void Test ()
{
 Student s1 ("jack", 18);
 Student s2 (s1);
 Student s3 ("rose", 17);
 s1 = s3 ;
}

5.继承与友元 

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

class Student;
class Person
{
public:
 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; //这里无法打印成功,因为友元不继承,无法直接访问子类的私有成员
}
void main()
{
 Person p;
 Student s;
 Display(p, s);
}

6. 继承与静态成员 

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

class Person
{
public :
 Person () {++ _count ;}
protected :
 string _name ; // 姓名
public :
 static int _count; // 统计人的个数。
};

int Person :: _count = 0; //static成员初始化

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

class Graduate : public Student
{
protected :
 string _seminarCourse ; // 研究科目
};
void TestPerson()
{
 Student s1 ;
 Student s2 ;
 Student s3 ;
 Graduate s4 ;
 cout <<" 人数 :"<< Person ::_count << endl;
 Student ::_count = 0;
 cout <<" 人数 :"<< Person ::_count << endl;
}

 7.复杂的菱形继承及菱形虚拟继承

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

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

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

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

 虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和 Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地 方去使用。

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

class Student : virtual public Person     //virtual,虚继承
{
protected :
 int _num ; //学号
};

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

class Assistant : public Student, public Teacher
{
protected :
 string _majorCourse ; // 主修课程
};
void Test ()
{
 Assistant a ;
 a._name = "peter";
}

7.1虚拟继承解决数据冗余和二义性的原理 

class A
{
public:
 int _a;
};

// class B : public A
class B : virtual public A
{
public:
 int _b;
};

// class C : public A
class C : virtual public A
{
public:
 int _c;
};

class D : public B, public C
{
public:
 int _d;
};

int main()
{
 D d;
 d.B::_a = 1;
 d.C::_a = 2;
 d._b = 3;
 d._c = 4;
 d._d = 5;
 return 0;
}

下图是菱形继承的内存对象成员模型:这里可以看到数据冗余:

下图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下 面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指 向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表虚基表中存的偏移通过偏移量 可以找到下面的A。 

1.由图可知,由虚拟继承所得,B.C.D都含有同一个A(存储在最下方)

2.先来看B:

  2.1 第二行存储的是d继承的B中的_b的数值

  2.2 第一行存储的是虚基表指针

   

3.再来看B中存储的虚基表指针

  由第一行得,指针指向的地址为00bc5f50

   3.1 第一行什么都没存储

  3.2  第二行存储的是虚基表指针A的距离(偏移量)!!!!!!!!!!!!

详解: 

1.虚基表指针的地址是 0x005EF75C;

2.A的地址是0x005EF770;

可得两地址的距离为(十进制16*2-12=20(原来为16进制表示,C表示12)

3.再回到虚基表第二行存储的偏移量是16进制下的14转换为十进制为16+4=20

可得:偏移量和两地址间的距离相等!!!!!!!!!!!!!!!!!!!!

8.继承的总结和反思 

继承和组合:

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

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

3.优先使用对象组合,而不是类继承 。

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

5.对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象 来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复 用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。 组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被 封装。

6.实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有 些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用 继承,可以用组合,就用组合。

// Car和BMW Car和Benz构成is-a的关系
   class Car{
   protected:
   string _colour = "白色"; // 颜色
   string _num = "陕ABIT00"; // 车牌号
   };
   
   class BMW : public Car{
   public:
   void Drive() {cout << "好开-操控" << endl;}
   };
   
   class Benz : public Car{
   public:
   void Drive() {cout << "好坐-舒适" << endl;}
   };
   

   // Tire和Car构成has-a的关系
   
   class Tire{
   protected:
       string _brand = "Michelin";  // 品牌
       size_t _size = 17;         // 尺寸
   
   };
   
   class Car{
   protected:
   string _colour = "白色"; // 颜色
   string _num = "陕ABIT00"; // 车牌号
    Tire _t; // 轮胎  组合
   }; 

9.小习题 

 

p1和p3虽然相等,但指向的对象范围却是不同的,p1指向base1,p3指向整个d 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2106469.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为OD机试真题 - 最长连续子序列 - 双指针(Python/JS/C/C++ 2024 D卷 100分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

二叉树知识点

参考这篇文章二叉树知识点最详细最全讲解-CSDN博客 目录 预备知识 基本术语 相关性质 1.二叉树的定义 2.二叉树的性质 3.二叉树的种类 3.1 满二叉树 3.2 完全二叉树 3.3 二叉查找树 3.4 平衡二叉搜索树 4.二叉树的存储方式 4.1 链式存储 4.2 顺序存储 4.二叉树…

Behind the Code:与 Rakic 和 Todorovic 对话 OriginTrail 如何实现 AI 去中心化

原文&#xff1a;https://www.youtube.com/watch?vZMuLyLCtE3s&listPLtyd7v_I7PGnko80O0LCwQQsvhwAMu9cv&index12 作者&#xff1a;The Kusamarian 编译&#xff1a;OneBlock 随着人工智能技术的飞速发展&#xff0c;一系列前所未有的挑战随之而来&#xff1a;模型的…

NineData云原生智能数据管理平台新功能发布|2024年8月版​​

本月发布 10 项更新&#xff0c;其中重点发布 5 项、其他发布 5 项。 重点发布​ 数据库 DevOps - 敏感数据保护功能大幅升级​ 敏感数据保护全新升级&#xff0c;新增支持敏感数据等级&#xff0c;方便进行分类分级管控&#xff0c;加入数据类型概念&#xff0c;用于智能识…

分享基于PDF.JS的移动端PDF阅读器代码

一、前言 在之前的文章《分享基于PDF.js的pdf阅读器代码》里提到了PC端基于PDF.js的阅读器&#xff0c;本文将提供针对移动端的版本。 二、pdfViewer 为了能够直接使用&#xff0c;这里分享一下经过简单修改后能直接使用的pdfViewer代码&#xff1a; pdfViewer代码目录&…

SpringBoot2:请求处理原理分析-FORM表单请求接口

一、RESTFUL简介 Rest风格支持&#xff08;使用HTTP请求方式&#xff0c;动词来表示对资源的操作&#xff09; 以前&#xff1a;/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户 现在&#xff1a; /user GET-获取用户 DELETE-删除用户 PUT-修改…

气膜快递仓:便捷与效率的完美结合—轻空间

随着快递行业的飞速发展&#xff0c;仓储物流的需求日益增加。在这个以速度为核心竞争力的行业中&#xff0c;如何快速、高效地处理大量货物成为企业亟待解决的问题。气膜快递仓作为一种新型仓储解决方案&#xff0c;以其便捷与效率的优势&#xff0c;迅速成为市场的宠儿&#…

【多线程】深入剖析生产者-消费者模型

&#x1f490;个人主页&#xff1a;初晴~ &#x1f4da;相关专栏&#xff1a;多线程 / javaEE初阶 一、阻塞队列 阻塞队列是⼀种特殊的队列&#xff0c;也遵守 "先进先出" 的原则。是在普通的队列基础上做出了补充。 java标准库中的原有的队列Queue及其子类&#xf…

ElasticSearch-ELK

Logstash Logstash 配置文件结构Logstash 导入数据到 ES同步数据库数据到 ES FileBeatELK&#xff08;采集 Tomcat 服务器日志&#xff09; 使用FileBeats将日志发送到LogstashLogstash输出数据到Elasticsearch&#xff08;logstash开头的索引&#xff09; 利用Logstash过滤器解…

JVM4-运行时数据区

目录 概述 程序计数器 栈 Java虚拟机栈 概述 栈帧的组成 局部变量表 操作数栈 帧数据 栈内存溢出 本地方法栈 堆 方法区 类的元信息 运行时常量池 方法区的实现 方法区的溢出 字符串常量池 直接内存 概述 Java虚拟机在运行Java程序过程中管理的内存区域&am…

一款好看的导航网HTML源码((全静态页面带特效)

源码介绍 一款好看的导航网HTML源码(全静态页面带特效)&#xff0c;页面自适应&#xff0c;单页源码没有后台&#xff0c;需要的下载。 效果预览 源码获取 一款好看的导航网HTML源码

JENV版本管理工具

下载地址&#xff1a;https://github.com/jenv/jenv 安装步骤 将其添加到PATH中 使用方法&#xff08;注意&#xff1a;局部配置会覆盖全局配置。使用命令可覆盖局部&#xff09; 添加新的Java环境&#xff08;需提供绝对路径&#xff09; jenv add <名称> <路径>…

AIoTedge IoT平台替代网关、PLC和HMI,实现智慧农业大棚控制

AIoTedge作为一个集成了边缘物联网平台、软网关和边缘AI平台的创新产品&#xff0c;它通过边缘计算技术实现了数据的即时处理和智能分析&#xff0c;有效降低了延迟和带宽消耗。在智慧农业大棚的智能控制中&#xff0c;AIoTedge可以替代传统的网关、PLC和HMI&#xff08;人机界…

嵌入式全栈开发学习笔记---C++(运算符重载)

目录 运算符重载概念 运算符重载语法 运算符重载的两种方法 运算符重载的步骤 运算符重载限制 运算符重载原则 重载输出运算符 如何判断返回引用还是普通变量&#xff1f; 赋值运算符重载 重载自增运算符 重载数组下标运算符[ ] 重载函数调用运算符( ) 不要重载逻…

聚乙二醇-降冰片烯!有啥用?打印各种3D结构支持细胞培养!

大家好&#xff0c;今天我们来了解一项关于3D生物打印的研究——《Poly(ethylene glycol)-Norbornene as a Photo-Click Bioink for Digital Light Processing 3D Bioprinting》发表于《ACS Applied Materials & Interfaces》。3D生物打印在组织工程和再生医学领域具有重要…

【RAG】LongRAG:利用长上下文LLMs增强检索增强生成

前言 现有的RAG框架通常使用100词的短段落作为检索单元&#xff0c;这种设计使得检索器需要在大量语料库中搜索&#xff0c;增加了工作负担&#xff0c;并且容易引入难负样本&#xff0c;影响性能。LongRAG框架为了解决这一问题&#xff0c;该框架使用长检索单元&#xff08;最…

伴奏提取免费软件怎么选?这5款工具让编辑更简单

音频提取&#xff0c;听起来可能有点技术范&#xff0c;但其实它就在我们生活的各种小细节里。 比如在网上看到一个超有感觉的视频&#xff0c;背景音乐简直完美&#xff0c;但你只想保存这段音乐&#xff1b;又或者你是个播客&#xff0c;需要从一段采访中提取清晰的对话声轨…

全国百佳出版社专著转让

1、信息化XXX高校英语教学模式研究 2、高校计算机教学XXXXXX革创新研究 3、文创产品设计XXXXXX 4、多元化语文教学&#xff1a;XXX策略

拆解kolors古诗文绘本comfyui工作流,学习提示词多样性组合!

前言 三人行必有我师&#xff0c;在comfyui工作流的搭建中这句话同样有用&#xff0c;几天内我们就学习如何借助kolors这个更具中国元素的模型生古诗文绘本工作流&#xff01; 老规矩先说原理&#xff0c;借助CR prompt list节点 1.搭建kolors文生图工作流 不会搭建kolors文生…

21.新增管理员页面制作

新增管理员页面制作 1.修改AdminUser.vue <template><el-main><!-- 搜索栏 --><el-form :model"searchParm" :inline"true" size"default"><el-form-item><el-input v-model"searchParm.nickName"…