【C++总复习】 第5章----继承与派生

1.继承

  • 继承:在一个已经存在的类的基础上建立一个新的类。已存在的类称为父类、基类;新建立的类称为子类、派生类
  • 一个派生类只从一个基类派生称为单继承
  • 一个派生类有两个或多个基类称为多重继承
  • 图解单继承在这里插入图片描述
  • 图解多继承在这里插入图片描述- 派生类是基类的具体化,而基类则是派生类的抽象
    在这里插入图片描述

从上图中可以看出:
1.小学生、中学生、大学生、研究生、留学生是学生的具体化,都是从学生的共性基础上加上某些特点形成的子类
2.学生则是各类学生共性的提取形成的一个抽象的类
3.基类综合了派生类的公共特征,派生类则在基类的基础上增加某些特性,把抽象变的具体

  • 派生类的声明形式
  • class 派生类名 : [继承方式] 基类名{ 子类新增成员 };
  • 继承方式:public(公用的) private(私有的) protected(受保护的)
  • 如果不写继承方式则默认为private
  • 派生类的成员有:(1)从父类继承来的成员 (2)修改从父类继承来的成员 (3)在派生类中增加的新成员
  • 继承优点:继承性提供了重复利用程序资源的一种途径。通过继承机制,可以扩充和完善旧的程序设计以适应新的需求。这样不仅可以节省程序开发的时间和资源,并且为未来程序增添了新的资源。

2.子类成员的访问属性

  • 无论是单继承还是多继承,都符合以下访问属性的限制规律

2.1公用继承

  • 子类从父类的继承方式为public,称为公用继承
  • 公用继承后父类成员访问属性表格
    在这里插入图片描述
class A{
	private:
		int y;	//private访问权限 
	protected:
		int z;	//protected访问权限 
	public:
		int x;	//public访问权限 
	
};
class B:public A{
	public:
		void Change(){
			x = 1;	
//合法,基类中public成员在public继承之后依然是public访问权限 
			y = 1;	
//不合法,基类中private成员在public继承之后无法访问 
			z = 1;	
//合法,基类中protected成员在public继承之后依然是protected访问权限 
		} 
};

2.2私有继承

  • 子类从父类的继承方式为private,称为私有继承
  • 私有继承后父类成员访问属性表格
    在这里插入图片描述
class A{
	private:
		int y;	//private访问权限 
	protected:
		int z;	//protected访问权限 
	public:
		int x;	//public访问权限 
	
};
class B:private A{
	public:
		void Change(){
			x = 1;	
//合法,基类中public成员在private继承之后是private访问权限 
			y = 1;
//不合法,基类中private成员在private继承之后无法访问 
			z = 1;	
//合法,基类中protected成员在private继承之后是private访问权限 
		} 
};

2.3保护继承

  • 子类从父类的继承方式为protected,称为保护继承
  • 保护成员对于类的用户角度看,等价于私有成员,但是保护成员可以在子类的成员函数中使用
  • 保护继承后父类成员访问属性表格
    在这里插入图片描述
class A{
	private:
		int y;	//private访问权限 
	protected:
		int z;	//protected访问权限 
	public:
		int x;	//public访问权限 
	
};
class B:protected A{
	public:
		void Change(){
			x = 1;	
//合法,基类中public成员在protected继承之后是protected访问权限 
			y = 1;	
//不合法,基类中private成员在protected继承之后无法访问 
			z = 1;	
//合法,基类中protected成员在protected继承之后是protected访问权限 
		} 
};

2.4类中成员访问属性

在这里插入图片描述

3.子类构造函数和析构函数

3.1构造函数

  • 子类构造函数不仅仅需要为子类中特有的属性传值,还要为父类中继承过来的属性传值(即调用父类构造函数)

子类构造函数格式:
子类名(总参数列表) : 父类名(父类参数表){ 新增数据成员初始化 }

例:B(int x,int y,int z) : A(x,y){ this->z = z;}
B继承A类,A中有x,y两个参数,B中新增一个z参数

class A{
	private:
		int x;
		int y;	
		int z;	
	public:
		A(){
			x = 0;
			y = 0;
			z = 0;
		}
		A(int x,int y,int z){
			this->x = x;
			this->y = y; 
			this->z = z;
		}
};
class B:protected A{
	private:
		int m;
		int n;
	public:
		//子类无参构造函数 
		B():A(){
			m = 0;
			n = 0;
		}
		//子类有参构造函数 
		B(int x,int y,int z,int m,int n):A(x,y,z){
			this->m = m;
			this->n = n;
		}	
};
  • 无论继承结构多么复杂,无论继承了多少层,子类一定要负责对直接父类进行初始化

构造函数执行顺序:先基类后子类

