【C++】——继承(详解)

一  继承的定义和概念

1.1  继承的定义

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保
持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类被继承的称为基类

继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复用,继承是类设计层次的复用。 

下面由代码来进行理解吧 

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person
{
public:
	void Print()
	{
		cout << "name:" <<_name<< endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "喜羊羊";
	int _age = 21;
};

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


class Teacher : public Person
{
protected:
	int _jobid; // 工号
};
int main()
{
	Student s;
	Teacher t;
	s.Print();
	t.Print();
	return 0;
}

这段代码分为两个个部分 

class Person
{
public:
	void Print()
	{
		cout << "name:" <<_name<< endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "喜羊羊";
	int _age = 21;
};

这个也就是我们之前写的class类的写法

第二部分

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


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

 对于学生和老师来说,他们都有一个共同的特性,那就是人,那我们把人的属性封装起来,然后用老师和学生去复用他,你也就达到了目的,这里的复用也可以称之为继承。

那继承的方法是什么呢?

class 新类的名字:继承方式 继承类的名字{};

就和这个例子一样 

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

这里的Student和Tercher我们规范里面称之为派生类,对于Person类来说我们称之为基类。

但是一般来说我们会称之为子类和父类。

1.2 继承的访问权限

由上面我们知道,public是一种继承的方式,但是还有其他的,他们继承方式的不同,访问权限也就不一样

可以看出全部组合起来有很多,但是我们发现一个规律就是他们都是向下取舍的。

我们知道它们三个的权限大小分别为 public>protected>private ,所以如果我们把它们两两组合,取的是权限小的那一个。

1.3 继承的细节

1.如果是private成员,那么无论什么方式都是不可见的,这里不可见是指派生类在类里面和类外面都不能访问,但是它们确确实实是继承了

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person
{
public:
	void Print()
	{
		cout << "name:" <<_name<< endl;
		cout << "age:" << _age << endl;
	}
private:
	string _name = "喜羊羊";
	int _age = 21;
};

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


上面代码中Person成员变量变为私有,但是确确实实是继承了,唯一的就是不能访问

 

 

2.对于protected来说,它不像private那样那么严格,它可以让你在类里面访问它,但是在类外面也不能访问。可以看出保护成员限定符是因继承才出现的。

3. 关键字struct默认继承的方式是public,class默认继承方式是private;

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

我们之前的类型转换是创建一个临时变量然后进行赋值,但是这里不一样,这里采用的切片的方式进行的

也就是说子类给父类的时候,会进行一个类似切片的操作,把指给父类,但是如果是父类给子类,那么就会有问题了,因为父类没有子类的_No成员,所以给不了它相应的值。

这里也可以用指针和引用进行操作,但是也有许多情况

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person
{
protected:
	string _name;
	string _sex;
	int _age;
};
class Student : public Person
{
public:
	int _No; // 学号
};
int main()
{
	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;
	return 0;
}

 这里着重看一下后面这段

    pp = &sobj;
	Student* ps1 = (Student*)pp; // 这种情况转换时可以的。
	ps1->_No = 10;

	pp = &pobj;
	Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问
	ps2->_No = 10;

这里的第一部分pp实际上是指向student对象,所以这里ps1也指向student对象,所以可以访问里面的成员

但是第二部分pp是指向Person对象,这里直接强转,相当于告诉编译器忽略这个事实,虽然也是转了,ps2也指向这个对象,但是里面没有_No这个成员,所以会发生越界访问的存在

三  继承中的作用域

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

 3.1  同名成员变量

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
// 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;
        cout << " 学号:" << _num << endl;
    }
protected:
    int _num = 999; // 学号
};
void Test()
{
    Student s1;
    s1.Print();
};

 

 void Print()
    {
        cout << " 姓名:" << _name << endl;
        cout << " 身份证号:" << Person::_num << endl;
        cout << " 学号:" << _num << endl;
    }

我们在打印身份证号的时候采用了指定类域的方式,如果不这样,那么就会导致编译器分不清,那么就直接打印子类的成员了

 

 3.2  同名成员函数

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

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和A中的fun不是构成重载,因为不是在同一作用域
 B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏。

 四  派生类的默认成员函数

4.1  构造函数

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class Person {
public:
	Person(string name = "喜羊羊")
		:_name(name)
	{
		cout << name << endl;
	}
protected:
	string _name;
};


