首页 > 其他分享 >三. 继承和派生

三. 继承和派生

时间:2023-12-04 17:24:44浏览次数:27  
标签:初始化 派生 继承 成员 派生类 基类 public 构造函数

文章参考:

《C++面向对象程序设计》✍千处细节、万字总结(建议收藏)_白鳯的博客-CSDN博客

1. 继承案例

#include <iostream>
#include <string>
using namespace std;

class Person{
private:
	string name;
	string id_number;
	int age;
public:
	Person(string name1, string id_number1, int age1) {
		name = name1;
		id_number = id_number1;
		age = age1;
	}
	~Person() {

	}
	void show() {
		cout << "姓名: " << name << "  身份证号: " << id_number << " 年龄: " << age << endl;
	}
};

class Student:public Person{
private:
	int credit;
public:
	Student(string name1, string id_number1, int age1, int credit1):Person(name1, id_number1, credit1) {
		credit = credit1;
	}
	~Student() {

	}
	void show() {
		Person::show();
		cout << "学分: " << credit << endl;
	}
};

int main() {
	Student stu("白", "110103**********23", 12, 123);
	stu.show();

	return 0;
}

2. 基类成员访问规则

派生类可以访问基类成员中除了构造函数和析构函数以外的所有成员,并且可以在派生的过程中对这些成员的访问属性进行修改。从基类继承来成员在派生类中访问属性也不同。

基类中的访问属性 继承方式 在派生类中的访问属性
public public/protected/private public/protect/private(取决于继承方式)
protected public/protected/private protected/protected/private
private public/protected/private 不可直接访问

3. 派生类的构造函数和析构函数

由于派生类无法继承积累的构造函数和析构函数,而我们对派生类进行初始化时,又需要对基类进行初始化,因此必须在派生类的构造函数中完成对基类构造函数所需参数的设置。同理,也必须在派生类的析构函数中完成对基类的销毁。

3.1 构造和析构的顺序

一个案例:

  • 代码:

    #include <iostream>
    #include <string>
    using namespace std;
    
    class A{
    public:
    	A() {
    		cout << "A类对象构造中..." << endl;
    	}
    	~A() {
    		cout << "析构A类对象..." << endl;
    	}
    };
    
    class B : public A{
    public:
    	B() {
    		cout << "B类对象构造中..." << endl;
    	}
    	~B(){
    		cout << "析构B类对象..." << endl;
    	}
    };
    
    int main() {
    	B b;
    	return 0;
    }
    
  • 输出:

    A类对象构造中...
    B类对象构造中...
    析构B类对象...
    析构A类对象...
    
  • 分析:可以看出,创建对象时先初始化基类,再初始化派生类。销毁对象时先销毁派生类,再销毁基类。

3.2 构造和析构的规则

基本规则如下:

派生类构造函数的一般格式为:
派生类名(参数总表):基类名(参数表) {
    派生类新增数据成员的初始化语句
}
-----------------------------------------------------------------
含有子对象的派生类的构造函数:
派生类名(参数总表):基类名(参数表0),子对象名1(参数表1),...,子对象名n(参数表n)
{
    派生类新增成员的初始化语句
}

其顺序为:

  1. 初始化基类
  2. 初始化子对象
  3. 初始化派生类

注意:

  • 如果基类不带参数,则派生类不一定要定义构造函数。
  • 如果派生类的直接基类A也有一个基类,那么派生类的构造函数只需要对直接基类A进行负责,程序会依次向上追溯。

3.3 调整访问属性

所谓修改访问属性,就是基类的成员在派生类中的访问属性(在基类中的访问属性依旧不变)。

修改方式:

把基类的保护成员或共有成员直接写在私有派生类定义式中的同名段中,同时给成员名前冠以基类名和作用域标识符“::”。利用这种方法,该成员就成为派生类的保护成员或共有成员了。

EG:

