c++--继承与派生

面向对象强调软件的可重用性。
C++中可重用性是通过“继承”实现的。

继承和派生基本概念

  • 保持已有类的特性而构造新类的过程称为继承。
  • 在已有类的基础上新增自己的特性而产生新类的过程称为派生。
  • 被继承的已有类称为基类(或父类)。
  • 派生出的新类称为派生类。
  • 一个基类可以派生出多个派生类,每一个派生类又可以做为基类派生出新的派生类。

派生与继承举例
在这里插入图片描述
多继承举例
在这里插入图片描述

  • 继承与派生的目的
  1. 继承:实现代码的重用
  2. 派生:当新的问题出现,原有程序无法解决(或不能完全解决)时,需要对原有程序进行改造。


派生类的声明

class 派生类名 :继承方式 基类名{
      新增成员声明;
}
若不写继承方式,默认为private继承。



派生类的构成

  • 派生类的成员包含基类继承来的成员和自己新增的成员。
  • 构造一个派生类3部分工作:
    1. 从基类接收成员

      1. 派生类不加选择的把基类全部成员接收过来,不可以舍弃任何一部分。
      2. 缺点: 造成数据的冗余,浪费时间和空间,因为派生类用不到的数据也被继承过来。
    2. 调整从基类接收成员
      接收基类数据无法选择,但是程序员可以对某些成员做出调整。
      调整方式:

      1. 改变基类成员在派生类中的访问权限—通过继承方式实现。
      2. 在派生类声明一个与基类成员同名的成员,新成员会覆盖基类的同名成员。
    3. 在声明派生类时增加的成员
      体现派生类对基类功能的扩展。



继承的方式

  • 不同继承方式的影响主要体现在:
  1. 派生类成员对基类成员的访问权限
  2. 通过派生类对象对基类成员的访问权限
  • 三种继承方式
  1. 公有继承(public)

    1. 基类的public和protected成员的访问属性在派生类中保持不变,但基类的private成员不可直接访问。
    2. 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员。

    在这里插入图片描述

    1. 通过派生类的对象只能访问基类的public成员。

    在这里插入图片描述

    扫描二维码关注公众号,回复: 3502974 查看本文章
  2. 私有继承(private)

    1. 基类的public和protected成员都以private身份出现在派生类中。
    2. 派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类private成员。
    3. 通过派生类的对象不能直接访问基类中的任何成员。

    在这里插入图片描述

  3. 保护继承(protected)

    1. 基类的public和protected成员都以protected身份出现在派生类中
    2. 派生类中的成员函数可以直接访问基类中public和protected成员,但不能直接访问基类的private成员。
    3. 通过派生类的对象不能直接访问基类中的任何成员
    4. 保护类成员可以被派生类的成员函数引用。
    5. protected成员的特点与作用
      • 对建立其所在类对象的模块来说,它与 private 成员的性质相同。
      • 对于其派生类来说,它与 public成员的性质相同。
      • 既实现了数据隐藏,又方便继承,实现代码重用

    在这里插入图片描述

  • 多级派生时的访问权限
    与单派生相同原则。



继承时的构造函数

  • 基类的构造函数不被继承,派生类中需要声明自己的构造函数。
  • 定义构造函数时,只需要对本类中新增成员进行初始化,对继承来的基类成员的初始化,自动调用基类构造函数完成。
  • 派生类的构造函数需要给基类的构造函数传递参数

单一继承时的构造函数

  • 一般形式:
 派生类名::派生类名(基类所需的形参,本类成员所需的形参):基类名(参数表){
        本类成员初始化赋值语句;
 }
  • “:” 后面是调用基类构造函数和它的参数。

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

有子对象的派生类构造函数