class student :public Person {
public:
	student(string name, int age)
		:_age(age)
	{
		cout << name << endl << age << endl;
	}
protected:
	int _age;
};


int main()
{
	student st("沸羊羊", 18);
	return 0;
}

对于构造来说,它是先调用父类的构造函数,然后再调用子类的构造函数

子类的构造函数没有去初始化父类的,这里是编译器自己去调用父类的默认构造函数,跟自定义类型是一个道理。

4.2  析构函数

这里析构函数和构造函数相反,这里是先调用子类的,再调用父类的

这里也可以用先构造的后析构,后构造的先析构来解释

class Person {
public:
	Person(string name = "喜羊羊")
		:_name(name)
	{
		cout << name << endl;
	}
	~Person()
	{
		cout << "~Person()" << endl;
	}
protected:
	string _name;
};


class student :public Person {
public:
	student(string name, int age)
		:_age(age)
	{
		cout << name << endl << age << endl;
	}
	~student()
	{
		cout << "~student()" << endl;
	}
protected:
	int _age;
};

 如果我们在子类中调用父类的析构,如果是指针类型,就会被析构两次。其次之所以先调用子类的析构后调用父类的析构,是因为可能析构的时候存在某些记录工作,所以不能先调用父类的析构函数

4.3  拷贝构造

派生类对象通常包含基类部分和派生类特有的部分。当创建一个派生类对象的副本时,我们需要确保这两部分都被正确地复制。基类部分的复制是通过调用基类的拷贝构造函数来完成的。

如果不调用父类的拷贝构造,那么就会导致资源缺失

class Person {
public:
	Person(string name = "喜羊羊")
		:_name(name)
	{
		cout << name << endl;
	}
	~Person()
	{
		cout << "~Person()" << endl;
	}
	Person(const Person&s):_name(s._name)
	{

	}
protected:
	string _name;
};


class student :public Person {
public:
	student(string name, int age)
		:_age(age)
	{
		cout << name << endl << age << endl;
	}
	~student()
	{
		
		cout << "~student()" << endl;
	}
	student(const student&s) :Person(s),_age(s._age)//这里Person的拷贝,直接传一个s过去切片就行
	{

	}
protected:
	int _age;
};

这里的拷贝构造重点就在于巧妙运用了Person的切片,其实这里可以不写,编译器默认生成的就够用了,但是我们要显示调用就必须这样写

4.4  赋值运算符重载

class Person {
public:
	Person(string name = "喜羊羊")
		:_name(name)
	{
		cout << name << endl;
	}
	~Person()
	{
		cout << "~Person()" << endl;
	}
	Person(const Person&s):_name(s._name)
	{

	}
	Person& operator=(const Person&s)
	{
		if (this != &s)
		{
			_name = s._name;
		}
		return *this;
	}
protected:
	string _name;
};


class student :public Person {
public:
	student(string name, int age)
		:_age(age)
	{
		cout << name << endl << age << endl;
	}
	~student()
	{
		
		cout << "~student()" << endl;
	}
	student(const student&s) :Person(s),_age(s._age)
	{

	}
	student& operator=(const student& s)
	{
		if (this != &s)
		{
			Person::operator=(s);
			_age = s._age;
		}
		return *this;
	}
protected:
	int _age;
};

这里的赋值运算符重载需要指定类域,不然就会出现死循环,一直调用自己的成员函数,因为这两个是同名的,属于隐藏关系。

五 单继承和多继承

单继承:

一个子类只有一个父类

多继承:

一个子类有多个父类

菱形继承:

菱形继承是多继承的一个特例,同时菱形继承也弄出了很多问题

1.在菱形继承中,Student和Teach都继承了Person里面的成员变量a,后面Assistant继承了它们两个,那么Assistant是不是有两个a呢,这里就是数据的二义性。

2.对于二义性我们可以加访问限定符去解决,但是我们还是存在一个问题就是有两个a,我们继承下来就想要一个,这个就是数据冗余,对于数据冗余我们能用的就是虚继承去解决

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
class A
{
public:
	int _a;
};
class B :  public A
{
public:
	int _b;
};
// class C : public A
class C : public A
{
public:
	int _c;
};
class D : public B, public C
{
public:
	int _d;
};
int main()
{
	D d;
	d._a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;
	return 0;
}

如果我们直接访问

    d._a = 1;

但是我们加上就欧克了

d.B::_a = 1;

这里也就解决数据二义性的问题,但是对于数据冗余,我们采用虚继承,这里继承以后就是把_a当作成共有的了

