【1++的C++初阶】之继承

news2024/12/26 10:48:18

👍作者主页:进击的1++
🤩 专栏链接:【1++的C++进阶】

文章目录

  • 一,什么是继承?
  • 二,基类和派生类对象赋值转换
  • 三,派生类的默认成员函数
  • 四,继承与友元,静态成员
  • 五,菱形继承及菱形虚拟继承
  • 六,总结

一,什么是继承?

在这里插入图片描述
继承机制是面向对象程序设计使代码可以复用的重要手段。它使得我们可以在原有类的基础上可以进行扩展,其产生的新类我们称为派生类或者子类;原来的类我们称为基类或父类。继承呈现了面向对象程序设计的层次结构。
我们以一下代码为例:

class person
{
public:
	void Print()
	{
		cout << "person" << endl;
	}
protected:
	char name;
	int age;
};

class student : public person
{
private:
	int num;//学号
};


void Test1()
{
	student s1;
	person p1;
	s1.Print();
	p1.Print();
}

在这里插入图片描述
在这里插入图片描述

上述代码中,我们先是定义了一个person类,其定义了人的基本信息:姓名,年龄。接着,又定义了另一个学生类,定义了学号,并且继承了person类,复用了person的成员。通过监视窗口 我们可以很清楚的看到,在student类实例化出的对象s1中,其成员变量除了自己的还有从person中继承下来的成员变量。成员函数也继承了下来。

讲完了什么是继承,我们接下来讲继承的格式。

//    派生类    继承方式  基类
class student : public person
{
private:
	int num;//学号
};

如上就是继承的格式,派生类和基类我们在前面提过,那么什么是继承方式呢?
在这里插入图片描述
继承方式与我们的访问限定符一样都是public , protected ,private三种。
继承方式与访问限定符的关系如下表:
在这里插入图片描述
这里我们可以总结一个规律来记住上表:我们设public的权限>protected的权限>private的权限。继承后的成员的访问限定符则是在基类的访问限定符与继承方式中选择权限最小的那一个。接下来我们来细细讲解继承后的成员权限的不同处。若继承后是public成员,则在类的里面外面都可以进行访问 ;若是protected则只能在类里面访问;若是private,则不可见,也就是其虽然被继承到了派生类中,但在类里面类外面都访问不了。
在这里插入图片描述

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

派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。寓意把派生类中父类那部分切来赋值过去。
以下代码为例:

class person
{
public:
	void Print()
	{
		cout << "person" << endl;
	}
protected:
	char name;
	int age;
};

//    派生类    继承方式  基类
class student : public person
{
private:
	int num=111;//学号
};

void Test2()
{
	student s1;
	person p1 = s1;//派生类对象赋值给基类
	person* ptr1 = &s1;//派生类对象赋值给基类的指针
	person& p2 = s1;//派生类对象赋值给基类的引用
}

在这里插入图片描述
通过观察结果,我们发现确实有切片的现象。

基类对象不能赋值给派生类对象。

基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类的指针是指向派生类对象时才是安全的。
要注意的是:这种情况转换时虽然可以,但是会存在越界访问的问题。

在这里插入图片描述

基类与派生类的作用域

基类与派生类都有其独立的作用域。
子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义。

如下:

class person
{
public:
	int Print()
	{
		cout << "person" << endl;
	}
protected:
	char name;
	int age;
};

//    派生类    继承方式  基类
class student : public person
{
public:
	void Print()
	{
		cout << "test" << endl;
	}

	int num=111;//学号
};


void Test1()
{
	student s1;
	s1.Print();
	
}

在这里插入图片描述
若想访问基类中的同名函数,可以通过 基类::基类成员
在这里插入图片描述
还要注意区分重载与隐藏:
重载必须在同一个作用域。

三,派生类的默认成员函数

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

来看代码:

class person
{
public:

	person(string name="张三", const int age = 20)
		:_name(name)
		,_age(age)
	{
		cout << "person()" << endl;
	}
	
	void Print()
	{
		cout << "person" << endl;
	}
protected:
	string _name;
	int _age;
};

//    派生类    继承方式  基类
class student : public person
{
public:

	student(int num,string name = "张三", const int age = 20)
		:_num(num)
		//,person(name,age)
	{
		cout << "student()" << endl;
	}

	void Print()
	{
		cout << "test" << endl;
	}
private:
	int _num=111;//学号
};

void test3()
{
	student s1(12345);
}