派生类名::派生类名(形参表):基类名1(参数), 基类名2(参数), ...基类名n(参数),新增成员对象的初始化{
       本类成员初始化赋值语句;
};
#include <iostream>
#include <string>
using namespace std;
class Student {
public:
	Student(int s, string na) { sno = s; name = na; }
	void display() {
		cout << "this student message:" << endl;
		cout << "  sno: " << sno << endl;
		cout<< "sname: "  << name << endl;
	}
protected:
	int sno;
	string name;
};
class Student1 :public Student {
public:
	Student1(int s, string na, int s1, string na1, char se) :Student(s, na), yj(s1, na1) {
		sex = se;
	}
	void show(){
		display();
		cout << "  sex: " << sex << endl;
	}
	void show_yj() {
	    yj.display();
	}
private:
	Student yj;
	char sex;
};

void main() {
	Student1 s(1, "Tom", 2, "YJ", 'F');
	s.display();
	s.show();
	s.show_yj();
	/*
	    this student message:
		  sno: 1
		sname: Tom
		this student message:
		  sno: 1
		sname: Tom
		  sex: F
		this student message:
		  sno: 2
		sname: YJ
	*/
}

多层派生时的构造函数

一个类可以进行派生,派生类还可以继续派生,形成派生层次关系。

派生类名::派生类名(参数表):基类名1(基类1 初始化参数表), 基类名2(基类2初始化参数表), ...基类名n(基类n初始化参数表){
             本类成员初始化赋值语句;
};
#include <iostream>
#include <string>
using namespace std;
class Student {
public:
	Student(int s, string na) { 
		sno = s;
		name = na;
	}
	void display() {
		cout << "this student message:" << endl;
		cout << "  sno: " << sno << endl;
		cout<< "sname: "  << name << endl;
	}
protected:
	int sno;
	string name;
};
class Student1 :public Student {
public:
	Student1(int s, string na, char se) :Student(s, na){
		sex = se;
	}
	void show(){
		display();
		cout << "  sex: " << sex << endl;
	}	
private:
	char sex;
};
class Student2 :public Student1 {
public:
	Student2(int s, string na, char se,int a) :Student1(s, na, se) { //派生类student2的构造函数首体不能写成:Student2(int s, string na, char se,int a) :Student1(s, na, se) ,Student(s,na)或者Student2(int s, string na, char se,int a) :Student(s,na),Student1(s, na, se) 
		age = a;
	}
	void show_all() {
		show();
		cout << "  age: " << age << endl;
	}
private:
	int age;
};
void main() {
	Student2 s( 1 , "Tom", 'F',18);
	s.display();
	s.show();
	s.show_all();
	/*
	  输出结果:
	    this student message:
		  sno: 1
		sname: Tom
		this student message:
		  sno: 1
		sname: Tom
		  sex: F
		this student message:
		  sno: 1
		sname: Tom
		  sex: F
		  age: 18

	*/
}

派生类与基类的构造函数

  • 当基类中声明有缺省构造函数或未声明构造函数时,派生类构造函数可以不向基类构造函数传递参数,也可以不声明,构造派生类的对象时,基类的缺省构造函数将被调用。
  • 当需要执行基类中带形参的构造函数来初始化基类数据时,派生类构造函数应在初始化列表中为基类构造函数提供参数。

构造函数的执行顺序

  1. 调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)。
  2. 对成员对象进行初始化,初始化顺序按照它们在类中声明的顺序。
  3. 执行派生类的构造函数体中的内容。

先调用基类构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)并接受派生类中的参数值传递的值
在这里插入图片描述在这里插入图片描述

拷贝构造函数

  1. 若建立派生类对象时没有编写拷贝构造函数,编译器会生成一个隐含的拷贝构造函数,该函数先调用基类的拷贝构造函数,再为派生类新增的成员对象执行拷贝。
  2. 若编写派生类的拷贝构造函数,则需要为基类相应的拷贝构造函数传递参数。

例如:
C::C(C &c1): B(c1) {…}



继承时的析构函数