那么虚继承就是 会在类B和类C里面生成一个虚基表指针,这指针指向一张表,表里面存有偏移量,然后通过这个偏移量找到_a,所以这里的_a是共有的。

 

六  继承其他问题

1. 继承如果父类有友元函数,继承以后,友元函数是不能被子类使用的。

2. 基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例 。这里也可以理解为是共有的,所以不会重复。

七  总结

以上就是继承的全部内容了,希望对你有所帮助

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

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

相关文章

【多元统计】期末复习必备!按题型分类

一&#xff0c;简答题 二&#xff0c;证明题 三&#xff0c;计算题

推荐一款WPF绘图插件OxyPlot

开始 使用 NuGet 包管理器添加对 OxyPlot 的引用&#xff08;如果要使用预发布包&#xff0c;请参阅下面的详细信息&#xff09;向用户界面添加PlotView在代码中创建一个PlotModel绑定到你的属性PlotModelModelPlotView 例子 您可以在代码存储库的文件夹中找到示例。/Source/Ex…

9 - 上升的温度(高频 SQL 50 题基础版)

9 - 上升的温度 -- 找出与之前&#xff08;昨天的&#xff09;日期相比温度更高的所有日期的 id -- DATEDIFF(2007-12-31,2007-12-30); # 1 -- DATEDIFF(2010-12-30,2010-12-31); # -1select w1.id from Weather w1, Weather w2 wheredatediff(w1.recordDate,w2.recordDat…

【MySQL】表的基本增删查改(结合案例)

文章目录 1.前言2.插入数据&#xff08;Create&#xff09;2.1案例2.2单行数据全列插入2.3多行数据指定列插入2.4插入否则更新2.5替换 3. 读取数据(Retireve)3.1案例3.2全列查询3.3指定列查询3.4查询字段为表达式3.5为查询结果起别名3.6去重3.7where条件3.7.1案例 3.8排序3.9筛…

初识 AQS

一、什么是 AQS AQS是一个用来构建锁和同步器的框架。JUC 的同步器底层都是用了 AQS&#xff0c;例如ReentrantLock&#xff0c;Semaphore&#xff0c;CountDownLatch&#xff0c;CyclicBarrier&#xff0c;ReentrantReadWriteLock。 二、前置知识 在了解 AQS之前&#xff0c…

C++ 01 之 hello world

c01helloworld.cpp #include <iostream>using namespace std;int main() {cout << "hello world" << endl;return 0; } #include<iostream>; 预编译指令&#xff0c;引入头文件iostream.using namespace std; 使用标准命名空间cout <&l…

LW-DETR:实时目标检测的Transformer, Apache-2.0 开源可商用,论文实验超 YOLOv8

LW-DETR&#xff1a;实时目标检测的Transformer&#xff0c; Apache-2.0 开源可商用&#xff0c;论文实验超 YOLOv8 LW-DETR 架构实例化高效训练高效推理 目的与解法拆解ViT编码器和DETR解码器多级特征图聚合变形交叉注意力窗口注意力和全局注意力 论文&#xff1a;https://arx…

1124. 表现良好的最长时间段 (python) 前缀和 分类讨论 最大长度 力扣 面试题

给你一份工作时间表 hours&#xff0c;上面记录着某一位员工每天的工作小时数。 我们认为当员工一天中的工作小时数大于 8 小时的时候&#xff0c;那么这一天就是「劳累的一天」。 所谓「表现良好的时间段」&#xff0c;意味在这段时间内&#xff0c;「劳累的天数」是严格 大…

什么是 URL 过滤?是如何保障浏览体验的?

互联网是一个无边无际的空间&#xff0c;几乎包含了你能想象到的一切。不幸的是&#xff0c;这意味着也存在着从不合适到非常危险的网站。这就是 URL 过滤可以发挥作用的地方。 一、URL 过滤的含义 我们希望您已经熟悉 URL&#xff08;统一资源定位器&#xff09;&#xff0c;…

在韩国遇到阿姨叫“아줌마”还是“이모”?都不如称呼好!柯桥学韩语来银泰附近基础教学通俗易懂

认识母音 母音&#xff0c;又叫元音&#xff0c;共21个&#xff0c;包含10个基本母音和11复合母音&#xff08;又称双元音&#xff09;。 10个基本母音&#xff1a;ㅏ(a)、ㅑ(ya)、ㅓ(eo)、ㅕ(yeo)、ㅗ(o)、ㅛ(yo)、ㅜ(u)、ㅠ(yu)、ㅡ(eu)、ㅣ(i) 11个复合母音&#xff1a;ㅐ(a…

