一文搞懂C++中继承的概念与使用

目录
  • 前言
  • 继承概念及定义
    • 继承概念
    • 继承定义
    • 继承方式
  • 父类和子类对象赋值转换
  • 继承中的作用域
  • 派生类的默认成员函数
  • 派生类的友元与静态成员
  • 继承关系
    • 单继承
    • 多继承
    • 菱形继承

前言

我们都知道面向对象语言的三大特点是:**封装,继承,多态。**之前在类和对象部分,我们提到了C++中的封装,那么今天呢,我们来学习一下C++中的继承。

继承概念及定义

继承概念

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

看概念是一件很让人疑惑的东西,接下来我就来举个例子来看看继承具体是什么东西

首先我们定义两个类,一个Student类,一个Teacher类,二者都有年龄和姓名,学生有学号,老师有工号。

class Student
{
private:
    int _age;     //年龄
    string _name; //姓名
    int _stuid;   //学号
};

class Teacher
{
private:
    int _age;     //年龄
    string _name; //姓名
    int _jobid;   //工号
};

我们发现这两个类有一些重复的地方,比如年龄和姓名,这二者是他们的成员,此时代码就产生了冗余。那么我们可不可以像个方法去复用这两个成员呢?继承此时就可以发挥它的重大作用。

我们将他们重复的地方提取出来,重新定义一个Person类。而Student类和Teacher类将Person类继承下来,此时我们就实现了代码的复用。

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

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

class Teacher : public Person
{
private:
	int _jobid; //工号
};

我们首先分别用Student和Teacher类来创建两个对象,来看看对象里面有什么。

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

此时我们可以看到我们创建的两个对象里面都含有从Person类中继承过来的 age 和 name 两个成员。

所以继承实际上是一个代码的复用,我们可以借用已完成的类的代码来完善我们需要创造的新类。

继承定义

以我们刚刚创建的Student类来举例:我们看到Person是父类,也称作基类。Student是子类,也称作派生类。

继承方式

我们在类和对象的时候介绍了三种访问限定符:public(公有),protected(保护)和private(私有)。访问限定符限定了我们在类外如何去访问类中的成员。

在继承中我们一样使用这三种限定符来限定子类该如何去访问父类的的成员,下面有一张表来表示他们的关系。

类成员\继承方式 public继承 protected继承 private继承
父类的public成员 派生类的public成员 派生类的protected成员 派生类的private成员
父类的protected成员 派生类的protected成员 派生类的protected成员 派生类的private成员
父类的private成员 在派生类中不可见 在派生类中不可见 在派生类中不可见

首先解释一下在派生类中不可见是什么意思,就如同我们在类外无法直接去修改类中的private成员一样,我们在子类中也无法直接修改父类的private成员。

如何简洁的去记这个表呢?在C++中权限的关系:public > protected > private。在继承的时候呢,父类成员的权限取的是:父类成员原本权限和继承方式中较小的那个。

比如父类的A成员原本权限为public,而子类的继承方式为private。此时A成员相对子类来说就为private成员

父类和子类对象赋值转换

子类的对象可以赋值给 父类的对象/父类的指针/父类的应用,那么是如何进行赋值的呢?形象一点来说就是切片,将子类中父类的部分切割父类。

但我们无法反过来,将父类对象赋值给子类对象。

继承中的作用域

在继承体系中父类和子类都有独立的作用域,如果子类和父类中有同名的成员,子类成员将屏蔽对父类成员的直接访问,这种情况叫隐藏,也叫重定义。

下面还是用我们的Person类和Student类来举个栗子,我们分别在Person类和Student类中加入一个print函数,通过打印内容来区分调用的为哪一print函数。

class Person
{
protected:
    int _age;
    string _name;

public:
    void print()
    {
        cout << "Person"<< endl;
    }
};

class Student : public Person
{
private:
    int _stuid; //学号
    public:
    void print()
    {
        cout << "Student" << endl;
    }
};

接下来我们创建一个对象然后来试一下结果。

int main()
{
    Student s;
    s.print();
    return 0;
}

我们可以看到我们调用的为Student中的print函数。此时子类的print函数已经对父类的print函数进行了重定义。重定义不代表子类无法去调用父类的同名函数,只是不那么直接而已。使用下面这种方法我们就可以调用父类中的同名函数。

int main()
{
    Student s;
    s.Person::print();
    return 0;
}

通过指定类域,我们就可以去调用父类的print函数。但在实际中最好不要去定义同名函数以免带来问题。

派生类的默认成员函数

首先我们来回顾一下有哪几个默认成员函数。

那么在子类中,这些默认成员函数是怎么生成的呢?

