C++——继承

目录:


继承的概念及定义

        面向对象的三个基本特征:封装、继承、多态。在前面的讲解中封装已经用的很多了,那么接下来的两篇文章就来介绍一下继承和多态。

继承的概念

        继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。
        举一个例子,曾经写过的学生管理系统,我们可以拓展一下,写一个学校的管理系统,学校的的成员有学生,有老师,还有职工人员,每个人的信息都有姓名、年龄、学号和工号、家庭住址等等,所以这就可以有一个公共的类存放这些公有的信息,这就要用到继承,再单独写出学生、老师这些类去继承这个公共的类。被继承的这个类就叫做父类或基类,继承的类就叫做子类或派生类。所以继承体现的是类设计定义层次的复用。
class Person
{
protected:
    string _name = "xxx"; // 姓名
    int _age = 18;  // 年龄
};

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

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

int main()
{
    Student s;
    s._name = "张三";
    s._age = 18;

    Teacher t;
    t._name = "老师";
    t._age = 30;
}
继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了
Student和Teacher复用了Person的成员。

继承的定义

定义格式

Person是父类,也称作基类。Student是子类,也称作派生类。

继承关系和访问限定符

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

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

总结:

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

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

  • 派生类对象可以赋值给基类的对象/基类的指针/基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切开,pp用指针去指向,rp是切出来的别名,所以算子类对象的大小也是要算父类的。
  • 基类对象不能赋值给派生类对象,因为基类少了一部分。
  • 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,下一篇多态再讲解。
class Person
{
protected:
	string _name = "xxx"; // 姓名
	int _age = 18;
};

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

int main()
{
	Student s;
	// 1.子类对象可以赋值给父类对象/指针/引用
	// 注意这里不是同类型,但不是隐式类型转换
	// 算是一个特殊支持
	Person p = s;
	Person* pp = &s;
	Person& rp = s;

	//2.基类对象不能赋值给派生类对象
	// s = p;
}

继承中的作用域

  1. 在继承体系中基类派生类都有独立的作用域

  2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问
  3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  4. 注意在实际中在继承体系里面最好不要定义同名的成员

// Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person
{
protected:
    int _num = 111; // 身份证号
};
class Student : public Person
{
public:
    void Print()
    {
        cout << " 身份证号:" << Person::_num << endl; // 111
        cout << " 学号:" << _num << endl; // 999
    }
protected:
    int _num = 999; // 学号
};
// 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;
    }
};

派生类的默认成员函数

        6个默认成员函数,我们不写编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢,我们一个一个说:

子类默认生成的构造函数:

  1. 对于自己的成员会做什么不变。
  2. 对于继承的父类成员,必须调用父类的构造函数初始化,所以要在子类构造函数的初始化列表调用父类构造函数。
class Person
{
public:
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}
protected:
	string _name; // 姓名
};
class Student : public Person
{
public:
	Student(const char* name, int num)
		: Person(name)
		, _num(num)
	{
		cout << "Student()" << endl;
	}
protected:
	int _num; //学号
};

int main()
{
	Student s("张三", 1);
}

子类默认的拷贝构造函数:

  1. 对于自己的成员做什么不变。(内置类型值拷贝,自定义类型调用它的拷贝构造)
  2. 对于继承的父类成员,必须调用父类的拷贝构造函数。
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;
	}
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;
	}
protected:
	int _num; //学号
};

int main()
{
	Student s("张三", 1);
}

赋值运算符重载:

这个也是差不多的处理方式。

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;
	}
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;
	}
protected:
	int _num; //学号
};

int main()
{
	Student s("张三", 1);
	Student s1 = s;
}

默认生成的析构函数

  1. 子类的析构函数跟父类析构函数构成隐藏。
  2. 由于后面多态的需要,析构函数名字会统一处理成destructtor()。
  3. 不需要显示调用父类析构函数,每个子类析构函数后面,会自动调用父类析构函数,这样才能保证先析构子类再析构父类。
class Person
{
public:
	Person(const char* name = "peter")
		: _name(name)
	{
		cout << "Person()" << endl;
	}
    ~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()
    {
        // Person::~Person();
        cout << "~Student()" << endl;
    }
protected:
	int _num; //学号
};

 【总结】:

  1. 子类的构造函数必须调用父类的构造函数初始化父类的那一部分成员。如果父类没有默认的构造函数,则必须在子类构造函数的初始化列表阶段显示调用
  2. 子类的拷贝构造函数必须调用父类的拷贝构造完成父类的拷贝初始化
  3. 子类的operator=必须要调用父类的operator=完成父类的复制
  4. 子类的析构函数会在被调用完成后自动调用父类的析构函数清理父类成员。因为这样才能保证子类对象先清理子类成员再清理父类成员的顺序
  5. 子类对象初始化先调用父类构造再调子类构造
  6. 子类对象析构清理先调用子类析构再调父类的析构

