【C++】:继承

朋友们、伙计们,我们又见面了,本期来给大家解读一下有关C++继承的知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!

C 语 言 专 栏:C语言:从入门到精通

数据结构专栏:数据结构

个  人  主  页 :stackY、

C + + 专 栏   :C++

Linux 专 栏  :Linux

目录

1. 继承的概念及定义

1.1 继承的概念

1.2 继承的定义

1.2.1 定义的格式

1.2.2 继承关系和访问限定符

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

2. 基类和派生类对象赋值转化

3. 继承中的作用域

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

4.1 构造函数

4.2 拷贝构造

4.3 赋值重载

4.4 析构函数

4.5 完整代码

5. 继承于友元

6. 继承与静态成员

7. 继承和组合


1. 继承的概念及定义

1.1 继承的概念

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

class Person
{
public:
	void Print()
	{
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "peter"; // 姓名
	int _age = 18; // 年龄
};
// 继承后父类的Person的成员(成员函数+成员变量)都会变成子类的一部分。这里体现出了
//Student和Teacher复用了Person的成员。
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;
}

继承的本质是一种复用

1.2 继承的定义

1.2.1 定义的格式

下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类。

1.2.2 继承关系和访问限定符

在学习继承之前我们认为protected和private是没有区别的,但是在学习完继承之后对于这两个访问限定符就要有区别之分。

1.2.3 继承基类成员访问方式的变化
类成员/继承方式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继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强。

2. 基类和派生类对象赋值转化

  • 派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
  • 基类对象不能赋值给派生类对象。
  • 基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。这里基类如果是多态类型,可以使用RTTI(RunTime Type Information)的dynamic_cast 来进行识别后进行安全转换。(ps:这个我们后面再讲解,这里先了解一下)

class Person
{
public:
	Person()
	{
		cout << "_name:" << _name << endl;
		cout << "_age:" << _age << endl;
	}
protected:
	string _name = "Bob";
	int _age = 18;
};

class Student : public Person
{
public:
	Student()
	{
		cout << "_id:" << _id << endl;
	}
public:
	int _id = 226;
};

int main()
{
	Student stobj;
	// 1.子类对象可以赋值给父类对象/指针/引用
	Person pobj = stobj;
	Person* pp = &stobj;
	Person& rp = stobj;

	//2.基类对象不能赋值给派生类对象
	//stobj = pobj;   //没有与之匹配的操作数

	// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
	Student* pst1;
	pst1 = &stobj;
	Student* pst2 = (Student*)pp; // 这种情况转换是可以的。
	pst2->_id = 227;

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

	return 0;
}

3. 继承中的作用域

  • 1. 在继承体系中基类和派生类都有独立的作用域。
  • 2. 子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基类::基类成员 显示访问)
  • 3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
  • 4. 注意在实际中在继承体系里面最好不要定义同名的成员。
// Student的_id和Person的_id构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
class Person
{
protected:
	string _name = "张三";  //姓名
	int _id = 999;   //身份证
};

class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名:" << _name << endl;
		cout << " 身份证号:" << _id << endl;
		cout << " 学号:" << _id << endl;
	}
protected:
	int _id = 111;   //学号
};

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

class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名:" << _name << endl;
		cout << " 身份证号:" << Person::_id << endl; //对类域进行特指
		cout << " 学号:" << _id << endl;
	}
protected:
	int _id = 111;   //学号
};

// 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;
	}
};

int main()
{
	B b;
	b.fun(10);
	b.A::fun();  //构成隐藏,需要特指类域进行调用
	return 0;
}

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

6个默认成员函数,“默认”的意思就是指我们不写,编译器会变我们自动生成一个,那么在派生类中,这几个成员函数是如何生成的呢?

如果我们不写,编译器会默认生成,而生成的会去调用基类的各种默认成员函数。

//基类
class Person
{
public:
	Person(const char* name = "Bob")
		:_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;
	}
	~Person()
	{
		cout << "~Person()" << endl;
	}
