类 —— 继承、多重继承

继承

一个类,继承另一个已有的类。(在一个已存在的类的基础上建立一个新的类,并拥有其特性)

是一个父类(基类)派生出子类(派生类)的过程。
派生类往往是基类的具象化,基类则是派生类的抽象。

基类与派生类是相对的,一个类可能存在又是基类又是派生类的情况。
继承提高了代码的复用性

在实际的使用中,派生类会做出一些与基类的差异化:

● 修改继承来自的基类内容

属性:公有属性直接修改,私有属性通过接口修改

函数:函数隐藏,通过派生类实现一个同名同参数的函数,来隐藏基类的函数。

● 新增派生类的内容

cpp 复制代码
#include <iostream>
using namespace std;

// 基类
class Father
{
private:
    string name = "张";

public:
    void set_name(const string &name)
    {
        this->name = name;
    }

    string get_name()
    {
        return name;
    }

    void work()
    {
        cout << "我的工作是厨师,我负责炒菜。" << endl;
    }
};

// 派生类继承基类
class Son:public Father
{
public:
    void init()
    {
        set_name("王");				// 私有属性通过接口修改
    }

    void work()
    {
        cout << "我是飞行员,我要上天。" << endl;
    }

    void game()
    {
        cout << "三硝基甲苯。" << endl;
    }
};

int main()
{
    Son s;
    cout << s.get_name() << endl; 			// 张
    s.init();
    cout << s.get_name() << endl; 			// 王

    s.work();				// 
    s.game();

    // 调用基类被隐藏的成员函数
    s.Father::work();

    return 0;
}

继承的格式、权限

class 类名:父类名 {};					// 默认权限是 private

class 类名:继承的权限 父类名 {};


继承时类中的特殊成员函数不会被继承

构造函数

派生类中的任意一个构造函数,都必须直接或者间接调用基类的一个构造函数。

problem:
cpp 复制代码
#include <iostream>
using namespace std;

class Father
{
    string secret;
public:
    string firstname;
    float asset;
    Father(string secret, string fname, float asset):secret(secret), firstname(fname), asset(asset)
    {
        cout << "A constructor of father class with parameters. " << endl;
    }
};

class Son:public Father
{
    string secret;
public:
    void show()
    {
        cout << "First name: " << firstname << endl;
    }

};

int main()
{
    // Son s1;  				// 找不到基类的无参构造函数    
    // error,因为父类只有有参构造,而子类中没有提供 能够调用父类有参构造的构造函数,不能成功创建父类的空间

	// Son s2("~", "0", 1);		// 找不到匹配的构造函数
    
    cout << "Size of father: " << sizeof(Father) << endl;
    cout << "Size of son: " << sizeof(Son) << endl;

    return 0;
}


solution1:补充基类的无参构造函数
cpp 复制代码
#include <iostream>
using namespace std;

class Father
{
    string secret;
public:
    string firstname;
    float asset;
	Father():secret("~"), firstname("w"), asset(1000000) { }
    Father(string secret, string fname, float asset):secret(secret), firstname(fname), asset(asset)
    {
        cout << "A constructor of father class with parameters. " << endl;
    }
};

class Son:public Father
{
    string secret;
public:
    void show()
    {
        cout << "First name: " << firstname << endl;
    }

};

int main()
{
    Son s1;
    s1.show();

	// Son s2("~", "0", 1);		// 找不到匹配的构造函数
    
    cout << "Size of father: " << sizeof(Father) << endl;
    cout << "Size of son: " << sizeof(Son) << endl;

    return 0;
}
solution2:透传构造

需要 在子类构造函数的初始化列表中 显性调用父类的构造函数

当父类中只有有参构造,子类在创建类对象时,必须手动调用父类的构造函数。

cpp 复制代码
#include <iostream>
using namespace std;

class Father
{
    string secret;
public:
    string firstname;
    float asset;
    Father(string fname, float asset):firstname(fname), asset(asset)
    {
        cout << "A constructor of father class with parameters. " << endl;
    }
};

class Son:public Father
{
    string secret;
public:						
	Son():Father("default string", 120000)//, secret("0")		// 这里使用具体的值
	{ 
    }
							// Father(fname, asset) 和 secret(secret) 可以交换,但有警告
    Son(string fname, float asset, string secret):Father(fname, asset), secret(secret)
    {
    }
    void show()
    {
        cout << "First name: " << firstname << endl;
        cout << "Asset: " << asset << endl;
        cout << "Secret of the son: " << secret << endl;
    }
};