继承与友元

        友元关系不能继承,也就是说父类友元不能访问子类私有和保护的成员。而且之前也说过友元也不能用的太多。
class Person
{
public:
	friend void Display(const Person& p, const Student& s);
protected:
	string _name; // 姓名
};
class Student : public Person
{
// public:
//     friend void Display(const Person& p, const Student& s); // 友元无法继承,想要访问子类中的保护就要把函数写成子类的友元
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);
}

继承与静态成员

        基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例,这个没什么可说的,静态成员存放在静态区,不受继承的限制,所以只有一个static成员实例。

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

单继承:一个子类只有一个直接父类时称这个继承关系为单继承。
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承 。
先讲一个小知识点:如何定义一个不能被继承的类?
前面也讲到了,子类再实例化对象的时候,先调用父类的构造函数,这就有一个思路,就是把父类的构造函树私有,就像一个类不想让它拷贝,就把它的拷贝构造私有,只声明不实现。
class A
{
private:
	A()
	{}
protected:
	int _a;
};

class B : public A
{

};

int main()
{
	B bb; // 这样编译就会报错
	return 0;
}

以上就是c++98的处理方式,在c++11有了新的方法,就是在父类的类名后加上final,明确规定A不能被继承。

class A final
{
    // ...
};

还有一个就是小问题就是多继承的指针偏移问题。

class B1
{ 
public: 
	int _b1;
};
class B2
{ 
public: 
	int _b2;
};
class D : public B1, public B2
{
public:
	int _d;
};
int main()
{
	D d;
	B1* p1 = &d;
	B2* p2 = &d;
	D* p3 = &d;
}

问p1,p2,p3的关系,之前的知识也说了切割会把父类的部分从子类中切割。

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

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

记录 | xftp远程连接两台windows

1、打开openssh 设置 -> 应用 -> 可选功能 -> 添加功能 -> OpenSSH 客户端&#xff0c;将 ssh 客户端安装将两台电脑的 ssh 开启&#xff0c;cmd 中输入 net start sshd2、配置 win10 账号密码 3、进行 xftp 连接

SSD在AI发展中的关键作用:从高速缓存到数据湖-1

随着人工智能技术的飞速发展&#xff0c;存储在其中发挥着至关重要的作用。特别是在AI训练过程中&#xff0c;存储SSD&#xff08;固态硬盘&#xff09;的高性能和可靠性对于提升训练效率和保证数据安全具有不可替代的作用。 存储SSD在AI发展中的作用和趋势&#xff0c;存储将…

自动化测试之读取配置文件

前言&#xff1a; 在日常自动化测试开发工作中&#xff0c;经常要使用配置文件&#xff0c;进行环境配置&#xff0c;或进行数据驱动等。我们常常把这些文件放置在 resources 目录下&#xff0c;然后通过 getResource、ClassLoader.getResource 和 getResourceAsStream() 等方法…

Spring框架学习:Bean生命周期

目录 SpringBean的生命周期 Bean实例属性填充 三级缓存 常用的Aware接口 Spring IoC容器实例化Bean总结 SpringBean的生命周期 Spring Bean的生命周期是从 Bean 实例化之后&#xff0c;即通过反射创建出对象之后&#xff0c;到Bean成为一个完整对象&#xff0c;最终存储到…

[足式机器人]Part2 Dr. CAN学习笔记-自动控制原理Ch1-3燃烧卡路里-系统分析实例

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-自动控制原理Ch1-3燃烧卡路里-系统分析实例 1. 数学模型2. 比例控制 Proprotional Control 1. 数学模型 2. 比例控制 Proprotional Control

<JavaEE> 经典设计模式之 -- 单例模式(“饿汉模式”和“懒汉模式”实现单例模式)

目录 一、单例模式概述 二、“饿汉模式”实现单例模式 三、“懒汉模式”实现单例模式 3.1 单线程下的“懒汉模式” 3.2 多线程下的“懒汉模式” 一、单例模式概述 1&#xff09;什么是单例模式&#xff1f; 单例模式是一种设计模式。 单例模式可以保证某个类在程序中只存…

Leetcode 40 组合总和 II

题意理解&#xff1a; 每个数字在每个组合中只能使用 一次 数字可以重复——>难点&#xff08;如何去重&#xff09; 每个组合和target 求组合&#xff0c;对合限制&#xff0c;考虑回溯的方法。——将其抽象为树结构。 树的宽度——分支大小 树的深度——最…

分配栈空间的三种方式(基于适配qemu的FreeRTOS分析)