private:
	string _name;
};
//派生类
class Student :public Person
{
	//什么都不写,编译器会默认生成
private:
	int _age;
};
int main()
{
	Person p("Peter");
	Student s;
	p = s;
	return 0;
}

注意:

构造是先构造基类,再构造派生类,而析构的时候是先析构派生类,再析构基类。

4.1 构造函数

编译器默认生成的构造函数会去调用父类的构造函数,那么我们要自己实现的写该怎么去写呢?

//派生类
class Student :public Person
{
public:
	Student(const char* name, int age)
		:Person(name)   //使用匿名对象直接用父类构造函数进行构造
		,_age(age)
	{
		cout << "Student()" << endl;
	}
private:
	int _age;
};

4.2 拷贝构造

在前面的基类与派生类对象的赋值转化中说到过派生类对象可以赋值给基类对象,这种行为叫做切割或者切片,那么在派生类的拷贝构造中也需要用派生类的对象构造出一个基类的对象,那么可以直接将派生类的对象传递给基类,是实现切割,完成拷贝构造。

//派生类
class Student :public Person
{
public:
	Student(const char* name, int age)
		:Person(name)   //使用匿名对象直接用父类构造函数进行构造
		,_age(age)
	{
		cout << "Student()" << endl;
	}
	Student(const Student& s)
		:Person(s)  //将派生类对象传递给基类实现切割从而完成拷贝构造
		,_age(s._age)
	{
		cout << "Student(const Student& s)" << endl;
	}
private:
	int _age;
};

4.3 赋值重载

派生类的赋值重载同样的可以用派生类的对象去显示的调用基类的赋值重载来进行切割进而完成运算符的重载。

//派生类
class Student :public Person
{
public:
	Student& operator=(const Student& s)
	{
		cout << "Student& operator= (const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator=(s);  //显示调用基类的运算符重载
			_age = s._age;
		}
		return *this;
	}
private:
	int _age;
};

4.4 析构函数

子类的析构函数如果还是显示的调用父类的析构函数就会发生多次析构,为什么呢?由于多态的原因析构函数统一会被处理成destructor,父子类的析构函数会构成隐藏,因此为了保证析构安全,先子后父,所以不需要显示调用父类的析构函数,子类析构函数结束时会自动调用父类析构,从而保证先子后父。

//派生类
class Student :public Person
{
public:
	~Student()
	{
		//Person::~Person();   //不需要显示调用,为了先子后父
		cout << "~Student()" << endl;
	}
private:
	int _age;
};

4.5 完整代码

//派生类
class Student :public Person
{
public:
	Student(const char* name, int age)
		:Person(name)   //使用匿名对象直接用父类构造函数进行构造
		,_age(age)
	{
		cout << "Student()" << endl;
	}
	Student(const Student& s)
		:Person(s)  //将派生类对象传递给基类实现切割从而完成拷贝构造
		,_age(s._age)
	{
		cout << "Student(const Student& s)" << endl;
	}
	Student& operator=(const Student& s)
	{
		cout << "Student& operator= (const Student& s)" << endl;
		if (this != &s)
		{
			Person::operator=(s);  //显示调用基类的运算符重载
			_age = s._age;
		}
		return *this;
	}
	~Student()
	{
		//Person::~Person();   //不需要显示调用,为了先子后父
		cout << "~Student()" << endl;
	}
private:
	int _age;
};

5. 继承于友元

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

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; //友元不能继承,所以不能访问子类私有
}

6. 继承与静态成员

基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子
类,都只有一个static成员实例。

 

class Person
{
public:
	Person() 
	{ 
		++_count; 
	}
protected:
	string _name; // 姓名
public:
	static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:
	int _stuNum; // 学号
};

void TestPerson()
{
	Student s1;
	Student s2;
	Student s3;
	cout << " 人数 :" << Person::_count << endl;
	Student::_count = 0;
	cout << " 人数 :" << Person::_count << endl;
}

