【C++】——继承与虚继承

文章目录

  • 继承
    • 继承的概念
    • 继承的定义
    • 继承类模版
    • 基类与派生类的赋值转换
    • 继承的作用域
    • 派生类的默认成员函数
      • 构造函数与析构函数
      • 拷贝构造
    • 不能被继承的类
    • 继承与友元
    • 继承与静态成员
    • 多继承与菱形继承
  • 虚继承
  • 继承与组合

继承

什么是继承?

继承其实就是胆码复用的一种手段,它允许我们定义一个类来继承另一个类的属性和方法。

非继承:

#define   _CRT_SECURE_NO_WARNINGS 1#include<iostream>
#include<string>
using namespace std;
class Teacher    //教师类
{
public:void print(){cout << _name << endl;cout << _age << endl;}
private:string _name = "zhangsan";    //名字int _age = 20;   //年龄string _num;   //工号
};
class student
{
public:void print(){cout << _name << endl;cout << _age << endl;}
private:string _name = "zhangsan";    //名字int _age = 20;   //年龄string _id;   //学号
};
int main()
{Teacher t;student s;t.print();s.print();return 0;
}

继承:

class Person
{
public:void Print(){cout << "名字:" << _name << endl;cout << "年龄:" << _age << endl;}
protected:string _name = "renqing"; // 姓名int _age = 20;  //年龄
};class Student : public Person
{
public:void playgame(){// ...}
private:int _id; // 学号
};

继承的概念

继承(inheritance)机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段,它允许我们在保持原有类特性的基础上进⾏扩展,增加⽅法(成员函数)和属性(成员变量),这样产⽣新的类,称派⽣类。继承呈现了⾯向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的函数层次的复⽤,继承是类设计层次的复⽤。

继承的定义

下⾯我们看到Person是基类,也称作⽗类。Student是派⽣类,也称作⼦类。(因为翻译的原因,所以既叫基类/派⽣类,也叫⽗类/⼦类)
在这里插入图片描述
在这里插入图片描述
继承基类成员访问⽅式的变化
在这里插入图片描述
基类的private成员只是不可访问,但是派生类还是得到了继承
在这里插入图片描述

public和protected的区别

class Base {  
public:  void publicMethod() {}  protected:  void protectedMethod() {}  
};  class Derived : public Base {  
public:  void anotherMethod() {  publicMethod(); // 可以访问基类的public成员  protectedMethod(); // 可以访问基类的protected成员  }  
};  int main() {  Derived d;  d.publicMethod(); // 正确:可以通过Derived实例访问public成员  // d.protectedMethod(); // 错误:不能从Derived实例的外部访问protected成员  
}

在这个示例中,Derived类可以访问基类Base的public和protected成员,但外界(如main函数中的代码)只能访问Derived的public成员以及从基类继承来的public成员,不能访问protected成员。

总结:
1.在实际运用中一般使用都是public继承,几乎很少使用protetced / private继承,也不提倡 使用protetced / private继承,因为protetced / private继承下来的成员都只能在派生类的类里 面使用,实际中扩展维护性不强。
2. 父类的私有 (private) 成员在子类中无论以何种方式继承都是不可见的。这意味着尽管这些成员被继承到了子类对象中,但由于语法上的限制,子类对象无论是在类内部还是外部都无法直接访问这些私有成员。
3. 基类的私有成员在子类都是不可见。基类的其他 成员在子类的访问方式 == Min(成员在基类的访问限定符,继承方式),public > protected>private。
4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过 最好显示的写出继承方式。
5. 基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在 派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。

继承类模版


#include <vector>
#include <list>
#include <deque>#define CLASSTYPE std::vector
//#define CLASSTYPE std:list
//#define CLASSTYPE std:dequetemplate<class T>
class stack : public CLASSTYPE<T>
{
public:void push(const T& x){// 基类是类模板时,需要指定⼀下类域,// 否则编译报错:error C3861: “push_back”: 找不到标识符// 因为stack<int>实例化时,也实例化vector<int>了// 但是模版是按需实例化,push_back等成员函数未实例化,所以找不到//push_back(x);CLASSTYPE<T>::push_back(x);}void pop(){CLASSTYPE<T>::pop_back();}const T& top(){return CLASSTYPE<T>::back();}bool empty() const {return ClASSTYPE<T>::empty();}};int main()
{stack<int> st;st.push(1);st.push(2);st.push(3);while (!st.empty()){cout << st.top() << " ";st.pop();}return 0;
}

基类与派生类的赋值转换

派生类可以赋值给基类,但是基类不能赋值给派生类

	Person p;Student s;p = s;//oks = p;//error

原因有以下几点:

  1. 类型不匹配:
    虽然基类和派生类有关系,但是它们仍然是两个不同的类型,派生类通过继承机制从基类继承了属性和方法,但是它也有可能添加了自己的属性和方法,或者重写了基类的某些部分。
  2. 违反了继承的目的:
    派生类本就是通过继承基类后再根据需求对于基类进行扩展或修改。如果把基类赋值给派生类,那么派生类的属性和方法就会被影响,那不就白继承了。
  3. 可能导致数据丢失:
    如果基类把派生类扩展或修改的属性和方法中特有的数据给覆盖了,就会导致数据丢失

派生类对象的引用赋值给基类

	Student s;Person& rp = s;

派生类对象的指针赋值给基类对象

	Student s;Person* pp = &s;

继承的作用域

隐藏(重定义):
在继承机制中基类和派生类都有独立的作用域,如果派⽣类和基类中有同名成员,派⽣类成员将屏蔽基类对同名成员的直接访问,这种情况叫隐藏。(在子类成员函数中,可以使用 基类::基类成员 显示访问)

同名成员变量

class Person
{
protected:double _height = 1.75;//身高int _age = 20;//年龄string _name = "renqing";//姓名
};class Student :public Person
{
public:void Print()//隐藏{cout << _height << endl;cout << _age << endl;cout << _name << endl;}
private:double _height = 1.65;//身高int _stuid = 123456;//学号int _grade = 10;//年级
};
int main()
{Student s;s.Print();return 0;
}

在这里插入图片描述
如果想打印基类的_height,加上作用域限定符 : :

void Print()//隐藏
{cout << Person::_height << endl;cout << _age << endl;cout << _name << endl;
}

在这里插入图片描述

同名成员函数

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;}
};
int main()
{B b;b.fun(10);return 0;
};

在这里插入图片描述
在隐藏关系中,同名函数默认调用的当前作用域的函数,如果想调用其他作用域的函数,则需要使用域作用限定符。

派生类的默认成员函数

在这里插入图片描述

构造函数与析构函数

派生类对象在调用构造函数时会先调用基类的构造函数,再调用派生类的构造函数。调用析构函数时会先调用派生类的析构函数,再调用基类的析构函数。

class Person
{
public:Person(const char* name = "renqing"): _name(name){cout << "Person()" << endl;}~Person(){cout << "~Person()" << endl;}
protected:string _name; // 姓名
};
class Student : public Person
{
public:Student(){cout << "Student()" << endl;}~Student(){cout << "~Student()" << endl;}
protected:int _num; //学号
};int main()
{Student s;return 0;
}

在这里插入图片描述

拷贝构造

派生类构造函数:
派生类需要定义自己的构造函数来初始化自己的成员变量,同时它还需要通过初始化列表来调用基类的构造函数以初始化从基类继承的成员。如果派生类构造函数没有显式地调用基类构造函数,编译器会尝试调用基类的默认构造函数(如果存在的话)。

我举个基类没有默认构造函数的例子:

class Person
{
public:Person(const char* name)//没有默认构造: _name(name){cout << "Person()" << endl;}~Person()//析构{cout << "~Person()" << endl;}
protected:string _name; // 姓名
};
class Student : public Person
{
public:Student(int num, const char* name)//构造:_num(num)//,_name(name) error, Person(name)// 基类没有默认构造函数,需要显示调用基类的构造函数{cout << "Student()" << endl;}~Student(){//因为构成覆盖关系,所以指定域作用限定符Person::~Person();cout << "~Student()" << endl;}
protected:int _num; //学号
};int main()
{Student s(20, "renqing");return 0;
}

编译器会对派生类与基类的析构函数名进行特殊处理,都会被处理成destrutor(),所以派生类与基类的析构函数构成隐藏关系。
在这里插入图片描述

但是为什么Person的析构函数会多调用一次呢?因为编译器为了保证基类的析构最后调用,所以在调用派生类析构函数之后会自动调用基类的构造函数。所以为了保证调用的正确顺序,派生类的析构函数我们不需要显示定义。

赋值重载

赋值运算符 operator=:
子类的 operator= 需要显式调用父类的 operator=
来完成父类成员的复制,并需要指定父类的作用域。

派生类赋值重载调用基类赋值重载时一定要加域作用限定符,不然就会发生死循环。

//拷贝构造
Person(const Person& p): _name(p._name)
{
}
//赋值重载
Person& operator=(const Person& p)
{if (this != &p)_name = p._name;return *this;
}
Student(const Student& s)//拷贝构造:_num(s._num), Person(s)//派生类赋值给基类
{;
}
//赋值重载
Student& operator = (const Student& s)
{if (this != &s){//加域作用限定,否则发生死循环Person::operator =(s);_num = s._num;}return *this;
}

不能被继承的类

⽅法1:基类的构造函数私有,派⽣类的构成必须调⽤基类的构造函数,但是基类的构成函数私有化以后,派⽣类看不⻅就不能调⽤了,那么派⽣类就⽆法实例化出对象。
⽅法2:C++11新增了⼀个final关键字,final修改基类,派⽣类就不能继承了。

// C++11的⽅法
class Base final
{
public:void func5() { cout << "Base::func5" << endl; }
protected:int a = 1;
private:// C++98的⽅法/*Base(){}*/
};class Derive :public Base
{void func4() { cout << "Derive::func4" << endl; }
protected:int b = 2;
};
int main()
{Base b;Derive d;return 0;
}

在这里插入图片描述

继承与友元

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

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;
}
int main()
{Person p;Student s;Display(p, s);return 0;
}

在这里插入图片描述

继承与静态成员

基类定义了static静态成员,则整个继承体系⾥⾯只有⼀个这样的成员。⽆论派⽣出多少个派⽣类,都只有⼀个static成员实例。

class Person
{
public:string _name;static int _count;
};
int Person::_count = 0;
class Student : public Person
{
protected:int _stuNum;
};
int main()
{Person p;Student s;// 这⾥的运⾏结果可以看到⾮静态成员_name的地址是不⼀样的// 说明派⽣类继承下来了,⽗派⽣类对象各有⼀份cout << &p._name << endl;cout << &s._name << endl;// 这⾥的运⾏结果可以看到静态成员_count的地址是⼀样的// 说明派⽣类和基类共⽤同⼀份静态成员cout << &p._count << endl;cout << &s._count << endl;// 公有的情况下,⽗派⽣类指定类域都可以访问静态成员cout << Person::_count << endl;cout << Student::_count << endl;return 0;
}

在这里插入图片描述

多继承与菱形继承

单继承:⼀个派⽣类只有⼀个直接基类时称这个继承关系为单继承
多继承:⼀个派⽣类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型是,先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯。

在这里插入图片描述

	// 单继承
class Person {
public:string _name;
};class Student : public Person {
public:int _number;
};class info : public Student {
public:int year;
};int main() {info s1;return 0;
}

在这里插入图片描述

// 多继承
class Person {
public:string _name;
};class Student {
public:int _number;
};class info : public Student, public Person
{
public:int year;
};int main() 
{info s1;return 0;}

在这里插入图片描述

// 菱形继承
class Person {
public:string _name;
};class Student : public Person {
public:int _number;
};class info :  public Person
{
public:int year;
};class Son : public Student, public info {
public:string wang;
};int main() {Son s1;s1._name = "李四";	//E0266	"Son::_name" 不明确	return 0;
}

菱形继承发生在多继承环境中,当一个类(称为“孙子类”)继承自两个或多个基类,而这些基类又共同继承自同一个基类时,这就叫菱形继承。一定要避开菱形继承,因为菱形继承会造成两个问题:数据冗余和二义性。

虚继承

虚继承主要用于解决多重继承中可能出现的二义性和数据冗余问题。
虚拟继承(Virtual Inheritance)在C++中通过在继承声明中加上virtual关键字来实现,使得派生类在继承多个基类时,对于某个共享基类的继承只保留一份基类拷贝,而不是在每个基类中都保留一份。

class Person
{
public:string _name; // 姓名
};
//虚继承
class Student : virtual public Person
{
protected:int _num; //学号
};
//虚继承 
class Teacher : virtual public Person
{
protected:int _id; // 职工编号
};

菱形继承(二义性和数据冗余)
在这里插入图片描述
菱形虚拟继承(解决了二义性和数据冗余)
在这里插入图片描述

继承与组合

is-a关系
public继承就是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。基类的内部细节对子类可见。

class A
{};class B : public A
{};

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

has-a关机

class A
{};class B
{A _aa;
};

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

总结:
继承耦合度高,所以依赖关系很强。
组合耦合度低,依赖关系不强。
所以推荐使用组合而不是继承。

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

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

相关文章

江科大笔记—OLED显示屏

OLED显示屏 OLED的GND接到负极&#xff0c;OLED的VCC接正极&#xff0c;同时也会接到stm32上的PB6和PB7 SCL接PB8 SDA接PB9 在Hardware文件夹里面放3个文件&#xff1a;OLED.c、OLED.h、OLED_Font.h OLED_Font.h:存的是OLED的字库数据&#xff0c;因OLED是不带字库的&#xf…

APP测试--含【学车不】项目实战

本文参考黑马程序员以下课程; 1-002-App应用架构_哔哩哔哩_bilibili 1. APP环境 1.1 app应用系统架构 json是一种轻量级的数据交换格式&#xff0c;采用完全独立于编程语言的文本格式来储存和表示数据 1.2 app 后台开发测试环境 预发布环境&#xff1a; 使用后端的测试代码&a…

Meta-Learning数学原理

文章目录 什么是元学习元学习的目标元学习的类型数学推导1. 传统机器学习的数学表述2. 元学习的基本思想3. MAML 算法推导3.1 元任务设置3.2 内层优化&#xff1a;任务级别学习3.3 外层优化&#xff1a;元级别学习3.4 元梯度计算3.5 最终更新规则 4. 算法合并5. 理解 MAML 的优…

钢索缺陷检测系统源码分享

钢索缺陷检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

在线制作PPT组织架构图!这个AI工具简单又好用!

ppt组织架构图如何制作&#xff0c;用哪个软件好&#xff1f; 在现代商业世界中&#xff0c;组织架构图是展示公司结构和层级关系的重要工具&#xff0c;譬如内部沟通或者对外展示等场合下&#xff0c;一个精美且清晰的组织架构图都能有效传达信息&#xff0c;提升企业形象。 …

高精度加法和减法

高精度加法 在C/C中&#xff0c;我们经常会碰到限定数据范围的情况&#xff0c;我们先来看看常用的int和long long两种数据类型的范围吧。 C标准规定&#xff1a;int占一个机器字长。在32位系统中int占32位&#xff0c;即4个字节&#xff0c;所以int的范围是[-2的31次方&#…

独立站技能树之建站33项自检清单 1.0丨出海笔记

很多时候大家建好站之后很嗨&#xff0c;但过一会就开始担忧各种纠结我是不是还有什么点没做好&#xff0c;或者我的站漏了什么东西&#xff0c;那么接下来以下这个独立站自检清单能很好的帮到你。其实对于新手我还是建议大家直接用一些模板&#xff0c;因为模板上面基本该有的…

基于SpringBoot+Vue+MySQL的在线招投标系统

系统展示 用户前台界面 管理员后台界面 系统背景 在当今商业环境中&#xff0c;招投标活动是企业获取项目、资源及合作伙伴的重要途径。然而&#xff0c;传统招投标过程往往繁琐复杂&#xff0c;涉及众多文件交换、信息审核与沟通环节&#xff0c;不仅效率低下&#xff0c;还易…

车市状态喜人,国内海外“两开花”

文/王俣祺 导语&#xff1a;随着中秋假期告一段落&#xff0c;“金九”也正式过半&#xff0c;整体上这个销售旺季的数据可以说十分喜人&#xff0c;各家车企不是发布新车、改款车就是推出了一系列购车权益&#xff0c;充分刺激了消费者的购车热情。再加上政府政策的鼎力支持&a…

动态线程池实战(一)

动态线程池 对项目的认知 为什么需要动态线程池 DynamicTp简介 接入步骤 功能介绍 模块划分 代码结构介绍

中、美、德、日制造业理念差异

合格的产品依赖稳定可靠的人机料法环&#xff0c;要求减少变量因素&#xff0c;增加稳定因素&#xff0c;避免“熵”增&#xff1b;五个因素中任何一个不可控&#xff0c;批次产品的一致性绝对差&#xff1b; 日本汽车企业&#xff0c;侧重“人”和“环”&#xff0c; 倚重是人…

828华为云征文|华为云Flexus云服务器X实例之openEuler系统下部署SQLite数据库浏览器sqlite-web

828华为云征文&#xff5c;华为云Flexus云服务器X实例之openEuler系统下部署SQLite数据库浏览器sqlite-web 前言一、Flexus云服务器X实例介绍1.1 Flexus云服务器X实例简介1.2 Flexus云服务器X实例特点1.3 Flexus云服务器X实例使用场景 二、sqlite-web介绍2.1 sqlite-web简介2.2…

画台扇-第15届蓝桥省赛Scratch中级组真题第3题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第188讲。 如果想持续关注Scratch蓝桥真题解读&#xff0c;可以点击《Scratch蓝桥杯历年真题》并订阅合集&#xff0c;…

【教程】鸿蒙ARKTS 打造数据驾驶舱---前序

鸿蒙ARKTS 打造数据驾驶舱 ​ 前面2章我介绍了如何通过定义View绘制箭头以及圆形进度&#xff0c;初步了解了鸿蒙如何进行自定义View。接下来我将通过我最近在带的一个VUE的项目&#xff0c;简单实现了几个鸿蒙原生页面。帮助大家快速上手纯血鸿蒙开发. 本项目基于Api11Stage模…

揭开GPRC5D靶点的神秘面纱,助力多发性骨髓瘤药物开发

前 言 多发性骨髓瘤属于第二大常见的血液系统恶性肿瘤&#xff0c;起源于骨髓造血组织的浆细胞恶性增殖。首发症状表现为非特异性&#xff0c;如腰疼、反复感染等&#xff0c;造成误诊、漏诊率较高&#xff0c;且难治愈易复发。目前临床上的治疗有靶向治疗、放疗、化疗、干细…

C++之继承(通俗易懂版)

前言&#xff1a;我们都知道C是一门支持过程化编程&#xff0c;面向对象的高级语言&#xff0c;既然是面向对象的语言&#xff0c;那么对于对象而言&#xff0c;对象会有很多中相同的属性&#xff0c;举个例子:你和你老师&#xff0c;你们都有着共同的属性和身份&#xff0c;例…

Linux--守护进程与会话

进程组 概念 进程组就是一个或多个进程的集合。 一个进程组可以包含多个进程。 下面我们通过一句简单的命令行来展示&#xff1a; 为什么会有进程组&#xff1f; 批量操作&#xff1a;进程组允许将多个进程组织在一起&#xff0c;形成一个逻辑上的整体。当需要对多个进程…

【关联规则】【Apriori算法】理解

关联规则学习是数据挖掘中的一种技术&#xff0c;用于发现大型数据库中变量间的有趣关系&#xff0c;特别是变量之间的有意义的关联、相关和依赖关系。这种类型的规则在零售业中特别有用&#xff0c;因为它可以帮助确定哪些商品经常一起购买。 关键概念 频繁项集&#xff08;F…

连锁会员管理系统应该有的高级功能

会员连锁管理系统是一种专门针对连锁企业设计的会员管理软件&#xff0c;它可以帮助连锁企业实现跨区域、跨店铺的会员信息、消费记录和积分等的统一管理。以下分析商淘云连锁会员管理系统的主要功能。 会员信息管理&#xff1a;全面收集和管理会员信息&#xff0c;如手机号码、…

2.4 卷积1

2.4 卷积1 2.4 卷积 在了解了系统及其脉冲响应之后&#xff0c;人们可能会想知道是否有一种方法可以通过任何给定的输入信号&#xff08;不仅仅是单位脉冲&#xff09;确定系统的输出信号。卷积就是这个问题的答案&#xff0c;前提是系统是线性且时不变的&#xff08;LTI&…