1.子类的构造函数必须调用父类的构造函数初始化父类的那一部分成员。如果父类没有默认的构造函数,则必须在派生类构造函数的初始化列表中显式调用。还是用我们的Person类和Student类举例。

情况一:有默认构造函数

class Person
{
protected:
    int _age;
    string _name;

public:
    Person()
    {
        cout << "Person" << endl; //调用就打印
    }
};

class Student : public Person
{
private:
    int _stuid; //学号
public:
    Student()
    {
        cout << "Student" << endl; //调用就打印
    }
};

int main()
{
    Student s;
    return 0;
}

情况二:无默认构造函数

class Person
{
protected:
    int _age;
    string _name;

public:
    Person(int age, string name)
    {
        cout << "Person" << endl;
    }
};

class Student : public Person
{
private:
    int _stuid; //学号
public:
    Student()
        : Person(19, "wanku") //无默认构造,此时我们需要在初始化列表中初始化
    {
        cout << "Student" << endl;
    }
};

int main()
{
    Student s;
    return 0;
}

int main()
{
    Student s;
    return 0;
}

2.子类的拷贝构造函数必须调用父类的拷贝构造完成父类的拷贝初始化化。

class Person
{
protected:
    int _age;
    string _name;

public:
    Person(int age = 10, string name = "wanku")
    {
        cout << "Person" << endl;
    }

    Person(const Person &p)
        : _age(p._age), _name(p._name)
    {}
};

class Student : public Person
{
private:
    int _stuid; //学号
public:
    Student()
    {
        cout << "Student" << endl;
    }

    Student(const Student &s)
        : Person(s) /*显示调用父类的拷贝构造*/, _stuid(s._stuid)
    {}
};

有些朋友可能会疑惑,在Person类中的拷贝构造函数参数明明是Person类,为什么我们的Student类可以传过去呢?那是因为我们刚刚讲的切片原理,当我们把子类对象传过去时,编译器会进行切分,然后再传给父类。

3.派生类的operator=必须要调用基类的operator=完成基类的复制。(原理和拷贝构造大体相似,值得注意的是:当我们在子类直接想去调用父类的operator= 时,会发生重定义,使用时记得加上父类的作用域)

4.在继承中一个对象的历程如下:父类的构造函数 –> 子类的构造函数 –> 子类的析构函数 –> 父类的析构函数。这个过程相当于把这些行为存在一个栈中,然后再把行为从栈中拿出来一般

派生类的友元与静态成员

父类的友元不是子类的友元。(你爸爸的朋友不一定是你的朋友)

父类中有一个静态成员,那么子类和父类共用一个静态成员。(静态成员并不存在对象中,只开辟一个空间,所以只能共用一个)

继承关系

单继承

一个子类只有一个直接父类。

多继承

一个子类有两个及以上的父类

菱形继承

多继承的一种特殊情况

以上就是一文搞懂C++中继承的概念与使用的详细内容,更多关于C++继承的资料请关注我们其它相关文章!

(0)

