C++ 继承的范例讲解

目录
  • 1.继承的概念
  • 2.继承方式
  • 3.基类与派生类的赋值转换
  • 4.作用域与隐藏
  • 5.派生类的默认成员函数
  • 6.友元与静态成员
  • 7.菱形继承与虚继承
  • 8.继承和组合

1.继承的概念

继承,是面向对象的三大特性之一。继承可以理解成是类级别的一个复用,它允许我们在原有类的基础上进行扩展,增加新的功能。

当创建一个类时,我们可以继承一个已有类的成员和方法,并且在原有的基础上进行提升,这个被继承的类叫做基类,而这个继承后新建的类叫做派生类。

用法如下:

class [派生类名] : [继承类型] [基类名]

例如:

class Person
{
public:
	string _name;
	int _age;
};
class Student : public Person
{
protected:
	string _stuNum;
};

这里的派生类Student就复用了Person的方法和成员,并在此基础上扩展补充。

2.继承方式

继承的方式和类的访问限定符一样,分为public(公有继承),private(私有继承), protected(保护继承)三种。

不同的继承方式,在派生类中继承下来的基类成员的访问权限也不一样。

基类的其他成员在子类的访问方式 = Min(成员在基类的访问限定符,继承方式)

备注:

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

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

3.基类与派生类的赋值转换

派生类可以赋值给基类的对象、指针或者引用,这样的赋值也叫做对象切割。

例如上面的Person类和Student类

这种赋值只能是派生类赋给基类(但需要割掉多出来的成员例如_ stuID),而基类对象不能赋给派生类。

基类的指针可以强制类型转换赋值给派生类的指针, 如:

int main()
{
	Person p1;
	Student s1;
	Person* hPtr1 = &s1;//指向派生类对象
	Person* hPtr2 = &p1;//指向基类对象
	Student* pPtr = (Student*)hPtr1;//没问题
	Student* pPtr = (Student*)hPtr2;//有时候没有问题,但是会存在越界风险
	return 0;
}

小结:

1.派生类可以赋值给基类的对象、指针或者引用

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

3.基类的指针可以通过强制类型转换赋值给派生类的指针。**但是必须是基类的指针是指向派生类对象时才是安全的,否则会存在越界的风险。**这里基类如果是多态类型,可以使用RTT的dynamic_cast来进行识别后进行安全转换。

4.作用域与隐藏

隐藏:隐藏,也叫做重定义,当基类和派生类中出现重名的成员时,派生类就会将基类的同名成员给隐藏起来,然后使用自己的。(但是隐藏并不意味着就无法访问,可以通过指明基类作用域来显式访问隐藏成员。)

class Person
{
public:
	void f(int age)
	{
		cout << "姓名" << _name << endl;
		cout << "年龄" << _age << endl;
	}
protected:
	string _name;
	int _age;
};
class Student : public Person
{
public:
	void f()
	{
		Person::f(32);//需显式调用f函数
		cout << "学号" << _stuNum << endl;
	}
private:
	string _stuNum;
};

例如这里的f( )就构成了隐藏

同时,这里还有个需要注意的问题,在基类与派生类中,同名的方法并不能构成重载,因为处于不同的作用域中。而只要满足方法名相同,就会构成隐藏。

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

在每一个类中,都会有6个默认的成员函数,这些函数即使我们自己不去实现,编译器也会帮我们实现。

这里有两点需要注意(笔试题常考):

1.构造函数,拷贝构造,operator=三种情况,都要调用父类对应的构造函数/拷贝构造/operator=进行对父类的成员变量的初始化,并且倘若父类没有默认的构造函数的时候(比如父类写了带参的构造函数),我们就要显式调用(Person(参数…),Person::operator=(参数…))

构造函数显示调用

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

Student()
    	:People()
	{
		cout << "Student()" << endl;
	}

拷贝构造显示调用

建议拷贝构造都用显示调用,不然免不了出现子类拷贝构造当中调用了父类的构造函数的情况(因为拷贝构造也是构造,在初始化列表处,对于子类而言,父类相当于一个自定义类型对象,子类会调用父类的构造函数对父类的资源进行初始化。)