1、定义全局的数组 定义的全局数组属于bss段&#xff0c;相当于把bss段的一部分作为栈空间&#xff0c;栈空间的大小就是数组的大小如果把栈空间放在bss段&#xff0c;则在bss段清零时会多清零一段地址空间 2、在链接脚本中指定 用链接脚本在所有段的后面增加stack段&#xff…

Altair Radioss碰撞 安全与冲击 衡祖仿真

Altair Radioss是解决瞬态加载工况下非线性问题的领先的结构分析求解器。其具备高扩展性、高品质、高鲁棒性&#xff0c;以及诸多功能&#xff1a;多域求解技术、高级材料功能(复合材料)等。Radioss求解器被广泛应用于汽车、航空航天、电子/家电、包装、轨道机车、生物医疗、能…

数据结构(C语言)

链表 链表的基本能操作 #include <stdbool.h> #include <stdio.h> #include <stdlib.h>//链表的接口 typedef struct node_s{int val;struct node_s*next; } Node; typedef struct linkedlist_s{Node* head;Node* tail;int size; }LinkedList;//创建空链表…

腾讯物联网平台之规则引擎

1.腾讯物联网平台简介 腾讯云物联网开发平台&#xff08;IoT Explorer&#xff09;为客户提供便捷的物联网开发工具与服务&#xff0c;助力客户更高效的完成设备接入&#xff0c;并为客户提供物联网应用开发及场景服务能力&#xff0c;帮助客户高效、低成本构建物联网应用。  …

Java LeetCode篇-二叉树经典解法(实现:判断平衡二叉树、找两个节点最近的祖先等)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 平衡二叉树 1.1 实现判断平衡二叉树的思路 1.2 代码实现判断平衡二叉树 2.0 二叉树的层序遍历 2.1 实现二叉树层序遍历的思路 2.2 代码实现二叉树层序遍历 3.0 …

Linux开发工具--vim

Linux开发工具--vim 一、vim的基本概念二、常见命令三、简单配置vim配置文件的位置常用配置选项&#xff0c;用来测试使用插件 一、vim的基本概念 vim编辑器&#xff0c;只负责写代码&#xff0c;vim是一款多模式的编辑器 vim的三种模式(其实有好多模式&#xff0c;目前掌握这…

PbootCMS 前台RCE漏洞复现

0x01 产品简介 PbootCMS是全新内核且永久开源免费的PHP企业网站开发建设管理系统,是一套高效、简洁、 强悍的可免费商用的PHP CMS源码,能够满足各类企业网站开发建设的需要 0x02 漏洞概述 PbootCMS v<=3.1.6版本中存在模板注入,攻击者可构造特定的链接利用该漏洞,执行…

跟着我学Python基础篇:06.列表

往期文章 跟着我学Python基础篇&#xff1a;01.初露端倪 跟着我学Python基础篇&#xff1a;02.数字与字符串编程 跟着我学Python基础篇&#xff1a;03.选择结构 跟着我学Python基础篇&#xff1a;04.循环 跟着我学Python基础篇&#xff1a;05.函数 目录 往期文章1. 列表的基本…

ES-分析器

分析器 两种常用的英语分析器 1 测试工具 #可以通过这个来测试分析器 实际生产环境中我们肯定是配置在索引中来工作 GET _analyze {"text": "My Moms Son is an excellent teacher","analyzer": "english" }2 实际效果 比如我们有下…

win10脚本 | 使用 Word 自动化对象模型找出指定路径下含有特定内容的.docx

场景 今年的实验日志被我放在这样一个文件夹下&#xff0c;每个月下是每天具体的.docx文件&#xff0c;里面记录了我的一些实验操作步骤。现在我需要补充一个实验&#xff0c;用到一个名为chatunitest的插件&#xff0c;但是这是很久之前做的事情了&#xff0c;我无法判断是哪…

PHP 之道(PHP The Right Way 中文版)

PHP 之道&#xff08;PHP The Right Way 中文版&#xff09;

2022年重庆市职业院校技能大赛高职组“信息安全管理与评估”赛项竞赛任务书-试题01

信息安全管理与评估 第一阶段 网络平台搭建与设备安全防护 目 录 第一阶段竞赛项目试题 介绍 所需的设备、机械、装置和材料 评分方案 注意事项 项目和任务描述 1.网络拓扑图 2.IP地址规划表 工作任务 任务1&#xff1a;网络平台搭建 任务2&#xff1a;网络安全设备…

Find My手链|苹果Find My技术与手链结合,智能防丢,全球定位

手链是一种首饰&#xff0c;配戴在手腕部位&#xff0c;多为金银等金属制品&#xff0c;也有矿石、水晶等制的。手链是链状的&#xff0c;以祈求平安&#xff0c;镇定心志和美观为主要用途。手链可以展示个人的风格和品味&#xff0c;通过选择不同材质、款式和颜色的手链&#…