继承

Posted 小羊教你来编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了继承相关的知识,希望对你有一定的参考价值。

在这里插入图片描述

目录:

一.继承概念

1.继承机制 是面向对象程序设计让代码可以复用的一个重要的手段,允许你程序员在原有类的特性的基础上进行扩展,来增加新的功能.,
2.通过这样方式产生的新的类叫做派生类
3.继承呈现了面向对象程序设计的层次结构,是类设计层次的复用.

二.继承定义

1. 定义

在这里插入图片描述

2.基类成员可访问方式

在派生类(也就是继承了其他功能的类)中可见的部分:

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

1.基类的private成员无论如何进行继承,在派生类中都无法看见
2.如果基类成员不想在类外直接被访问,但是要在派生类中被访问,就定义为 protected
3.使用class关键字默认为private继承,struct关键字默认public继承

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

!!!其实就是理解怎样利用基类给派生类进行赋值.怎样利用派生类给基类进行赋值!!!

class Person//基类
{
protected://派生类可以访问,类外不能访问
	string _name; // 姓名
	string _sex; // 性别
	int _age; // 年龄
};
class Student : public Person//派生类
{
public:
	int _No; // 学号
};

1.子类可以给父类直接赋值

Student stu;			//创建子类的对象

Person  p1 = stu;		//通过普通方式将子类的值赋予给对应的基类

Person* p2 = &stu;		//通过指针的方式将值赋予

Person& p3 = stu;		//通过引用的方式来赋值

2.基类对象不能赋值给派生类

stu = p1;		//这样赋值会报错,无法通过编译

3.基类的指针可以通过强制类型转换给派生类的指针

Student* ps=(Student*) p2;	//将上面的基类的指针p2强制转换成子类的指针,变成ps

ps->_NO=10;		//就可以直接通过ps指向对应的值来进行改变数值

四.继承的作用域

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

两个作用域有一定的重叠部分,不能单纯的觉得两个函数是完全不会发生函数重载的

2.隐藏(重定义)

子类和父类中有同名函数,子类成员会屏蔽掉父类成员的访问

class Person
{
protected:
	string _name = "小羊"; 
	int _num = 111; 		//这里是第一个_num
};
class Student : public Person
{
public:
	void Print()
	{
		cout << " 姓名:" << _name << endl;	//打印对应的姓名
		cout << " 身份证号:" << Person::_num << endl;	//这里打印的时候_num加了作用的类,所以会打印对应的111
		cout << " 学号:" << _num << endl;		//如果没有声明的话只会打印子类中的_num
	}
protected:
	int _num = 999; 		//第二个_num,两个变量构成了隐藏关系!!!!
};
void Test()
{
	Student s1;		//创建
	s1.Print();		//打印对应的值
};

在这里插入图片描述

3.如果是函数成员的隐藏,只需要函数名相同就可以构成隐藏

class A {
public:
	void fun()		//成员函数发生隐藏
	{
		cout << "func()" << endl;
	}
};
class B : public A {
public:
	void fun(int i)	//成员函数发生隐藏
		
	{
		  A::fun();	//如果这里不会声明A类中的fun()函数的时候,则不会调用,被直接隐藏
		  cout << "func(int i)->" << i << endl;
}
};
void Test()
{
	B b;
	b.fun(10);
};

在这里插入图片描述

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

1.构造函数

派生类的默认构造必须先调用基类的默认构造,如果没有必须在派生类的构造函数的初始化列表中显示的调用

在这里插入图片描述

2.拷贝构造函数

必须先调用基类的拷贝来完成拷贝初始化.(拷贝完成后也需要调用析构来进行销毁)

在这里插入图片描述

3.operator=

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

在这里插入图片描述

4.析构函数

派生类的析构函数会被在调用完之后自动的调用基类的析构函数来完成对于基类的清理
(必须保持先清理派生类的成员,然后再调用基类的析构函数对于基类的成员进行清理.)

5.派生类对象的初始化先调用基类的构造,再调用派生类构造

6.派生类对象的析构清理先调用派生类的析构清理,在调用基类的析构清理

六.继承和有元

有元关系不能被继承 ,基类如果有有元的话,不能访问子类私有和保护成员

七.继承和静态成员

基类中如果定义了static静态成员,则整个继承体系中只有一个这样的成员

原理:

在基类中如果声明了一个静态成员,系统就会自动将其放在静态数据段中,它的基类和对应的子类可以访问到这一个数据,故只有一个这样的成员.

八.菱形继承/菱形虚拟继承

1.概念

在这里插入图片描述
像这种继承的方式叫做菱形继承,也是一种特殊的继承方式

2.数据冗余/二义性

//菱形继承方式
class Person
{
public:
	string _name; // 姓名
};
class Student : public Person
{
protected:
	int _num; //学号
};
class Teacher : public Person
{
protected:
	int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse; // 主修课程
};
int main(){
	
	Assistant a;
	a._name = "peter";	//在这里访问的时候,因为有两个对应的父类,故会产生二义性,导致无法区分,会报错

	a.Student::_name = "xxx";	//像这里加上具体的类的声明后就可以正常操作了
	a.Teacher::_name = "yyy";	//但是所存在的数据冗余的问题还是没有办法进行解决的.
}

在这里我们可以通过虚拟继承来实现防止二义性的操作,就是在继承的前面加上virtual,可以解决二义性的问题,但是还是无法解决数据冗余的问题.

在继承的时候有virtual才会存在虚基表的出现
在这里插入图片描述

虚基表中存的偏移量。通过偏移量可以找到下面的A。

对于继承的知识的总结,后期如果有问题继续查缺补漏.

以上是关于继承的主要内容,如果未能解决你的问题,请参考以下文章

26.Qt Quick QML-RotationAnimationPathAnimationSmoothedAnimationBehaviorPauseAnimationSequential(代码片段

Flask之模板之宏继承包含

java中封装,继承,多态,接口学习总结

php如何实现多继承?

如何覆盖继承的嵌套类中存在的虚拟方法

多线程 Thread 线程同步 synchronized