Student(const Student& s)
		:People(s)
	{
		cout << "Student(const Student& s)" << endl;
	}

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

Student& operator=(const Student& s)
	{
		cout << "Student& operator = (const Student& s)" << endl;
		if (this != &s)
		{
			Person:: operator=(s);
		}
	}

析构函数

由于编译器会将析构函数的名字处理成destructor,因此派生类和基类的析构函数会构成隐藏关系,故若要派生类要调用基类的析构函数,那么需要显式调用,但是编译器会默认在派生类的析构函数调用结束后调用基类的析构函数,这样就析构两次了。

	~Person()
	{
		cout << "~Person()" << endl;
	}
	~Student()
	{
		Person:: ~Person();
		cout << "~Student()" << endl;
	}

在派生类中,基类的析构函数会被隐藏,虽然它们这里的名字不同,但是为了实现多态, 它们都会被编译器重命名为destructor。在调用子类的构造函数时,我们是先调用父类的构造函数,后对子类的成员进行构造。由先构造后析构的顺序,所以我们是在析构函数当中析构子类的资源,析构函数调用完后编译器自动帮我们调用父类的析构函数。

6.友元与静态成员

1.友元

友元关系是不会继承的,如果子类要使用父类的友元,则子类自己也要将其定义为友元。

2.静态成员

基类定义了static静态成员,无论继承了多少次,派生了多少子类,静态成员在这整个继承体系中有且只有一个。静态成员不再单独属于某一个类亦或者是某一个对象,而是属于这一整个继承体系。

7.菱形继承与虚继承

首先简单介绍下单继承、多继承的概念

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

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

菱形继承:菱形继承是多继承的一种特殊情况,下面简单举例介绍下菱形继承及其带来的二义性问题:

class Human
{
public:
	int _age;
};
class Student : public Human
{
public:
	int _stuNum;
};
class Teacher : public Human
{
public:
	int _teaNum;
};
class Assistant : public Teacher, public Student
{
};

哦豁!!!菱形继承这个关系感受到了吧!!!

直接讲下这个菱形继承带来的二义性问题:

按照道理来说,各个类的大小应该是这样的。human类4个字节,teacher和student都是8个字节,而assistant是12个字节,但是实际上assistant却是16字节。

为什么assistant会有16字节?

这就是菱形继承的数据冗余和二义性问题的体现。

这里的teacher和student都从human中继承了相同的成员 _age。但是assistant再从teacher和student继承时,就分别把这两个 _age都给继承了过来,导致这里有了两个一样的成员。

在这样的情况下,后续想给 _age赋值,也会被编译器提示指示不明确,报错。

菱形继承的二义性是很致命的问题,如何解决呢?

虚继承,在腰部的类继承时添加virtual关键字。

class Student : virtual public Human
{
public:
	int _stuNum;
};
class Teacher : virtual public Human
{
public:
	int _teaNum;
};
class Assistant : public Teacher, public Student
{
};

这次,二义性问题解决了,teacher和student都是12个字节,而assistant是20个字节。

想知道为什么?点这里:从内存角度看待虚继承

简单小结一下

1.可以看到没有虚继承的情况下,Assitant中的成员连续排列出现了Teacher和Student中的_age是两个不同的值,但实际上一个人不会有两个年龄,所以这就出现了数据冗余。

2.这里多出来的8个字节,其实是两个虚基表指针。因为这里Human中的 _age是 teacher和 student共有的,所以为了能够方便处理,在内存中分布的时候,就会把这个共有成员 _age放到对象组成的最末尾的位置。然后通过了Teacher和Student的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量可以找到下面的Human。由此可见,使用了虚拟继承后,就可以解决菱形继承导致的问题。

为什么Teacher、Student需要去找属于自己的 _age?

基类与派生类的赋值转换时,需要进行切片。

int main()
{
	Assistant a;
	Teacher t = a;
	Student s = a;
	return 0;
}

当把对象a赋值给t和s的时候,因为他们互相没有对方的 _stuNum和 _teaNum,所以他们需要进行对象的切割,但是又因为 _age存放在对象的最尾部,所以只有知道了自己的偏移量,才能够成功的在切割了没有的元素时,还能找到自己的 _age。