class B:private A{
private:
    int y;
public:
    B(int x1, int y1) : A(x1) {
        y = y1;
    }
    A::show;               // 访问声明
};

注意:

  • 重写访问属性时,必须加上基类::来说明这时基类中的成员,否则就是在派生类中创建了一个和基类成员同名的成员,会覆盖基类的成员。
  • 访问属性只是在派生类中变了,在基类中依旧不变。
  • 修改访问属性时,无论是基类的函数成员还是数据成员,都去掉类型和参数,只要名字。
  • 对于基类的重载函数名,访问声明将对基类中所有同名函数其起作用。(因为只填了函数的名字)

3.4 多继承

形式:

class 派生类:继承方式1 基类1, 继承方式2 基类2, ... , 继承方式n 基类n{
    派生类新增的数据成员和成员函数
}

默认的继承方式是private

3.5 虚基类

3.5.1 引入

如果一个派生类有多个直接基类,而这些直接基类又有一个共同基类,这就会导致派生类中有多个同名的成员。为了避免产生二义性,派生类访问这些同同名的成员时,必须加上该成员所属的直接基类名,从而唯一的标识一个成员。

EG:

  • 代码:

    #include <iostream>
    #include <string>
    using namespace std;
    
    class Base{
    protected:
    	int a;
    public:
    	Base(){
    		a = 5;
    		cout << "Base a = " << a << endl;
    	}
    };
    
    class Base1: public Base{
    public:
    	Base1() {
    		a = a + 10;
    		cout << "Base1 a = " << a << endl;
    	}
    };
    
    class Base2: public Base{
    public:
    	Base2() {
    		a = a + 20;
    		cout << "Base2 a = " << a << endl;
    	}
    };
    
    class Derived: public Base1, public Base2{
    public:
    	Derived() {
    		cout << "Base1::a = " << Base1::a << endl;		// 必须要添加直接基类名进行限制,避免产生二义性。
    		cout << "Base2::a = " << Base2::a << endl;
    	}
    };
    
    int main() {
    	Derived obj;
    	return 0;
    }
    
  • 输出:

    Base a = 5
    Base1 a = 15
    Base a = 5
    Base2 a = 25
    Base1::a = 15
    Base2::a = 25
    

3.5.2 虚基类的初始化

3.5.1中,我们虽然通过用直接基类名来限定成员,避免了二义性的产生。但当继承关系较为复杂时,这显然会大大提高编程难度。那么,如果我们能让这些基类中多次继承的成员(也就是上例中的a)只存在一个拷贝(也就是对虚基类只进行一次初始化),那么对于该成员的访问就不会产生二义性了。为此,我们可以通过virtualbase基类声明为虚基类。

语法:

class 派生类:virtual 继承方式 积累名{
    ......
}

对上例进行修改:

#include <iostream>
#include <string>
using namespace std;

class Base{
protected:
	int a;
public:
	Base(){
		a = 5;
		cout << "Base a = " << a << endl;
	}
};

class Base1: virtual public Base{
public:
	Base1() {
		a = a + 10;
		cout << "Base1 a = " << a << endl;
	}
};

class Base2: virtual public Base{
public:
	Base2() {
		a = a + 20;
		cout << "Base2 a = " << a << endl;
	}
};

class Derived: public Base1, public Base2{
public:
	Derived() {
		cout << "Base1::a = " << Base1::a << endl;		
		cout << "Base2::a = " << Base2::a << endl;
	}
};

int main() {
	Derived obj;
	return 0;
}

得到运行结果:

Base a = 5
Base1 a = 15
Base2 a = 35
Base1::a = 35
Base2::a = 35

3.5.3 虚基类子类构造函数

如果在虚基类中定义有带形参的构造函数,并且没有定义默认形式的构造函数,则整个继承结构中,所有直接或间接的派生类都必须在构造函数的成员初始化表中列出对虚基类构造函数的调用,以初始化在虚基类中定义的数据成员。