7. 继承和组合

  • public继承是一种is-a的关系。也就是说每个派生类对象都一个基类对象。
  • 组合是一种has-a的关系。假设B组合了A,每个B对象中都一个A对象。
  • 优先使用组合,而不是继承。
  • 继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。
  • 对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。
  • 组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。
  • 实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。
  • // Car和BMW Car和Benz构成is-a的关系(继承)
    class Car {
    protected:
    	string _colour = "白色"; // 颜色
    	string _num = "陕ABIT00"; // 车牌号
    };
    class BMW : public Car {
    public:
    	void Drive() { cout << "好开-操控" << endl; }
    };
    class Benz : public Car {
    public:
    	void Drive() { cout << "好坐-舒适" << endl; }
    };
    
    // Tire和Car构成has-a的关系(组合)
    class Tire {
    protected:
    	string _brand = "Michelin"; // 品牌
    	size_t _size = 17; // 尺寸
    };
    class Car {
    protected:
    	string _colour = "白色"; // 颜色
    	string _num = "陕ABIT00"; // 车牌号
    	Tire _t; // 轮胎
    };

朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!  

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

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

相关文章

洛谷 P1064 [NOIP2006 提高组] 金明的预算方案 python解析

P1064 [NOIP2006 提高组] 金明的预算方案 时间&#xff1a;2023.11.19 题目地址&#xff1a;[NOIP2006 提高组] 金明的预算方案 题目分析 动态规划的0-1背包&#xff0c;采用动态数组。如果不了解的话&#xff0c;可以先看看这个背包DP。 这个是0-1背包的标准状态转移方程 f…

域名的理解

域名的分类 见下图 这里引用的阿里云对域名的定义&#xff0c;个人理解是有两种叫法&#xff0c;一种是传统的叫法&#xff0c;也就是将sample.org.cn划分成了三级域名&#xff0c;还有一种叫法是基于用户注册的域名来说的&#xff0c;将用户注册的整体域名称作一级域名&…

SOME/IP 协议介绍(五)指南

指南&#xff08;信息性&#xff09; 选择传输协议 SOME/IP直接支持互联网上使用最广泛的两种传输协议&#xff1a;用户数据报协议&#xff08;UDP&#xff09;和传输控制协议&#xff08;TCP&#xff09;。UDP是一种非常简洁的传输协议&#xff0c;仅支持最重要的功能&#…

Java Swing实现简单的文本编辑器

内容要求 1) 本次程序设计是专门针对 Java 课程的,要求使用 Java 语言进行具有一定代码量的程序开发。程序的设计要结合一定的算法&#xff0c;在进行代码编写前要能够设计好自己的算法。 本次程序设计涉及到 Java 的基本语法&#xff0c;即课堂上所介绍的变量、条件语句、循…

Jmeter配置脚本录制进行抓包并快速分析、定位接口问题

对于测试人员、开发人员来说&#xff0c;善用抓包工具确实是快速分析和定位问题的一大必备神技&#xff0c;现将配置过程记录如下: 1、打开jmeter后&#xff0c;首先添加—个线程组: 2、线程组可以重新命名按项目名称分类: 如果你想学习自动化测试&#xff0c;我这边给你推荐一…

Leetcode经典题目之“双指针交换元素“类题目