当基类有默认构造时,派生类会自己调用基类的构造函数去初始化基类的那部分成员。
在这里插入图片描述
若没有默认构造则必须在派生类初始化列表显式调用。

student(int num,string name = "张三", const int age = 20)
		:_person(name,age)_num(num)
	{
		cout << "student()" << endl;
	}
		
	{
		cout << "student()" << endl;
	}

拷贝构造
派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。

class person
{
public:

	person(string name="张三", const int age = 20)
		:_name(name)
		,_age(age)
	{
		cout << "person()" << endl;
	}

	person(const person& p)
	{
		_name = p._name;
		_age = p._age;
		cout << "person拷贝" << endl;
	}
	
	void Print()
	{
		cout << "person" << endl;
	}
protected:
	string _name;
	int _age;
};

//    派生类    继承方式  基类
class student : public person
{
public:

	student(int num,string name = "张三", const int age = 20)
		:_num(num)
		
	{
		cout << "student()" << endl;
	}

	student(const student& s)
		:person(s)
		,_num(s._num)
	{
		cout << "student拷贝" << endl;
	}


	void Print()
	{
		cout << "test" << endl;
	}
private:
	int _num;//学号
};

void test3()
{
	student s1(12345);
	//student s2("李四",21,12345);
	student s2(s1);
}

在这里插入图片描述

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

class person
{
public:

	person(string name="张三", const int age = 20)
		:_name(name)
		,_age(age)
	{
		cout << "person()" << endl;
	}

	person(const person& p)
	{
		_name = p._name;
		_age = p._age;
		cout << "person拷贝" << endl;
	}

	person& operator=(const person& p)
	{
		cout << "operator=" << endl;
		if (this != &p)
		{
			_name = p._name;
			_age = p._age;
		}
		return *this;
	}
		
		
		
	
	
	void Print()
	{
		cout << "person" << endl;
	}
protected:
	string _name;
	int _age;
};

//    派生类    继承方式  基类
class student : public person
{
public:

	student(int num,string name = "张三", const int age = 20)
		:person(name,age)
		,_num(num)
		
	{
		cout << "student()" << endl;
	}

	student(const student& s)
		:_num(s._num)
	{
		cout << "student拷贝" << endl;
	}

	student& operator=(const student& s)
	{
		cout << "operator=" << endl;
		if (this != &s)
		{
			person::operator=(s);
			_num = s._num;
		}

		return *this;
	}


	void Print()
	{
		cout << "test" << endl;
	}
private:
	int _num;//学号
};

void test3()
{
	student s1(12345);
	student s2(123,"李四",21);
	//student s2(s1);
	s1 = s2;

}

在这里插入图片描述

析构函数

派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能保证派生类对象先清理派生类成员再清理基类成员的顺序。

class person
{
public:

	person(string name="张三", const int age = 20)
		:_name(name)
		,_age(age)
	{
		cout << "person()" << endl;
	}

	person(const person& p)
	{
		_name = p._name;
		_age = p._age;
		cout << "person拷贝" << endl;
	}

	person& operator=(const person& p)
	{
		cout << "operator=" << endl;
		if (this != &p)
		{
			_name = p._name;
			_age = p._age;
		}
		return *this;
	}
		
	~person()
	{
		cout << "~person" << endl;
	}

	void Print()
	{
		cout << "person" << endl;
	}
protected:
	string _name;
	int _age;
};

//    派生类    继承方式  基类
class student : public person
{
public:

	student(int num,string name = "张三", const int age = 20)
		:person(name,age)
		,_num(num)
		
	{
		cout << "student()" << endl;
	}

	student(const student& s)
		:_num(s._num)
	{
		cout << "student拷贝" << endl;
	}

	student& operator=(const student& s)
	{
		cout << "operator=" << endl;
		if (this != &s)
		{
			person::operator=(s);
			_num = s._num;
		}

		return *this;
	}

	~student()
	{
		cout << "~student" << endl;
	}

	void Print()
	{
		cout << "test" << endl;
	}
private:
	int _num;//学号
};

void test3()
{
	student s1(12345);
	//student s2(123,"李四",21);
	//student s2(s1);
	//s1 = s2;

}

在这里插入图片描述
通过上述运行结果我们发现,在派生类的析构函数调用完后,自动调用了基类的析构函数。
若我们我们在派生类中显式调用基类的析构函数时。

~student()
	{
		person::~person();
		cout << "~student" << endl;
	}