int main()
{
    Son s1("Xing", 120000, "secret1");
    s1.show();

    Son s2;
    s2.show();

    return 0;
}
solution3:继承构造(C++ 11 支持)

C++支持 不用在子类中再写一遍父类的构造函数 ------

cpp 复制代码
using Father::Father; 		// 在子类中使用父类的构造函数

直接使用继承构造的方式,不能对子类成员初始化

继承构造本质上并不是把父类中的构造函数继承给子类,编译器自动根据父类中构造函数的格式,提供出派生的构造函数(个数、参数都和父类中的构造函数一致),主要还是通过透传构造创建父类的空间。

cpp 复制代码
#include <iostream>
using namespace std;

class Father
{
    string secret;
public:
    string firstname;
    float asset;

    Father():secret("~"), firstname("w"), asset(1000000)
    {
        cout << "A constructor of father class with no parameters. " << endl;
    }
    Father(float asset):asset(asset)
    {
        cout << "A constructor of father class with only one parameter. " << endl;
    }
    Father(string fname, float asset):firstname(fname), asset(asset)
    {
        cout << "A constructor of father class with two parameters. " << endl;
    }
    Father(Father &other):firstname(other.firstname), asset(other.asset)	// 拷贝构造
    {
        cout << "Duplicator_constructor of father class. " << endl;
    }
};

class Son:public Father
{
    string secret;
    using Father::asset;        // 把父类中公有继承下来的 asset 成员,在子类中改成私有权限
    using Father::Father;

public:
    void show()
    {
        cout << "First name: " << firstname << endl;
        cout << "Asset: " << asset << endl;
        cout << "Secret of the son: " << secret << endl;
    }
};

int main()
{
    Son s0;
    s0.show();

    Son s1(120000);
    s1.show();

    Son s2("Xing", 120000);
    s2.show();

    Son s3 = s2;
    s3.show();


    return 0;
}
solution4:委托构造

一个类的构造函数可以调用这个类中的另一个构造函数,但是要避免循环委托
委托构造的性能低于透传构造,但是代码维护性更好,因为通常一个类中构造函数都会委托给能力最强(参数最多)的构造函数,代码重构时,只需要更改这个参数最多的构造函数即可。

只有一个类

并不直接通过无参构造实例化对象,而是无参构造委托有参构造,从而实例化对象。

cpp 复制代码
#include <iostream>
using namespace std;

class Son
{
    string secret;
public:
    Son():Son("secret0")
    {
        cout << "A constructor of child class without parameters. " << endl;
    }
    Son(string secret):secret(secret)
    {
        cout << "A constructor of child class with parameter(s). " << endl;
    }

    void show()
    {
        cout << "Secret of the son: " << secret << endl;
    }
};

int main()
{
    Son s0;
    s0.show();

    Son s1("secret1");
    s1.show();

    return 0;
}
父子类的继承
cpp 复制代码
#include <iostream>
using namespace std;

class Father
{
    string secret;
public:
    string firstname;
    float asset;
    Father(string fname, float asset):firstname(fname), asset(asset)
    {
        cout << "A constructor of father class with two parameters. " << endl;
    }
    Father(float asset):asset(asset)
    {
        cout << "A constructor of father class with only one parameter. " << endl;
    }
};

class Son:public Father
{
    string secret;

public:
    Son():Son("secret0")
    {
        cout << "A constructor of child class without parameters. " << endl;
    }
    Son(string secret):Father("Xing", 120000), secret(secret)
    {
        cout << "A constructor of child class with parameter(s). " << endl;
    }

    void show()
    {
        cout << "First name: " << firstname << endl;
        cout << "Asset: " << asset << endl;
        cout << "Secret of the son: " << secret << endl;
    }
};

int main()
{
    Son s0;
    s0.show();

    return 0;
}

拷贝构造函数

需要在初始化列表中显性调用父类的拷贝构造,传 other 对象到父类的拷贝构造中。

cpp 复制代码
#include <iostream>
using namespace std;

class Father
{
    string secret;
public:
    string firstname;
    float asset;
    Father(string fname, float asset):firstname(fname), asset(asset)
    {
        cout << "A constructor of father class with two parameters. " << endl;
    }
    Father(float asset):asset(asset)
    {
        cout << "A constructor of father class with only one parameter. " << endl;
    }
    Father(Father &other):firstname(other.firstname), asset(other.asset)
    {
        cout << "Duplicator_constructor of father class. " << endl;
    }
};

class Son:public Father
{
    string secret;

public:
    Son(string fname, float asset, string secret):Father(fname, asset), secret(secret)
    {
    }
    Son(Son &other):Father(other), secret(other.secret)
    {
        cout << "Duplicator_constructor of child class. " << endl;
    }