相关推荐

  • C++继承详细介绍

    在我们进行开发的时候,我们经常会遇到抽象出来的类之间具有继承关系. 举个简单的例子,比如我们在设计某游戏,当中需要定义Human也就是人这个类.每个人有名字,以及一定的血量,能够工作.也就是说Human这个类具有名字和血量这两个成员变量,还有一个工作的函数. 现在我们还需要开发一个英雄Hero类,英雄也是人,他应该也有名字和血量,以及也可以工作.但英雄又和普通人不同,他具有一些特殊的属性.比如变异,比如超能力等等.那么我们在开发Hero这个类的时候,绝大多数的功能都和Human一样,但是又需要额

  • C++ 继承的范例讲解

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

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

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

  • C++中继承的概念和定义

    目录 1.继承的概念及定义 1.1继承的概念 1.2继承的定义格式 1.3继承基类成员访问方式的变化 (1)公有继承 (2)保护继承 (3)私有继承 1.4总结 2.基类和派生类对象赋值转换 3.继承中的作用域 总结 1.继承的概念及定义 1.1继承的概念 继承机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类.继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程.以前我们接触的复用都是函数复用,继承

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

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

  • 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++中的封装,那么今天呢,我们来学习一下C++中的继承. 继承概念及定义 继承概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能

  • 一文搞懂PHP中的抽象类和接口

    目录 一.抽象类 1.定义 2.应用场景 3.入门代码 4.细节 5. 代码分解 二.接口 1.定义 2. 应用场景 3.入门代码 4.注意细节 三.类和接口之间关系 一.抽象类 1.定义 用abstract 关键字来修饰一个类时,这个类就叫抽象类. 用abstract 关键字来修饰一个方法时,这个方法就是抽象方法. 2.应用场景 在实际开发中,我们可能有这样一种类,是其它类的父类,但是它本身并不需要实例化,主要用途是用于让子类来继承(规定子类),这样可以到达代码复用. 同时利于项目设计者来设计

  • 一文搞懂Spring中的注解与反射

    目录 前言 一.内置(常用)注解 1.1@Overrode 1.2@RequestMapping 1.3@RequestBody 1.4@GetMapping 1.5@PathVariable 1.6@RequestParam 1.7@ComponentScan 1.8@Component 1.9@Service 1.10@Repository 二.元注解 @Target @Retention @Documented @Inherited 三.自定义注解 四.反射机制概述 4.1动态语言与静态语

  • 一文搞懂Java中的注解和反射

    目录 1.注解(Annotation) 1.1 什么是注解(Annotation) 1.2 内置注解 1.3 元注解(meta-annotation) 1.4 自定义注解 2.反射(Reflection) 2.1 反射和反射机制 2.2 Class类的获取方式和常用方法 2.3 反射的使用 1.注解(Annotation) 1.1 什么是注解(Annotation) 注解不是程序本身,可以在程序编译.类加载和运行时被读取,并执行相应的处理.注解的格式为"@注释名(参数值)",可以附加在

  • 一文搞懂Java中对象池的实现

    目录 1. 什么是对象池 2. 为什么需要对象池 3. 对象池的实现 4. 开源的对象池工具 5. JedisPool 对象池实现分析 6. 对象池总结 最近在分析一个应用中的某个接口的耗时情况时,发现一个看起来极其普通的对象创建操作,竟然每次需要消耗 8ms 左右时间,分析后发现这个对象可以通过对象池模式进行优化,优化后此步耗时仅有 0.01ms,这篇文章介绍对象池相关知识. 1. 什么是对象池 池化并不是什么新鲜的技术,它更像一种软件设计模式,主要功能是缓存一组已经初始化的对象,以供随时可以

  • 一文搞懂Java中的序列化与反序列化

    目录 序列化和反序列化的概念 应用场景 序列化实现的方式 继承Serializable接口,普通序列化 继承Externalizable接口,强制自定义序列化 serialVersionUID的作用 静态变量不会被序列化 使用序列化实现深拷贝 常见序列化协议对比 小结 序列化和反序列化的概念 当我们在Java中创建对象的时候,对象会一直存在,直到程序终止时.但有时候可能存在一种"持久化"场景:我们需要让对象能够在程序不运行的情况下,仍能存在并保存其信息.当程序再次运行时 还可以通过该对

  • 一文搞懂Python中subprocess模块的使用

    目录 简介 常用方法和接口 subprocess.run()解析 subprocess.Popen()解析 Popen 对象方法 subprocess.run()案例 subprocess.call()案例 subprocess.check_call()案例 subprocess.getstatusoutput()案例 subprocess.getoutput()案例 subprocess.check_output()案例 subprocess.Popen()综合案例 简介 subprocess

  • 一文搞懂Java中的日期类

    目录 一.日期类 1.1 第一代日期类 1.2 第二代日期类Calendar 1.3 第三代日期类 一.日期类 在程序的开发中我们经常会遇到日期类型的操作,Java对日期类型的操作提供了很好的支持.在最初的版本下,java.lang包中的System.currentTimeMillis();可以获取当前时间与协调时间(UTC)1970年1月1日午夜之间的时间差(以毫秒为单位测量).我们往往通过调用该方法计算某段代码的耗时. public class TestTime { public stati

  • 一文搞懂ES6中的Map和Set

    Map Map对象保存键值对.任何值(对象或者原始值) 都可以作为一个键或一个值.构造函数Map可以接受一个数组作为参数. Map和Object的区别 •一个Object 的键只能是字符串或者 Symbols,但一个Map 的键可以是任意值. •Map中的键值是有序的(FIFO 原则),而添加到对象中的键则不是. •Map的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算. •Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突.

  • 一文搞懂Java中的反射机制

    前一段时间一直忙,所以没什么时间写博客,拖了这么久,也该更新更新了.最近看到各种知识付费的推出,感觉是好事,也是坏事,好事是对知识沉淀的认可与推动,坏事是感觉很多人忙于把自己的知识变现,相对的在沉淀上做的实际还不够,我对此暂时还没有什么想法,总觉得,慢慢来,会更快一点,自己掌握好节奏就好. 好了,言归正传. 反射机制是Java中的一个很强大的特性,可以在运行时获取类的信息,比如说类的父类,接口,全部方法名及参数,全部常量和变量,可以说类在反射面前已经衣不遮体了(咳咳,这是正规车).先举一个小栗子

随机推荐