在这里插入图片描述
我们会发现基类的析构函数被调用了两次,这时就会造成空间的二次释放。
需要注意的是:在一些场景中析构函数需要构成重写,重写的条件之一是函数名相同。那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。

四,继承与友元,静态成员

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

如下实例:

class student;
class person
{
public:
	friend void Print(const person& p , const student& s);

	person(string name="张三", const int age = 20)
		:_name(name)
		,_age(age)
	{
		cout << "person()" << endl;
	}

	person(const person& p)
	{
		_name = p._name;
		_age = p._age;
		cout << "person拷贝" << endl;
	}

	person& operator=(const person& p)
	{
		cout << "operator=" << endl;
		if (this != &p)
		{
			_name = p._name;
			_age = p._age;
		}
		return *this;
	}
		
	~person()
	{
		cout << "~person" << endl;
	}

	void Print()
	{
		cout << "person" << endl;
	}
protected:
	string _name;
	int _age;
};


void Print(const person& p, const student& s)
{
	cout << p._name	<< endl;
	cout << s._num << endl;

}

在这里插入图片描述
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例 。
如下实例:

class student;
class person
{
public:
	friend void Print(const person& p , const student& s);

	person(string name="张三", const int age = 20)
		:_name(name)
		,_age(age)
	{
		cout << "person()" << endl;
		_count++;
	}

	person(const person& p)
	{
		_name = p._name;
		_age = p._age;
		cout << "person拷贝" << endl;
	}

	person& operator=(const person& p)
	{
		cout << "operator=" << endl;
		if (this != &p)
		{
			_name = p._name;
			_age = p._age;
		}
		return *this;
	}
		
	~person()
	{
		cout << "~person" << endl;
	}

	void Print()
	{
		cout << "person" << endl;
	}

protected:
	string _name;
	int _age;

public: 
	static int _count;
};

int person::_count = 0;

void Print(const person& p, const student& s)
{
	cout << p._name	<< endl;
	//cout << s._num << endl;

}

//    派生类    继承方式  基类
class student : public person
{
public:

	student(int num,string name = "张三", const int age = 20)
		:person(name,age)
		,_num(num)
		
	{
		cout << "student()" << endl;
	}

	student(const student& s)
		:_num(s._num)
	{
		cout << "student拷贝" << endl;
	}

	student& operator=(const student& s)
	{
		cout << "operator=" << endl;
		if (this != &s)
		{
			person::operator=(s);
			_num = s._num;
		}

		return *this;
	}

	~student()
	{
		//person::~person();
		cout << "~student" << endl;
	}

	void Print()
	{
		cout << "test" << endl;
	}
private:
	int _num;//学号
};


void test4()
{
	student s1(1234);
	student s2(12435);
	student s3(6535);
	cout << person::_count << endl;


}

在这里插入图片描述

五,菱形继承及菱形虚拟继承

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

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

菱形继承:菱形继承是多继承的一种特殊情况。

下图为菱形继承
在这里插入图片描述
代码如下:

class person
{
public:
	string _name;
	int _age;

};

class student : public person
{
protected:
	int _num;//学号
};

class teacher:public person
{
protected:
	int id;//职工号码
};

class Assistant :public student, public teacher
{

protected:
	string major;//专业

};

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

二义性问题可以通过显式指定访问来解决,但数据冗余问题解决不了。

void test5()
{
	Assistant a1;
	a1.teacher::_name = "张三";
	a1.student::_name = "李四";
}

在这里插入图片描述
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。

在这里插入图片描述

在这里插入图片描述
通过上述窗口中的结果我们可以得出,其person似乎是仅有一份?这是怎么回事呢?
在这里插入图片描述

class person
{
public:
	
	int pp;

};

class student :virtual public person
{
public:
	int ss;
};

class teacher:virtual public person
{
public:
	int tt;
};

class Assistant :public student, public teacher
{

public:
	int aa;

};

void test6()
{
	Assistant a1;
	a1.aa = 1;
	a1.student::pp = 2;
	a1.teacher::pp = 3;
	a1.ss = 4;
	a1.tt = 5;
	

}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

六,总结

组合与继承
public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个A对象。

继承允许你根据基类的实现来定义派生类的实现。这种通过生成派生类的复用通常被称为白箱复用(white-box reuse)。术语“白箱”是相对可视性而言:在继承方式中,基类的内部细节对子类可见 。继承一定程度破坏了基类的封装,基类的改变,对派生类有很大的影响。派生类和基类间的依赖关系很强,耦合度高。