8.继承和组合

继承是一种复用的方式,但不是唯一方式!

1.public继承是一种is-a的关系,就是基类是一个大类,而派生类则是这个大类中细分出来的一个子类,但是他们本质上其实是一种东西。

2.组合是一种has-a的关系,就是一种包含关系,比如对象a是对象b的成员,那么他们的关系就是对象b的组成中包含了对象a,对象a是对象b中的一部分,对象b包含对象a。

3.继承方式的复用常称之为白箱复用,在继承方式中,基类的内部细节对子类可见,这一定程度上破坏了基类的封装,伴随着基类的改变,对派生类的改变很大。并且两者依赖关系强,耦合度大。

4.对象组合式继承之外的复用选择,对象组合要求被组合对象提供良好的接口定义。这种复用称之为黑箱复用,对象的内部实现细节是不可见的。耦合度低。

实际工程中能用继承和组合就用组合,组合的耦合度低,代码的维护性好,但是继承在有些关系就适合用继承就用继承,并且要实现多态就一定要用继承。

到此这篇关于C++ 继承的范例讲解的文章就介绍到这了,更多相关C++ 继承内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++ 超详细梳理继承的概念与使用

    目录 继承的概念及定义 继承的概念 继承定义 定义格式 继承关系和访问限定符 继承基类成员访问方式的变化 基类和派生类对象赋值转换 继承中的作用域 派生类的默认成员函数 继承与友元 继承与静态成员 复杂的菱形继承及菱形虚拟继承 菱形继承 虚拟继承解决数据冗余和二义性的原理 继承的总结和反思 继承的概念及定义 继承的概念 继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类.继承呈现了面向对象程序设计的层次结构,体

  • C++ 多继承详情介绍

    C++支持多继承,即允许一个类同时继承多个类. 关于多继承,一直以来争议不断,有一部分人认为多继承会带来大量的问题,为了解决这些问题会使得语言本身变得非常复杂,因此应当避免.另外一派认为多继承在某些场景下可以起到非常关键的作用,应当予以支持. 关于多重继承是好是坏,这是一个非常复杂的问题,网上历来争议不断.因此不过多阐述,感兴趣的同学可以查阅一下相关资料.仅仅从实际支持来看,目前市面上大部分的语言包括Java仅支持单继承,只有C++等少数语言支持多继承.这和C++的理念也有关,即认定不能通过减少

  • C++图文并茂讲解继承

    目录 一.生活中的例子 二.惊艳的继承 三.继承的意义 四.小结 一.生活中的例子 组合关系∶整体与部分的关系 下面看一个组合关系的描述代码: #include <iostream> #include <string> using namespace std; class Memory { public: Memory() { cout << "Memory()" << endl; } ~Memory() { cout <<

  • 详解C++中单继承与多继承的使用

    目录 前言 1.继承的概念和定义 (1)继承的概念 (2)继承的定义方法 (2)继承后子类的成员类型 2.基类与派生类的赋值转换 (1)派生类赋值给基类 (2)基类给派生类 3.继承中的作用域 (1)隐藏的概念 (2)例题 4.派生类的默认成员函数 (1)默认生成的成员函数 (2)自己写 5.友元与静态成员 6.多继承 (1)概念 (2)复杂的菱形继承 (3)虚继承解决菱形继承问题 (4)虚继承的原理 7.继承与组合 (1)两者区别 (2)继承与组合的区别 (3)使用情况 8.总结 前言 C++

  • C++深入探究不同的继承体系

    目录 单继承 多继承 菱形继承 概念 存在的问题 解决方案 菱形虚拟继承 虚拟继承 什么是虚拟继承 内存层面理解虚拟继承 虚拟继承和普通单继承的区别 虚拟继承+菱形继承 声明: 本文的测试环境为Windows平台下的VS2013环境 单继承 一个子类只有一个父类.这类继承方式是最为简单的.具体可以参考继承 上篇中的例子.这里给出简单的图示即可: 多继承 一个子类有两个或两个以上的直接父类,这样的继承关系称之为多继承. 图解: 注意事项:  ①在多继承的场景下,建议在继承的每一个基类名称前都加上继

  • C++的继承特性你了解吗

    目录 导语: 继承作用 继承的结果 继承方式 子类构造 赋值兼容规则/向上转换/内存切片 多继承 虚拟继承 总结 导语: C++是对C语言的优化和改进,C++之所以优秀的点在于它的特性:抽象.封装.继承和多态. 本章总结继承的规则和特性,都是干货,与读者共同学习. 继承作用 代码的复用 子类继承父类,可以理解为,将父类的代码拷贝一份到子类中,达到子类可以调用父类方法的目的. 那为什么是可以理解而不是就是呢? 是因为有几个东西是不可以拷贝的,比如,父类的拷贝和析构方法,友元和静态成员. 友元关系是

  • C++深入探究继承的概念与使用

    目录 1.概念及定义 1.1 概念 1.2 定义 2.class与struct的区别 3.赋值兼容规则 4.继承中的作用域问题 5.派生类(子类)的默认成员函数 5.1 构造函数 5.2 拷贝构造函数 5.3 赋值运算符重载 5.4 析构函数 6.基类中哪些成员被子类继承了 6.1 成员变量 6.2 成员方法 7.友元函数被继承了吗 1.概念及定义 1.1 概念 继承主要的工作就是-----共性抽取 具体地讲: ①继承机制是面向对象程序设计使代码可以复用的最重要的手段; ②允许程序员在保持原有类

  • C++数据结构继承的概念与菱形继承及虚拟继承和组合

    目录 继承的概念 继承的定义 基类和派生类对象之间的赋值转换 继承中的作用域 派生类的默认成员函数 继承中的两个小细节

  • C++详细讲解继承与虚继承实现

    目录 继承的概念及定义 概念: 定义: 继承关系和访问限定符 总结 基类和派生类对象赋值转换 继承中的作用域 派生类的默认成员函数 继承与友元 继承与静态成员 复杂的菱形继承及菱形虚拟继承 虚继承原理 继承的总结 继承的概念及定义 概念: 继承机制是面向对象程序设计为了提高代码复用率的一种手段,它可以保持原类特性的基础上进行拓展,简单来说继承是类层次的复用. 接下来我们来看一个简单的继承 class Person { public: void Print() { cout<<"nam

  • C++ 继承的范例讲解

    目录 1.继承的概念 2.继承方式 3.基类与派生类的赋值转换 4.作用域与隐藏 5.派生类的默认成员函数 6.友元与静态成员 7.菱形继承与虚继承 8.继承和组合 1.继承的概念 继承,是面向对象的三大特性之一.继承可以理解成是类级别的一个复用,它允许我们在原有类的基础上进行扩展,增加新的功能. 当创建一个类时,我们可以继承一个已有类的成员和方法,并且在原有的基础上进行提升,这个被继承的类叫做基类,而这个继承后新建的类叫做派生类. 用法如下: class [派生类名] : [继承类型] [基类

  • JavaScript面向对象之class继承类案例讲解

    1. 面向对象class继承 在上面的章节中我们看到了JavaScript的对象模型是基于原型实现的,特点是简单,缺点是理解起来比传统的类-实例模型要困难,最大的缺点是继承的实现需要编写大量代码,并且需要正确实现原型链. 有没有更简单的写法?有! 我们先回顾用函数实现 Student 的方法: function Student(name) { this.name = name; } // 现在要给这个Student新增一个方法 Student.prototype.hello = function

  • Python中关于面向对象中继承的详细讲解

    目录 1.继承 2.单继承 3.多继承 4.子类重写父类的同名属性和方法 5.子类调用父类同名属性和方法 6.多层继承 7.调用父类方法super() 8.案例 1.继承 在程序中,继承描述的是多个类之间的所属关系. 如果一个类A里面的属性和方法可以复用,则可以通过继承的方式,传递到类B里. 那么类A就是基类,也叫做父类:类B就是派生类,也叫做子类. 案例: # 父类 class A(object): def __init__(self): self.num=50 def print_num(s

  • SpringBoot使用Swagger范例讲解

    目录 1. Swagger 介绍 2. 使用Swagger接口文档框架 1. Swagger 介绍 在一个项目开发过程中,当前端开发人员根据后端开发人员给出的 API 接口文档进行接口联调对接时,可能会出现这样的矛盾:前端开发人员抱怨后端开发人员给出的 API 接口文档和实际的情况有所出入,而后端开发人员由于繁重的开发任务已经身心俱疲,想到自己还要负责维护接口文档的任务更是不堪重负. 这时就需要一个解决方案,希望它能够在后端开发人员进行接口开发时,能够帮助后端工程师自动生成相应的接口文档,当接口

  • Java面向对象关键字extends继承的深入讲解

    目录 一. 问题引出 二.继承extends 2.1 继承的用法 2.2 基本语法 2.3继承的好处 2.4继承性 总结 一. 问题引出 面向对象的编程思想使得代码中创建的类更加具体,他们都有各自的属性,方法.有的时候一些客观事物之前存在一些联系,那么他们在代码中的具体类也存在一些联系. 例如:设计一个动物类 public class Animal { public String name; public int age; public Animal(String name) { this.na

  • python3中类的继承以及self和super的区别详解

    python中类的继承: 子类继承父类,及子类拥有了父类的 属性 和 方法. python中类的初始化都是__init__().所以父类和子类的初始化方式都是__init__(),但是如果子类初始化时没有这个函数,那么它便调用父类的__init__():如果实现了这个函数,就会覆盖父类的初始化函数.如果继承父类的__init__(),就需要在子类中显示调用这个函数.实现如下: class Animal(object): def __init__(self): self.name = "我是父类&

  • Java中的继承详情

    目录 一. 继承 1.1 继承的实现 1.2 继承的好处和弊端 二. 继承中的成员访问特点 2.1 继承中变量的访问特点 2.2 super 2.3 继承中构造方法的访问特点 2.4 继承中成员方法的访问特点 2.5 super内存图 2.6 方法重写 2.7 方法重写的注意事项 2.8. Java中继承的注意事项 三. 继承练习 总结 一. 继承 众所周知,我们Java语言是一种面向对象的编程语言,每当我们提到Java的特性,大家一定会在脑海里浮现出Java中的​继承.多态以及封装​. 我们在

  • Windows Server 2003 系统安全配置方法

    一.系统的安装 1.按照Windows2003安装光盘的提示安装,默认情况下2003没有把IIS6.0安装在系统里面. 2.IIS6.0的安装 开始菜单->控制面板->添加或删除程序->添加/删除Windows组件 应用程序 ---ASP.NET(可选) |--启用网络 COM+ 访问(必选) |--Internet 信息服务(IIS)---Internet 信息服务管理器(必选) |--公用文件(必选) |--万维网服务---Active Server pages(必选) |--Int

  • JavaScript继承基础讲解(原型链、借用构造函数、混合模式、原型式继承、寄生式继承、寄生组合式继承)

    说好的讲解JavaScript继承,可是迟迟到现在讲解.废话不多说,直接进入正题. 既然你想了解继承,证明你对JavaScript面向对象已经有一定的了解,如还有什么不理解的可以参考<面向对象JS基础讲解,工厂模式.构造函数模式.原型模式.混合模式.动态原型模式>,接下来讲一般通过那些方法完成JavaScript的继承. 原型链 JavaScript中实现继承最简单的方式就是使用原型链,将子类型的原型指向父类型的实例即可,即"子类型.prototype = new 父类型();&qu

  • 基于python3 类的属性、方法、封装、继承实例讲解

    Python 类 Python中的类提供了面向对象编程的所有基本功能:类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法. 对象可以包含任意数量和类型的数据. python类与c++类相似,提供了类的封装,继承.多继承,构造函数.析构函数. 在python3中,所有类最顶层父类都是object类,与java类似,如果定义类的时候没有写出父类,则object类就是其直接父类. 类定义 类定义语法格式如下: class ClassName: <statement

随机推荐