• 析构函数也不被继承,派生类自行声明
• 声明方法与一般(无继承关系时)类的析构函数相同。
• 不需要显式地调用基类的析构函数,系统会自动隐式调用。
• 析构函数的调用次序与构造函数相反。

在这里插入图片描述
在主函数中构建派生类对象:Derived obj(1, 2, 3, 4);
在这里插入图片描述



基类与派生类的对应关系

  • 单继承
    派生类只从一个基类派生。
  • 多继承
    派生类从多个基类派生。

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

  • 多重派生
    由一个基类派生出多个不同的派生类。
  • 多层派生
    派生类又作为基类,继续派生新的类。



多重继承

一个派生类有一个以上的基类,称为多重继承

声明

class 派生类名 :继承方式1 基类名1,继承方式2 基类名2{
      新增成员声明;
}

多重继承时的构造函数

派生类名::派生类名(参数表):基类名1(基类1 初始化参数表), 基类名2(基类2初始化参数表), ...基类名n(基类n初始化参数表){
       本类成员初始化赋值语句;
};
#include <iostream>
#include <string>
using namespace std;
class Teacher {
public:
	Teacher(string na,int a,string t) {
		name = na;
		age = a;
		title = t;
	}
	void display() {
		cout << "name: " << name<<endl;
		cout << "age: " << age << endl;
		cout << "title: " << title << endl;
	}
protected:
	string name;
	int age;
	string title;
};
class Student {
public:
	Student(string na,char s, float sco) { 
		name1=na;
		sex = s;
		score = sco;
   	}
	void display1() {
		cout << "name: " <<name1<< endl;
		cout << "sex: " << sex<< endl;
		cout<< "score: "  << score<< endl;
	}
protected:
	string name1;
	char sex;
	float score;
};
class Graduate :public Teacher,public Student {
public:
	Graduate(string na, int a, char s, string t, float sco, float w) :Teacher(na, a, t), Student(na, s, sco), wage(w) {}
	void show(){
		cout << "name: " << name << endl;
		cout << "age: " << age << endl;
		cout << "title: " << title << endl;
		cout << "sex: " << sex << endl;
		cout << "score: " << score << endl;
		cout << "wage: " << wage << endl;
	}	
private:
	float wage;
};

void main() {
	Graduate g("yj",18,'F',"Boss",100,200000);
	g.show();
	/*
	    name: yj
		age: 18
		title: Boss
		sex: F
		score: 100
		wage: 200000

	*/
}

多继承引起的二义性

  • 同名隐藏规则
    当派生类与基类中有相同成员时:

    1. 若未强行指名,则通过派生类对象使用的是派生类中的同名成员。
    2. 如要通过派生类对象访问基类中被覆盖的同名成员,应使用基类名限定。
  • 二义性问题

    1. 在多继承时,基类与派生类之间,或基类之间出现同名成员时,将出现访问时的二义性(不确定性)——采用虚函数或同名隐藏规则来解决。
    2. 当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性——采用虚基类来解决。

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

  • 二义性的解决方法

  1. 解决方法一:用类名来限定

c1.A::f() 或 c1.B::f()

  1. 解决方法二:同名覆盖

在C 中声明一个同名成员函数f(),f()再根据需要调用 A::f() 或 B::f()

在这里插入图片描述



虚基类

若一个派生类有多个直接基类,而这些直接基类又有一个共同的基类,则最终派生类中会保留该间接共同基类数据成员的多份同名成员。

虚基类的引入

  • 用于有共同基类的场合。
  • c++提供虚基类的方法,使得在继承间接共同基类时只保留一份成员。

声明

  • 一般形式:
    class 派生类名 :virtual 继承方式 基类名
  • 以virtual修饰说明基类
  • 虚基类并不是在声明基类时声明的,而是在声明派生类时,指定继承方式时声明的。
  • 为了保证虚基类在派生类中只继承一次,应当在该基类的所有直接派生类中声明为虚基类。

    例:class B1:virtual public B
    在这里插入图片描述