对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。对象只以“黑箱”的形式出现。

组合类之间没有很强的依赖关系,耦合度低。优先使用对象组合有助于你保持每个类被封装。实际尽量多去用组合。组合的耦合度低,代码维护性好。不过继承也有用武之地的,有些关系就适合继承那就用继承,另外要实现多态,也必须要继承。类之间的关系可以用继承,可以用组合,就用组合。

如何使类不被继承

  1. 父类的构造函数私有化,在子类不可见。子类对象实例化时,就无法调用父类的构造函数。
  2. final关键字

在这里插入图片描述
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Prometheus服务器、Prometheus被监控端、Grafana、Prometheus服务器、Prometheus被监控端、Grafana

day03 day03Prometheus概述部署Prometheus服务器环境说明&#xff1a;配置时间安装Prometheus服务器添加被监控端部署通用的监控exporterGrafana概述部署Grafana展示node1的监控信息监控MySQL数据库配置MySQL配置mysql exporter配置mysql exporter配置prometheus监控mysql自动…

uni-app引用外部图标库(阿里矢量图)

uni-app引用外部图标库&#xff08;阿里矢量图&#xff09; 作为前端程序员&#xff0c;nui-app是必备的&#xff0c;但是有时候内置的图标&#xff0c;组件又不完全满足&#xff0c;这里就可以引进外部图标&#xff0c;这里引用的是阿里矢量图标 第一步&#xff0c;在项目目…

(树) 剑指 Offer 32 - III. 从上到下打印二叉树 III ——【Leetcode每日一题】

❓剑指 Offer 32 - III. 从上到下打印二叉树 III 难度&#xff1a;中等 请实现一个函数按照之字形顺序打印二叉树&#xff0c;即第一行按照从左到右的顺序打印&#xff0c;第二层按照从右到左的顺序打印&#xff0c;第三行再按照从左到右的顺序打印&#xff0c;其他行以此类推…

JVM深入 —— JVM的体系架构

前言 能否真正理解JVM的底层实现原理是进阶Java技术的必由之路&#xff0c;Java通过JVM虚拟机的设计使得Java的延拓性更好&#xff0c;平台无关性是其同时兼顾移动端和服务器端开发的重要特性。在本篇文章中&#xff0c;荔枝将会仔细梳理JVM的体系架构和理论知识&#xff0c;希…

547. 省份数量

有 n 个城市&#xff0c;其中一些彼此相连&#xff0c;另一些没有相连。如果城市 a 与城市 b 直接相连&#xff0c;且城市 b 与城市 c 直接相连&#xff0c;那么城市 a 与城市 c 间接相连。 省份 是一组直接或间接相连的城市&#xff0c;组内不含其他没有相连的城市。 给你一…

026 - sum()函数

SUM() 函数&#xff1a; SUM 函数返回数值列的总数&#xff08;总额&#xff09;。 SQL SUM() 语法&#xff1a; SELECT SUM(column_name) FROM table_name -- 实际操作&#xff08;计算salary总额&#xff09; &#xff1a; SELECT SUM(salary) FROM employee; -- 查询ti…

代码随想录—力扣算法题:704二分查找.Java版(示例代码与导图详解)

版本说明 当前版本号[20230802]。 版本修改说明20230802初版 目录 文章目录 版本说明目录数组数组理论基础二分查找思路左闭右闭[left, right]左闭右开[left, right)两种方法的区别总结 数组 数组理论基础 数组是存放在连续内存空间上的相同类型数据的集合。 数组可以方便…

Python集成开发环境IDE:Spyder自动换行、函数列表outline、代码折叠

Spyder是一个用PythonQt编写的集成开发环境&#xff0c;包含许多有用的函数和工具。以下是一些常用功能&#xff1a; 变量浏览器&#xff1a;可以动态交互并修改变量&#xff0c;可以进行绘制直方图、时间序列&#xff0c;编辑日期框架或Numpy数组&#xff0c;对集合进行排序&…

100 个鲜为人知的 Python 高级技巧 0-20

100 鲜为人知的 Python 功能 这篇文章是为那些每天使用 Python&#xff0c;但从未真正坐下来通读所有文档的人准备的。 如果您已经使用 Python 多年&#xff0c;并且知道足够多的知识来完成工作&#xff0c;那么为了发现一些新技巧而通读几千页的文档可能不是明智之举。 因此&a…