EG:

  • 代码:

    #include <iostream>
    #include <string>
    using namespace std;
    
    class Base{
    protected:
    	int a;
    public:
    	Base(int a){
    		cout << "Base"<< endl;
    	}
    };
    
    class Base1: virtual public Base{
    private:
        int a_1;
    public:
    	Base1(int a, int a_1): Base(a) {
    		this.a_1 = a_1;
    		cout << "Base1" << endl;
    	}
    };
    
    class Base2: virtual public Base{
    private:
        int a_2;
    public:
    	Base2(int a, int a_2): Base(a) {
    		this.a_2 = a_2;
    		cout << "Base2"<< endl;
    	}
    };
    
    class Derived: public Base1, public Base2{
    private:
        int b;   
    public:
    	Derived(int a, int a_1, int a_2, int b): Base(a),Base1(a_1),Base2(a_2) {
            this.b = a;
    		cout << "Derived" << endl;		
    	}
    };
    
    int main() {
    	Derived obj;
    	return 0;
    }
    
  • 输出:

    Base
    Base1
    Base2
    Derived
    

注意:如果存在一个基类同时继承虚基类和普通基类,那么在调用构造函数时,先调用虚基类的构造函数,再调用普通基类的,最后调用派生类的。

3.6 赋值兼容规则

遵循里氏替换原则,即:所有使用子类的地方,都可以使用父类进行替换。

标签:初始化,派生,继承,成员,派生类,基类,public,构造函数
From: https://www.cnblogs.com/beasts777/p/17875446.html

相关文章

  • 实验5 继承和多态
    实验任务三pets.hpp#pragmaonce#include<iostream>#include<string>usingnamespacestd;classMachinePets{protected:stringnickname;public:MachinePets(conststrings):nickname(s){};public:virtualstringtalk()=0;s......
  • 实验五 继承和多态
    实验任务1publisher.hpp#pragmaonce#include<iostream>#include<string>usingstd::cout;usingstd::string;usingstd::endl;classpublisher{public:publisher(conststring&s="");public:virtualvoi......
  • 实验5 继承和多态
    实验任务31#include<iostream>2#include<string>3usingnamespacestd;4classMachinePets5{6private:7stringnickname;8public:9MachinePets(conststrings):nickname{s}{}10stringget_nickname()const{return......
  • 实验五 继承和多态
    task3machinepets.hpp#include<iostream>#include<string>usingnamespacestd;classMachinePets{public:MachinePets(conststrings);MachinePets();stringget_nickname()const;public:virtualstringtalk(......
  • 实验5 继承和多态
    实验任务1源代码:#pragmaonce#include<iostream>#include<string>usingstd::cout;usingstd::endl;usingstd::string;//发行/出版物类:Publisher(抽象类)classPublisher{public:Publisher(conststring&s="");//构造函数......
  • 实验五 继承和多态
    Task3:pets.hpp:#include<iostream>#include<string>usingnamespacestd;classMachinePets{public:MachinePets(conststrings):nickname(s){}conststringget_nickname(){returnnickname;}virtualstringta......
  • 深入理解C++继承(一)
    一、继承的概念及定义1.1继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复......
  • 实验5 继承和多态
    实验任务3pets.hpp #pragmaonce#include<iostream>#include<string>usingnamespacestd;classMachinePets{private:stringnickname;public:MachinePets(conststrings):nickname{s}{}stringget_nickname()const{returnnicknam......
  • 实验五 继承和多态
    任务三pets.hpp#pragmaonce#include<iostream>#include<string>usingnamespacestd;classMachinePets{public:MachinePets(conststring&nickname="");stringgetNickname()const;virtualstringtalk()=0;prot......
  • 实验5 继承和多态
    实验任务3#pragmaonce#include<iostream>#include<string>usingnamespacestd;classMachinePets{public:MachinePets(conststrings);stringget_nickname()const;virtualstringtalk()=0;private:stringnickname;};......