多层继承:A派生出B,B派生出A
A: int x ; A构造函数:A(int x){ this-> x = x;}
B: int y ; B构造函数:B(int x,int y) : A(x){ this->y = y; }
C: int z ; C构造函数:C(int x,int y,int z):B(x,y){ this->z = z; }
调用C构造函数初始化对象时成员变量初始化顺序:
①初始化A的x
②初始化B的y
③初始化C的z

多重继承:A、B共同派生出C
A:int x; A构造函数:A(int x){ this-> x = x; }
B:int y ;B构造函数:B(int y) { this->y = y; }
C:int z;C构造函数:C(int x,int y,int z) : A(x),B(y){ this->z = z; }
调用C构造函数初始化对象时成员变量初始化顺序:
①初始化A的x
②初始化B的y
③初始化C的z
提醒:虽然A和B之间并无继承关系,本应初始化顺序并无先后,但是C中的构造函数是先初始化的A中的x,所以按照代码顺序执行初始化

3.2析构函数

  • 子类是无法继承父类的析构函数的
  • 子类必须通过自己的析构函数来调用父类的析构函数,来处理从父类继承来的成员,同时在自己的析构函数中处理自己新增的成员
class A{
	private:
		int x;
	public:
		A(){x = 0;}
		A(int x){this->x = x;}
		~A(){}		//父类析构函数
};
class B:public A{
	private:
		int m;
	public:
		B():A(){m = 0;}
		B(int x,int m):A(x){this->m = m;}
		~B(){}		//子类析构函数
};

5.虚基类

Q:看下面这段代码,你发现了什么问题?

class A{
	public:
		m(){}
};
class B : public A{
};
class C : public A{
};
class D : public B,public C{
	public:
		n(){
			m();
		}
}

在这里插入图片描述

Q:很显然:在多层继承加多重继承中,一个子类同时继承了两个父类,这两个父类又同时继承了同一个类,此时,中间层的两个类存在相同的方法或者属性,这时,如何在最底层的子类中区分相同的方法或属性呢?
A:由于同名方法或属性造成了二义性,这时解决方法有两个:第一个方法是使用作用域运算符进行指明,例如:将上述程序中的n方法修改成:n(){ B::m();},就明确指出了调用的是父类B中的m方法,这样便消除了二义性。第二个方法是使用虚基类

  • 虚基类的作用:让继承间接基类时只保留一份成员
  • 声明虚基类的形式:class 子类名 : virtual 继承方式 父类名
class B : virtual public A{
};
class C : virtual  public A{
};

此时A被声明为虚基类,D类同时继承了B和C类,D中只会出现一个A中的属性与方法,并不会出现二义性

  • 为了保证虚基类中的成员在子类中只被继承一次,需要将该基类的所有直接子类中声明为虚基类,否则,仍然会出现对基类的多次继承,如图:
    在这里插入图片描述

6.父类对象与子类对象之间的转换

class A {
	public:
		int x;
};
class B : public A{
	public:
		int y;
};

(1)用子类对象给父类对象赋值

A a;
B b;
a = b;

在这里插入图片描述
(2)父类对象引用可以引用子类对象

B b;
A &a = b
  • 引用对象a只是b对象中x部分的名字,只可以访问b对象中的x,无法访问y.

在这里插入图片描述
(3)父类类型指针变量指向子类对象

A *p;
B b;
p = &b;

p->x = 10;	//合法
p->y = 10;	//不合法
  • 通过指向父类对象的指针来指向子类对象,此指针只能访问子类从父类继承来的属性和方法,无法通过此指针间接访问子类特有的属性和方法
    在这里插入图片描述

7.类的组合

  • 简单来说,类的组合就是类中属性包含其他类的对象
  • 读懂以下程序就可理解类的组合、属性为对象时如何初始化
#include <iostream>
using namespace std;

class A{
	private:
		int x;
	public:
		A(){
			x = 0;
		}
		A(int x){
			this->x = x;
		}
		int getX(){
			return x;
		}
};
class B{
	private:
		int y;
		A a;
	public:
		B(){	//对象a默认调用无参构造函数 
			y = 0;
		}
		B(int y,A &ra){	
			this->y = y;
			a = ra;		//为对象a初始化 
		}
		void display(){
			cout<<this->a.getX()<<" "<<this->y<<endl;
		}
};

int main(){
	A a(1);
	B b1;
	B b2(2,a);
	
	b1.display();// 0 0	
	b2.display();// 1 2
	
	return 0;
}

8.类的继承和组合

  • 继承关系可以理解为 is a 关系:斗牛犬是一只狗
  • 组合关系可以理解为 has a 关系:张三有一只斗牛犬
  • 继承是纵向的,组合是横向的

9.继承的意义

  • 面向对象的四大特征:抽象 继承 封装 多态
  • 封装->继承->多态
  • 继承是多态的基础

猜你喜欢

转载自blog.csdn.net/weixin_45437022/article/details/106540746