IPC进程间通信探索——管道的原理与特点

&#x1f923; 爆笑教程 &#x1f449; 《看表情包学Linux》 &#x1f525; CSDN 累计订阅量破千的火爆 C/C 教程的 2023 重制版&#xff0c;C 语言入门到实践的精品级趣味教程。了解更多&#xff1a; &#x1f449; "不太正经" 的专栏介绍 ← 试读第一章订阅链接&am…

Babel编译与Webpack

目录 Babel初识BabelBabel 使用方式使用 Babel 前的准备工作 WebpackWebpack介绍Webpack初体验Webpack核心概念入口&#xff08;entry&#xff09;出口&#xff08;output&#xff09;加载 (loader)插件&#xff08;plugins&#xff09; Babel Babel官网: https://babeljs.io/…

贝锐蒲公英:没有公网IP,多分支企业如何高效远程访问OA系统?

贝锐蒲公英&#xff1a;没有公网IP&#xff0c;多分支企业、移动办公人员如何高效远程访问OA系统&#xff1f; 国内某大型美妆公司&#xff0c;旗下产品覆盖美容护肤品、彩妆、美容仪器、健康食品、SPA美容会所及等多类服务&#xff0c;致力于为客户提供高品质的产品和完善的服…

Centos7搭建Apache Storm 集群运行环境

文章目录 1. 安装 Java2. 下载并解压 Storm3. 配置环境变量4. 配置 ZooKeeper5. 配置 Stormstorm.yaml自定义 storm.yamlstorm-env.shlogback/cluster.xml 6. 启动 Storm 集群7. 验证 1. 安装 Java Storm 运行在 Java 平台上&#xff0c;因此需要先安装 Java。你可以使用以下命…

C++ 类的友元

【例1】 将数据与处理数据的函数封装在一起&#xff0c;构成类&#xff0c;既实现了数据的共享又实现了隐藏&#xff0c;无疑是面向对象程序设计的一大优点。但是封装并不总是绝对的。现在考虑一个简单的例子&#xff0c;就是Point类&#xff0c;每一个Point类的对象代表一个“…

《零基础入门学习Python》第075讲:GUI的终极选择:Tkinter12

Tkinter 的基本组件我们已经介绍得七七八八了&#xff0c;剩下的一些我们在这节课全部都会讲解完毕。 &#xff08;一&#xff09;Message组件 Message&#xff08;消息&#xff09;组件是 Label 组件的变体&#xff0c;用于显示多行文本消息。众所周知&#xff0c;我们的Lab…

简单的Kubernetes集群二进制方式部署

Kubernetes二进制方式部署 一&#xff1a;操作系统初始化配置&#xff08;所有机子&#xff09;关闭防火墙关闭selinux关闭swap根据规划设置主机名在master添加hosts调整内核参数时间同步 二&#xff1a;部署 etcd 集群1.准备签发证书环境#准备cfssl证书生成工具生成Etcd证书编…

在SIP 语音呼叫中出现单通时要怎么解决?

在VoIP的环境中&#xff0c;特别是基于SIP通信的环境中&#xff0c;我们经常会遇到一些非常常见的问题&#xff0c;例如&#xff0c;单通&#xff0c;注册问题&#xff0c;回声&#xff0c;单通等。这些问题事实上都有非常直接的排查方式和解决办法&#xff0c;用户可以按照一定…

Quartz中集群模式源码级解析

文章目录 案例搭建 案例搭建 创建一个JOB实现类 package org.quartz.examples.example13;import org.quartz.*;import java.util.Date;/*** This job has the same functionality of SimpleRecoveryJob except that this job implements is stateful, in that it* will have …

Spring框架——IOC配置文件方式

Spring框架的概述和入门 目录 Spring框架的概述和入门 什么是Spring框架 Spring框架的特点 Spring框架的IOC核心功能快速入门 Spring框架中的工厂&#xff08;了解&#xff09; Spring 创建Bean对象的三种方式 Spring框架的Bean管理的配置文件方式 Spring框架中标签的配…

Token与Cookie、Session登录机制

Cookie 背景 Web 的兴起&#xff08;所谓交互式就是你不光可以浏览&#xff0c;还可以登录&#xff0c;发评论&#xff0c;购物等用户操作的行为&#xff09;&#xff0c;单纯地浏览 web 已经无法满足人们的要求&#xff0c;比如随着网上购物的兴起&#xff0c;需要记录用户的…