【ETAS CP AUTOSAR基础软件】BswM模块详解

文章包含了AUTOSAR基础软件&#xff08;BSW&#xff09;中BswM模块相关的内容详解。本文从AUTOSAR规范解析&#xff0c;ISOLAR-AB配置以及模块相关代码分析三个维度来帮读者清晰的认识和了解BswM这一基础软件模块。文中涉及的SOLAR-AB配置以及模块相关代码都是依托于ETAS提供的…

pdf添加书签的软件,分享3个实用的软件!

在数字化阅读日益盛行的今天&#xff0c;PDF文件已成为我们工作、学习和生活中不可或缺的一部分。然而&#xff0c;面对海量的PDF文件&#xff0c;如何高效地进行管理和阅读&#xff0c;成为了许多人关注的焦点。其中&#xff0c;添加书签功能作为提高PDF文件阅读体验的重要工具…

数据结构01 栈及其相关应用

栈是一种线性数据结构&#xff0c;栈的特征是数据的插入和删除只能通过一端来实现&#xff0c;这一端称为“栈顶”&#xff0c;相应的另一端称为“栈底”。 栈及其特点 用一个简单的例子来说&#xff0c;栈就像一个放乒乓球的圆筒&#xff0c;底部是封住的&#xff0c;如果你想…

c++线性关系求值

目的 线性关系是最简单的关系,但也是编程当中最常用的一种关系,很多行业,都用。 可以说,其是准确的,有时利用了正比例的关系,其具有预测性,检验其它数据是否正确,应用实在太多了。 生活中太多的东西可以认为成线性的,比如:年龄越大,经验越丰富,这也是线性关系,因…

揭秘湖北工程类助理工程师证书:纸质版 vs 电子版,哪个更靠谱

"揭秘湖北工程类助理工程师证书&#xff1a;纸质版 vs 电子版&#xff0c;哪个更靠谱&#xff1f;" 2024年湖北工程类助理工程师证书纸质版VS电子版 很多人会疑惑不是从2021年底就发布相关文件&#xff0c;湖北初级、中级、高级职称进入电子版证书时代&#xff0c;为…

分组聚集查询-GROUP BY子句

一、GROUP BY子句位置 SELECT 【ALL|DISTINCT】<目标列表达式1>【,<目标列表达式2>,...】 FROM <表名或视图名1>【&#xff0c;<表名或视图名2>&#xff0c;...】 【WHERE <元组选择条件表达式>】 【GROUP BY <属性列名1>【&#xff0…

2024 年 5 月公链研报:监管调整与市场新动向

作者&#xff1a;stellafootprint.network 数据来源&#xff1a;公链 Research 页面 五月份&#xff0c;加密货币市场经历了重要的监管和政治动态。美国证券交易委员会&#xff08;SEC&#xff09;批准了现货以太坊 ETF 的初步申请文件&#xff0c;这一举措提振了以太坊及其…

pom学习笔记:kimi的自动化操作

1.先看结构&#xff1a; 声明&#xff1a;我是初学&#xff0c;可能有不合理的地方。 2.Base层。 我是把原来一个kimi的自动问答的代码改过来。 分析&#xff1a;其实我是新手&#xff0c;因为我用的浏览器是固定的&#xff0c;也没有打算和别人用。所以浏览器层面年的全部写…

蓝牙芯片TD5322A,蓝牙5.1数传芯片介绍—拓达半导体

蓝牙芯片原厂&#xff0c;拓达芯片TD5322A是一颗支持蓝牙BLE和SPP的数传芯片&#xff0c;蓝牙5.1版本。芯片的优点是尺寸小(SOP-8封装&#xff09;&#xff0c;性能强&#xff0c;价格低&#xff0c;以及简单明了的透传和串口AT控制功能&#xff0c;大大降低了在其它电子产品中…

React 渲染流程分析

React 页面是由组件组成的&#xff0c;从根组件直到叶组件&#xff0c;内部的组件数通过 Fiber 来保存并触发并发更新。页面的展示分为两部分&#xff0c;首先是初始化&#xff0c;所有组件首次展示&#xff0c;都要进行渲染&#xff0c;之后是更新流程&#xff0c;也就是页面产…