    void show()
    {
        cout << "First name: " << firstname << endl;
        cout << "Asset: " << asset << endl;
        cout << "Secret of the son: " << secret << endl;
    }
};

int main()
{
    Son s1("Xing", 120000, "secret1");
    s1.show();

    Son s2 = s1;
    s2.show();

    return 0;
}

继承时构造和析构的时机

子类在继承父类时,会先把父类中的成员保留一份,再来创建子类自己的成员。

父类先构造,子类后构造。子类先析构,父类后析构。

cpp 复制代码
#include <iostream>
using namespace std;

class Father
{
    string secret;
public:
    string firstname;
    float asset;
    Father(string fname, float asset):firstname(fname), asset(asset)
    {
        cout << "A constructor of father class with two parameters. " << endl;
    }
    Father(float asset):asset(asset)
    {
        cout << "A constructor of father class with only one parameter. " << endl;
    }
    ~Father()				// 注意这里没有用虚析构函数
    {
        cout << "Destructor of father class. " << endl;
    }
};

class Son:public Father
{
    string secret;
public:
    Son():Father("default string", 120000)          // 这里使用具体的值
    {
        cout << "A constructor of child class with no parameters. " << endl;
    }
    Son(string fname, float asset, string secret):Father(fname, asset), secret(secret)
    {
        cout << "A constructor of child class with parameters. " << endl;
    }
    ~Son()
    {
        cout << "Destructor of child class. " << endl;
    }
};

int main()
{
    Son *s = new Son;
    delete s;
    s = nullptr;
    cout << endl;

    Son *ss = new Son("z", 1200000, "0");
    delete ss;
    ss = nullptr;
    cout << endl;

    Father *sss = new Son();
    delete sss;
    sss = nullptr;
    cout << endl;

    Father *ssss = new Son("z", 1200000, "0");
    delete ssss;
    ssss = nullptr;
    cout << endl;

    return 0;
}


当父子类中存在同名成员

访问时不会发生冲突,默认访问子类的成员变量。

cpp 复制代码
#include <iostream>
using namespace std;

class Father
{
    string secret;
public:
    string firstname;
    float *asset;
    Father():asset(new float)
    {
        cout << "A constructor of father class without parameters. " << endl;
    }
    ~Father()
    {
        delete asset;
        cout << "Destructor of father class. " << endl;
    }
};

class Son:public Father
{
    string secret;

public:
    float *asset;
    Son():asset(new float)
    {
        cout << "A constructor of child class without parameters. " << endl;
    }
    ~Son()
    {
        delete asset;
        cout << "Destructor of child class. " << endl;
    }

};

int main()
{
    Son *m = new Son;
    *(m->asset) = 140000;
    cout << "*(m->asset): " << *(m->asset) << endl;
    cout << "*(m->Father::asset): " << *(m->Father::asset) << endl;

    delete m;
    m = nullptr;

    return 0;
}

多重继承

一个子类,继承自多个基类。

格式

class 类名:继承权限 父类名, 继承权限 父类名, ...

{

};

1、当多个父类中包含同名成员

多个父类中包含同名成员,通过 域限定符 访问指定的父类中成员。

cpp 复制代码
#include <iostream>
using namespace std;

class Father
{
    string secret;
public:
    void act_1()
    {
        cout << "Earn money. " << endl;
    }
};

class Son:public Father
{
    string secret;

public:
    void act_1()
    {
        cout << "Save money. " << endl;
    }
    void act_2()
    {
        cout << "Inherit money. " << endl;
    }
};

class Grandchild:public Father, public Son
{
};

int main()
{
    Son s;
    s.act_1();
    s.Father::act_1();

    Grandchild g;
    g.act_2();
    g.Son::act_2();

    return 0;
}

2、菱形继承(钻石继承)

     A                ------>		公共基类
   /   \
  B     C             ------>		中间子类
   \   /
     D                ------>		汇集子类

汇集子类中,会包含多份公共基类中的内容。

Problems ------>

1、会发生二义性的问题(同一个变量或者函数,可以通过两种方法访问)

2、如果公共基类过大,会造成汇集子类中的代码 膨胀 / 冗余。

cpp 复制代码
#include <iostream>
using namespace std;

class A
{
public:
    int a;
};

class B:public A
{
};

class C:public A
{
};

class D:public C, public B
{
};