1 LC 27. 移除元素 class Solution {public int removeElement(int[] nums, int val) {int nnums.length;int s0;for(int i0;i<n;i){// 只有不等于目标值的时候才会进行交换&#xff0c;然后移动s指针if(nums[i]!val){swap(nums,i,s);}}return s;}void swap(int[]nums, int…

22. 深度学习 - 自动求导

Hi&#xff0c;你好。我是茶桁。 咱们接着上节课内容继续讲&#xff0c;我们上节课已经了解了拓朴排序的原理&#xff0c;并且简单的模拟实现了。我们这节课就来开始将其中的内容变成具体的计算过程。 linear, sigmoid和loss这三个函数的值具体该如何计算呢&#xff1f; 我们…

『力扣刷题本』:环形链表(判断链表是否有环)

一、题目 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&am…

苹果iOS系统开发APP应用启动几种速度优化技巧与实践

在移动应用开发过程中&#xff0c;启动速度是影响用户体验的关键因素之一。一个应用如果启动迅速&#xff0c;会给用户留下良好的第一印象&#xff0c;相反&#xff0c;如果启动缓慢&#xff0c;用户的耐心和满意度可能会大打折扣。对于iOS开发者而言&#xff0c;优化启动速度不…

uart_printf自定义串口printf输出

暂时只格式化了%s和%c&#xff0c;需要其他格式化的可自行添加&#xff0c;后续也可能更新 标准库 #include <stdarg.h> //需要包含的头文件--->任意参数功能需要void UART_printf(USART_TypeDef *USARTx, const char *fmt, ...) {va_list args;va_start(args, fmt)…

安装第三方包报错 error: Microsoft Visual C++ 14.0 or greater is required——解决办法

1、问题描述 手动安装第三方软件时&#xff0c;可以使用setup.py&#xff0c;来安装已经下载的第三方包。一般文件下会存在setup&#xff0c;在所要安装库的目录下的cmd执行&#xff1a;python setup.py install报错&#xff1a;error: Microsoft Visual C 14.0 or greater i…

详解ssh远程登录服务

华子目录 简介概念功能 分类文字接口图形接口 文字接口ssh连接服务器浅浅介绍一下加密技术凯撒加密加密分类对称加密非对称加密非对称加密方法&#xff08;也叫公钥加密&#xff09; ssh两大类认证方式&#xff1a;连接加密技术简介密钥解析 ssh工作过程版本协商阶段密钥和算法…

程序员如何做事更细致?

最近在工作中老是犯一些小错误&#xff0c;哦&#xff0c;当然也不是最近了&#xff0c;其实我一直是个马虎的人&#xff0c;我很讨厌做一些细活&#xff0c;因为这会让我反复改动多次在会成功&#xff0c;而平时的代码由于有debug&#xff0c;即便出错了&#xff0c;再改回来即…

高效背单词——单词APP安利

大英赛&#xff0c;CET四六级&#xff0c;以及考研英语&#xff0c;都在不远的未来再度来临&#xff0c;年复一年的考试不曾停息&#xff0c;想要取得好成绩&#xff0c;需要我们的重视并赋予相应的努力。对于应试英语&#xff0c;词汇量是不可忽略的硬性要求。相比于传统默写&…

SOME/IP 协议介绍(六)接口设计的兼容性规则

接口设计的兼容性规则&#xff08;信息性&#xff09; 对于所有序列化格式而言&#xff0c;向较新的服务接口的迁移有一定的限制。使用一组兼容性规则&#xff0c;SOME / IP允许服务接口的演进。可以以非破坏性的方式进行以下添加和增强&#xff1a; • 向服务中添加新方法 …

Node.js环境配置级安装vue-cli脚手架

一、下载安装Node.js (略) 二、验证node.js并配置 1、下载安装后&#xff0c;cmd面板输入node -v查询版本、npm -v ,查看npm是否安装成功&#xff08;有版本号就行了&#xff09; 2、选择npm镜像&#xff08;npm config set registry https://registry.npm.taobao.org&…

笔记55:长短期记忆网络 LSTM

本地笔记地址&#xff1a;D:\work_file\DeepLearning_Learning\03_个人笔记\3.循环神经网络\第9章&#xff1a;动手学深度学习~现代循环神经网络 a a a a a a a a a

如何解决msvcr100.dll丢失问题?5个实用的解决方法分享

在日常计算机操作过程中&#xff0c;相信不少小伙伴都经历过这样一种困扰&#xff0c;那便是某款应用程序或者游戏无法正常启动并弹出“找不到msvcr100.dll”的提示信息。这类问题让人头疼不已&#xff0c;严重影响到了我们的工作效率和休闲娱乐。接下来&#xff0c;就让小编带…

Amazon EC2的出现,是时代的选择了它,还是它选择了时代

目录 Amazon EC2简介 友商云服务器对比&#xff08;Amazon VS Tencent&#xff09; 友商云服务器对比&#xff08;Amazon VS Alibaba&#xff09; Amazon 云服务器的绝对优势 Amazon EC2功能 Amazon EC2 Linux 实例入门 启动实例 连接到的实例 清除的实例 终止的实例…