作用

  1. 主要用来解决多继承时可能发生的对同一基类继承多次而产生的二义性问题.
  2. 为最远的派生类提供唯一的基类成员,而不重复产生多次拷贝
  3. 注意:在第一级继承时就要将共同基类设计为虚基类.

虚基类及其派生类构造函数

  • 建立对象时所指定的类称为最(远)派生类。虚基类的成员是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的。
  • 在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的成员初始化表中给出对虚基类的构造函数的调用。如果未列出,则表示调用该虚基类的默认构造函数。
  • 在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数的调用被忽略。

在这里插入图片描述



类型兼容规则

  • 只有公有派生类才是基类真正的子类型。

  • 只能用子类对象对基类对象赋值,而不可以使用基类对象给子类对象赋值。因为基类不包括派生类新增的成员。

  • 一个公有派生类的对象在使用上可以被作为基类的对象,反之则禁止。具体体现在:

    1. 派生类的对象可以隐含转换为基类对象。
    2. 派生类的对象可以初始化基类的引用。
    3. 派生类的指针可以隐含转换为基类的指针。

    在这里插入图片描述

  • 通过基类对象名、指针只能使用从基类继承的成员

    #include <iostream>
    #include <string>
    using namespace std;
    class Teacher {
    public:
    	Teacher(string na,int a,string t) {
    		name = na;
    		age = a;
    		title = t;
    	}
    	void show() {
    		cout << "name: " << name<<endl;
    		cout << "age: " << age << endl;
    		cout << "title: " << title << endl;
    	}
    protected:
    	string name;
    	int age;
    	string title;
    };
    class Student {
    public:
    	Student(string na,char s, float sco) { 
    		name1=na;
    		sex = s;
    		score = sco;
       	}
    	void show() {
    		cout << "name: " <<name1<< endl;
    		cout << "sex: " << sex<< endl;
    		cout<< "score: "  << score<< endl;
    	}
    protected:
    	string name1;
    	char sex;
    	float score;
    };
    class Graduate :public Teacher,public Student {
    public:
    	Graduate(string na, int a, char s, string t, float sco, float w) :Teacher(na, a, t), Student(na, s, sco), wage(w) {}
    	void show(){
    		Teacher::show();
    		Student::show();
    		cout << "wage: " << wage << endl;
    		cout << endl;
    	}	
    private:
    	float wage;
    };
    
    void main() {
    	Student s("Tom", 'F', 100);
    	Graduate g("yj",18,'F',"Boss",100,200000);
    	Student *p = &s;
    	p->show();
    	cout << endl;
    	s.show();
    	cout << endl;
    
    	p = &g;
    	p->show();//通过基类指针,只能使用从基类继承的成员
    	cout << endl;
    	g.show();
    

    结果:
    在这里插入图片描述



组合和继承

  • 组合与继承:通过已有类来构造新类的两种基本方式
  • 组合:B类中存在一个A类型的内嵌对象
    1. 有一个(has-a)关系:表明每个B类型对象“有一个” A类型对象
    2. A类型对象与B类型对象是部分与整体关系
    3. B类型的接口不会直接作为A类型的接口

在这里插入图片描述



派生类对象的内存布局

  • 派生类对象的内存布局因编译器而异。
    内存布局应使类型兼容规则便于实现。
  • 一个基类指针,无论其指向基类对象,还是派生类对象,通过它来访问一个基类中定义的数据成员,都可以用相同的步骤
  • 不同情况下的内存布局
    1. 单继承:基类数据在前,派生类新增数据在后
    2. 多继承:各基类数据按顺序在前,派生类新增数据在后
    3. 虚继承:需要增加指针,间接访虚基类数据


派生类指针转化为基类指针的情况

单继承情形

在这里插入图片描述

多继承情形

在这里插入图片描述

虚拟继承情形

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_41498261/article/details/82946007