int main()
{
    D d0;
    d0.C::a = 100;
    cout << "In C: " << d0.C::a << endl;		// 100
    d0.B::a = 90;   				// 可以直接通过中间子类访问,直接访问 B 中的 a成员
    cout << "In B: " << d0.B::a << endl;		// 90

	// d0.B::A::a = 80;				// 以下四行报错
 	// cout << d0.B::A::a << endl;	
 	// d0.C::A::a = 70;
    // cout << d0.C::A::a << endl;  // 会发生二义性,因为有两条路径都可以访问到 A

    return 0;
}



虚继承(virtual)

虚继承,指对公共基类 的虚继承。

主要用于解决菱形继承问题,采用虚继承后,公共基类中的内容,只会在汇集子类中存在一份,在汇集子类中,可以通过任意一条路径访问到公共基类中的成员。在中间子类继承时加 virtual。

虚继承解决二义性主要通过虚基类指针和虚基类表实现,下例中的 类B、类C 会分别创建一个虚基类指针和虚基类表。所有同类型对象共用一张虚基类表,每个对象内部增加一个隐藏的虚基类指针成员变量,这个虚基类指针指向虚基类表。派生类D 中,也有隐藏的虚基类指针,但是没有自己虚基类表。在调用 A的成员时, D对象 会通过虚基类指针找到对应的虚基类表,通过查表避免二义性。

------> Solution
cpp 复制代码
#include <iostream>
using namespace std;

class A
{
public:
    int a;
};

class B:virtual public A
{
};

class C:virtual public A
{
};

class D:public B, public C
{
};

int main()
{
    D d0;
    d0.B::A::a = 90;
    cout << d0.C::A::a << endl;			// 成功打印 90,上一个问题得以解决
    
    return 0;
}
为每一个类添加构造函数
cpp 复制代码
#include <iostream>
using namespace std;

class A
{
public:
    int a;
    A(int a):a(a) {cout << "A" << endl; }
};

class B:virtual public A
{
public:
    int b;
    B(int a, int b):A(a), b(b) {cout << "B" << endl; }
};

class C:virtual public A
{
public:
    C(int a):A(a) {cout << "C" << endl; }	// B 和 C 哪一个先构造,取决于 D 在继承时的顺序
};

class D:public C, public B			// 决定结果的顺序 为 C、B
{
public:
    D(int a, int b):B(a, b), C(a), A(a) {cout << "D" << endl; }		
				// B、C 可交换顺序,结果不变
};

int main()
{
    D d0(12, 5);
    cout << d0.a << '\t' << d0.b << endl;
    d0.B::A::a = 90;
    cout << d0.C::A::a << endl;
    
    return 0;
}
cpp 复制代码
#include <iostream>
using namespace std;

class A
{
public:
    int a;
    A(int a):a(a) {cout << "A" << endl; }
};

class B:virtual public A
{
public:
    int b;
    B(int a, int b):A(a), b(b) {cout << "B" << endl; }
};

class C:virtual public A
{
public:
    C(int a):A(a) {cout << "C" << endl; }	// B 和 C 哪一个先构造,取决于 D 在继承时的顺序
};

class D:public B, public C
{
public:
    D(int a, int b):C(a), B(a, b), A(a) {cout << "D" << endl; }		// B、C 可交换顺序
};

int main()
{
    D d0(12, 5);
    cout << d0.a << '\t' << d0.b << endl;
    d0.B::A::a = 90;
    cout << d0.C::A::a << endl;
    return 0;
}

B 和 C 哪一个先构造,取决于 D 在继承时的顺序。

相关推荐
ragnwang4 分钟前
C++ Eigen常见的高级用法 [学习笔记]
c++·笔记·学习
西猫雷婶17 分钟前
python学opencv|读取图像(二十一)使用cv2.circle()绘制圆形进阶
开发语言·python·opencv
kiiila18 分钟前
【Qt】对象树(生命周期管理)和字符集(cout打印乱码问题)
开发语言·qt
小_太_阳43 分钟前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾1 小时前
scala借阅图书保存记录(三)
开发语言·后端·scala
唐 城1 小时前
curl 放弃对 Hyper Rust HTTP 后端的支持
开发语言·http·rust
码银3 小时前
【python】银行客户流失预测预处理部分,独热编码·标签编码·数据离散化处理·数据筛选·数据分割
开发语言·python
从善若水3 小时前
【2024】Merry Christmas!一起用Rust绘制一颗圣诞树吧
开发语言·后端·rust
lqqjuly3 小时前
特殊的“Undefined Reference xxx“编译错误
c语言·c++
冰红茶兑滴水4 小时前
云备份项目--工具类编写
linux·c++