继承

概述

对象是基本,但是可以再类的基础上在进行抽象,抽象出更高层次的类,例如猫可以抽象出一个猫类,狗可以抽象出一个狗类,但是猫和狗可以抽象出更高层次的动物类,而c++中模拟模拟这种结构的方式就是继承,他也是代码重用的方式之一,通过继承,我们可以用原有类型来定义一个新类型,定义的新类型即包含了原有类型的成员,也能自己添加新的成员,而不用将原有类的内容重新书写一边,原有类型称为“基类”或”父类“,在它基础上建立的类称为”派生类“或”子类“

1
2
3
4
5
class 基类{};

class 派生类
:public/protected/private 基类
{};

定义一个派生类的过程:

  1. 吸收基类的成员
  2. 添加新的成员(非必须)
  3. 隐藏基类的成员(非必须)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Point{
public:
Point(int x,int y)
:_ix(x)
,_iy(y)
{}
private:
int _ix;
int _iy;
};


class Point3D
: public Point
{
public:
Point3D(int x, int y, int z)
: Point(x,y)
, _iz(z)
{
cout << "Point3D(int*3)" << endl;
}

void display() const{ //添加新的成员函数
print();
cout << _iz << endl;
}
private:
int _iz; //添加新的数据成员
};

3种继承方式的区别:

public:可以访问public,可以访问protected,不可访问private,派生类的对象可以直接访问基类的public

protected:可以访问public,可以访问protected,不可访问private,派生类对象不可以访问基类的任何成员,基类成员相当于是protected权限

private:可以访问public,可以访问protected,不可访问private,派生类对象不可以访问基类的任何成员,基类成员相当于是private权限


继承的权限和基类的权限在派生类中的权限其实是取交集的,例如基类中的成员为protected权限,派生类是public继承,取交集可得protected,派生类继承的基类的protected成员的访问权限是protected,同理如果基类是private权限,派生类private继承,则派生类中基类的private成员的访问权限是private


protected和private两种继承的区别?

一重继承没什么区别,但是二重继承就有区别了,因为根据取交集,protected继承一级派生类中基类的public和protected是protected权限,是可以继续往下继承的,而private继承以及派生类中基类的public和protected是private继承的,是没办法往下继承的


总结:派生类的访问权限如下:

  1. 不管什么继承方式,派生类内部都不能访问基类的私有成员;
  2. 不管什么继承方式,派生类内部除了基类的私有成员不可以访问,其他的都可以访问;
  3. 不管什么继承方式,派生类对象在类外除了公有继承基类中的公有成员可以访问外,其他的都不能访问。

(记忆:1.私有的成员在类外无法直接访问; 2.继承方式和基类成员访问权限做交集)

常考题总结

Q1:派生类在类之外对于基类成员的访问 ,具有什么样的限制?

只有公有继承自基类的公有成员,可以通过派生类对象直接访问,其他情况一律都不可以进行访问

Q2:派生类在类内部对于基类成员的访问 ,具有什么样的限制?

对于基类的私有成员,不管以哪种方式继承,在派生类内部都不能访问;

对于基类的非私有成员,不管以哪种方式继承,在派生类内部都可以访问;

Q3:保护继承和私有继承的区别?

如果继承层次中都采用的是保护继承,任意层次都可以访问顶层基类的非私有成员;但如果采用私有继承之后,这种特性会被打断。

继承关系的局限性

创建销毁的方式不能被继承-构造析构

复制控制的方式不能被继承 –拷贝赋值

空间分配的方式不能继承–operator new ,opertaor delete

(类默认提供的方法都不能继承)

友元不能被继承

简单的单继承方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Base
{
public:
Base(){
cout << "Base()" << endl;
}
}


class Derived
:public Base
{
Derived(){
cout << "Derived()" << endl;
}

};

构造Derived对象的时候发现Base()在Derived()之前打印,那是因为创建派生类对象时,先到用基类构造函数在调用派生类构造函数的吗?

并不是,是因为调用派生类构造函数的时候会调用基类的构造函数,创建一个派生类对象其中是包含基类子对象的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Base
{
public:
Base(int x)
:_ix(x)
{
cout << "Base()" << endl;
}
private:
int _ix;
}


class Derived
:public Base
{
Derived(int base)
:Base(base)//不写这个会报错 不让创建派生类子对象,并且这里写的是类名不是构造函数名
{
cout << "Derived()" << endl;
}

};

派生类的析构函数调用完毕后会立即调用基类的析构函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Base
{
public:
Base(){
cout << "Base()" << endl;
}
~Base(){
cout << "~Base()" << endl;
}
}


class Derived
:public Base
{
Derived(){
cout << "Derived()" << endl;
}
~Derived(){
cout << "~Derived()" << endl;
}

};

基类子对象和成员子对象的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Base
{
public:
Base(int x)
:_ix(x)
{
cout << "Base()" << endl;
}
private:
int _ix;
}
class Test{
Test(int x)
:_ix(x)
{
cout << "Test()" << endl;
}
~Test(){
cout << "~Test()" << endl;
}
private:
int _ix;
}

class Derived
:public Base
{
Derived(int base,int test)
:Base(base)//显示调用基类构造函数,初始化基类子对象。基类子对象的初始化一定要放在最靠前的位置
,_te(test)//显示调用对象成员的构造函数,初始化成员子对象
{
cout << "Derived()" << endl;
}
private:
Test _te
};

构造和析构的调用顺序?

肯定是Derived的构造析构最先调用,然后基类,成员子对象,虽然调用顺序是这样,但是打印效果是Base()先调用,因为调用Derived的时候会调用Base的构造函数,析构是基类最后调用的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Base
{
public:
Base(int x)
:_ix(x)
{
cout << "Base()" << endl;
}
private:
int _ix;
}
class Test{
Test(int x)
:_ix(x)
{
cout << "Test()" << endl;
}
~Test(){
cout << "~Test()" << endl;
}
private:
int _ix;
}

class Derived
:public Base
{
Derived(int base,int test,int base2)
:Base(base)//显示调用基类构造函数,初始化基类子对象。基类子对象的初始化一定要放在最靠前的位置
,_te(test)//显示调用对象成员的构造函数,初始化成员子对象
,_be(base2)
{
cout << "Derived()" << endl;
}
private:
Test _te;
Base _be;
};

这个的调用顺序呢 构造 :Derived 基类子对象 Test Base

但打印结果是:Base() Test() Base() Derived()

析构:Derived Test Base 基类子对象

打印结果为:~Derived() ~Base() ~Test() ~Base()

其中成员子对象Base和成员子对象Test这俩的初始化顺序是跟声明顺序有关的

对基类成员的隐藏

基类数据成员的隐藏

派生类中定义了和基类的数据成员同名的数据成员,就会对基类的这个数据成员形成隐藏,无法直接访问基类的这个数据成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Base{
public:
Base(long x)
: _base(x)
{
cout << "Base()" << endl;
}

long _data = 100;
private:
long _base;
};

class Derived
: public Base
{
public:
Derived(long base,long derived)
: Base(base)//创建基类子对象
, _derived(derived)
{
cout << "Derived()" << endl;
}
private:
long _derived;

};

void test0(){
Derived dd(1,2);
cout << dd._data << endl;
cout << dd.Base::_data << endl;
}

上面的代码派生类Derived是可以对基类Base中的_base进行访问的

那如果派生类也定义一个同名的_base那还可以对基类的 _base访问吗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Base{
public:
Base(long x)
: _base(x)
{
cout << "Base()" << endl;
}

long _data = 100;
private:
long _base;
};

class Derived
: public Base
{
public:
Derived(long base,long derived)
: Base(base)//创建基类子对象
, _derived(derived)
{
cout << "Derived()" << endl;
}

long _data = 19;
private:
long _derived;

};

void test0(){
Derived dd(1,2);
cout << dd._data << endl;
cout << dd.Base::_data << endl;
}

发现访问不到了,那是对基类的_base进行了修改吗

1
2
3
4
5
cout << sizeof(Base) << endl;//16
cout << sizeof(Derived) << endl;//32

cout << d1._data << endl;//19
cout << d1.Base::_data << endl;//100

通过上述测试可知其实是对数据成员进行了隐藏,而不是修改,并且这个隐藏跟数据类型没关系,只要数据成员同名就会发生隐藏

(这里与嵌套类做区分——派生类对象中一定包含基类子对象,但嵌套类并不意味着内存结构上也是嵌套,除非外部类包含内部类类型的对象成员)

基类成员函数的隐藏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Base{
public:
Base(long x)
: _base(x)
{
cout << "Base()" << endl;
}

void print() const{
cout << "Base::_base:" << _base << endl;
cout << "Base::_data:" << _data << endl;
}
private:
long _base;
};

class Derived
: public Base
{
public:
Derived(long base,long derived)
: Base(base)//创建基类子对象
, _derived(derived)
{
cout << "Derived()" << endl;
}

private:
long _derived;

};

void test0(){
Derived dd(1,2);
cout << dd._data << endl;
cout << dd.Base::_data << endl;
}

Derived能否调用print()函数? 当然可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Base{
public:
Base(long x)
: _base(x)
{
cout << "Base()" << endl;
}

void print() const{
cout << "Base::_base:" << _base << endl;
cout << "Base::_data:" << _data << endl;
}
private:
long _base;
};

class Derived
: public Base
{
public:
Derived(long base,long derived)
: Base(base)//创建基类子对象
, _derived(derived)
{
cout << "Derived()" << endl;
}
void print() const{
cout << "Derived::_derived:" << _base << endl;

}

private:
long _derived;

};

这个时候Derived的对象调用print得到的结果是什么?

是Derived::_derived:这个print

1
2
3
4
Derived d1(1,2);

d1.print();
d1.Base::print();

派生类的成员函数对基类的同名成员函数形成了隐藏

验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Base{
public:
Base(long x)
: _base(x)
{
cout << "Base()" << endl;
}

int print() const{
cout << "Base::_base:" << _base << endl;
cout << "Base::_data:" << _data << endl;
return 1;
}
private:
long _base;
};

class Derived
: public Base
{
public:
Derived(long base,long derived)
: Base(base)//创建基类子对象
, _derived(derived)
{
cout << "Derived()" << endl;
}
void print() const{
cout << "Derived::_derived:" << _base << endl;

}

private:
long _derived;

};

void test(){
Derived d1(1,2);

d1.print() + 1;//error 说明调用的Derived的
d1.Base::print();//error 说明调用的是Base的
}

同时也说明了一个问题,返回值不同也可以实现隐藏,那参数不同可以实现隐藏吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Base{
public:
Base(long x)
: _base(x)
{
cout << "Base()" << endl;
}

void print() const{
cout << "Base::_base:" << _base << endl;
cout << "Base::_data:" << _data << endl;
return 1;
}
private:
long _base;
};

class Derived
: public Base
{
public:
Derived(long base,long derived)
: Base(base)//创建基类子对象
, _derived(derived)
{
cout << "Derived()" << endl;
}
void print(int x) const{
cout << "Derived::_derived:" << _base << endl;

}

private:
long _derived;

};

void test(){
Derived d1(1,2);

d1.print();//error 说明调用的Derived的 报错信息提示参数太少了
d1.Base::print();//error 说明调用的是Base的
}

说明返回值和参数不同也可以实现隐藏,说明只要函数名相同就行了

O:那如果派生类只有一个与基类同名的定义,那可以实现隐藏吗?

A:可以实现

多继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class A
{
public:
A(){ cout << "A()" << endl; }
~A(){ cout << "~A()" << endl; }
void print() const{
cout << "A::print()" << endl;
}
};

class B
{
public:
B(){ cout << "B()" << endl; }
~B(){ cout << "~B()" << endl; }
void show() const{
cout << "B::show()" << endl;
}
};

class C
{
public:
C(){cout << "C()" << endl; }
~C(){ cout << "~C()" << endl; }
void display() const{
cout << "C::display()" << endl;
}
};

class D
: public A,B,C
{
public:
D(){ cout << "D()" << endl; }
~D(){ cout << "~D()" << endl; }
};

void test(){
D d;//基类的构造函数的调用顺序和继承的顺序一致,析构也是先构造的后析构
d.print();
d.show();//error
d.display();// error 按: public A,B,C这样写public只对第一个A生效,后面的默认都是private继承
}

要想全部公有继承

1
2
3
4
5
6
7
8
9
class D
: public A
,public B
,public C
{
public:
D(){ cout << "D()" << endl; }
~D(){ cout << "~D()" << endl; }
};

若每个基类的成员函数都有同名函数呢?

成员名访问冲突的二义性

解决方式一:加类作用域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class A
{
public:
A(){ cout << "A()" << endl; }
~A(){ cout << "~A()" << endl; }
void print() const{
cout << "A::print()" << endl;
}
};

class B
{
public:
B(){ cout << "B()" << endl; }
~B(){ cout << "~B()" << endl; }
void print() const{
cout << "B::print()" << endl;
}
};

class C
{
public:
C(){cout << "C()" << endl; }
~C(){ cout << "~C()" << endl; }
void print() const{
cout << "C::print()" << endl;
}
};

class D
: public A
,public B
,public C
{
public:
D(){ cout << "D()" << endl; }
~D(){ cout << "~D()" << endl; }
};

void test(){
D d;
d.A::print();
d.B::print();
d.C::print();
}

解决方式二:在派生类中声明同名的数据成员,那么就可以对基类的这些同名成员函数实现隐藏

1
2
3
4
5
6
7
8
9
10
11
12
class D
: public A
,public B
,public C
{
public:
D(){ cout << "D()" << endl; }
~D(){ cout << "~D()" << endl; }
void print() const{
cout << "D::print()" << endl;
}
};

钻石继承–存储二义性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class A
{
public:
void print() const{
cout << "A::print()"<< _a << endl;
}
double _a = 1;
};

class B
: public A
{
public:
double _b = 2;
};

class C
: public A
{
public:
double _c = 3;
};

class D
: public B
, public C
{
public:
double _d = 4;
};

void test(){
D d;
d.print();//error A<-B A<-C B&C<-D 说明D中是包含两个A类的基类子对象的,D要调用A的成员函数必须要调用A类的基类子对象,但这时有两个A类的基类子对象,产生冲突
}

解决方案一:

1
2
3
4
5
6
void test(){
D d;
d.B::print();
d.C::print();
d.A::print();//error 这条路径仍然不唯一 有两条路找 D->C->A D->B->A
}

解决方案二:

1
2
3
4
5
6
7
8
9
10
11

class D
: public B
, public C
{
public:
void print() const{
cout << "D::print()"<< _a << endl;
}
double _d = 4;
};

通过隐藏的特性让调用同名函数的时候会在D中找

但是可能A类中如果有很多的成员,那这隐藏一个,实际上也没有解决D中有两个基类子对象的问题,那如果解决这个问题呢?

采用虚拟继承

解决方案三:

1
2
3
4
5
6
7
8
9
10
11
12
13
class B
:virtual public A
{
public:
double _b = 2;
};

class C
:virtual public A
{
public:
double _c = 3;
};

采用虚拟继承会发现内存结构也发生了变化

1
cout << sizeof(D) << endl;//不采用是40  采用是48

采用虚拟继承的方式处理菱形继承问题,实际上改变了派生类的内存布局。B类和C类对象的内存布局中多出一个虚基类指针,位于所占内存空间的起始位置,同时继承自A类的内容被放在了这片空间的最后位置。D类对象中只会有一份A类的基类子对象。

image-20240617162029626

与原来的对比

image-20240617162115691

采用这样的方法,就只会存储一份基类子对象A了

基类和派生类之间的转换

一般情况下,基类对象占据的空间小于派生类对象

(空继承可能相等)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Base{
public:
Base(long x=1)
: _base(x)
{
cout << "Base()" << endl;
}

void print() const{
cout << "Base::_base:" << _base << endl;
cout << "Base::_data:" << _data << endl;
}
private:
long _base;
};

class Derived
: public Base
{
public:


};

void test(){
Derived d1();
cout << sizeof(Base) << endl;//8
cout << sizeof(Derived) << endl;//8
}

1:可否把一个基类对象赋值给一个派生类对象?可否把一个派生类对象赋值给一个基类对象?

2:可否将一个基类指针指向一个派生类对象?可否将一个派生类指针指向一个基类对象?

3:可否将一个基类引用绑定一个派生类对象?可否将一个派生类引用绑定一个基类对象?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Base base;
Derived d1;

base = d1; //ok
d1 = base; //error
//可以用派生类对象给基类对象赋值,不能用基类对象给派生类对象赋值


Base * pbase = &d1; //ok
Derived * pderived = &base //error
//可以用基类指针指向派生类对象,不能用派生类指针指向基类对象


Base & rbase = d1; //ok
Derived & rderived = base; //error
//可以用基类引用绑定派生类对象,不能用派生类引用绑定基类对象

为什么基类的可以指向派生类而派生类不能指向基类?

因为基类指针属于小内存,派生类属于大内存,大内存可以被赋值给小内存,因为可以把大内存多出来的部分给舍弃,而小内存不能赋值给大内存,会出现非法区间
所以看似赋值成功了,但是其实基类还是只能访问派生类的基类子对象的部分

所以总结:

向下转型是有风险的(向上是指向基类方向,向下是指向派生类方向)

但是有些情况向下转型是合理的,例如

1
2
3
4
5
6
7
Base base;
Derived d1;

Base * pbase = &d1;
Derived * pderived = pbase;//这种转型是合理的,但是不能直接转型,会报错
//可以这样转换
Derived *pderived = dynamic_cast<Derived*>(pbase);
1
2
3
4
5
6
7
8
Base base;
Derived d1;

Base * pbase = &base;
]
//这样转换不合理,可以转换成功吗
Derived *pderived = dynamic_cast<Derived*>(pbase);
pderived->dispaly();//段错误 如果转换不合理就会发还一个空指针

派生类对象间的复制控制

复制控制函数就是 拷贝构造函数,赋值运算符函数

原则:基类部分与派生类部分要单独处理

(1)当派生类中没有显式定义复制控制函数时,就会自动完成基类部分的复制控制操作;

(2)当派生类中有显式定义复制控制函数时,不会再自动完成基类部分的复制控制操作,需要显式地调用;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Base{
public:
Base(long base)
: _base(base)
{}

protected:
long _base = 10;
};


class Derived
: public Base
{
public:
Derived(long base, long derived)
: Base(base)
, _derived(derived)
{}

void print()const {
cout << "_base" << _base << endl;
cout << "_derived" << _derived << endl;
}
private:
long _derived = 12;
};


void test(){
Derived d1(10,7);
Derived d2 = d1;
d2.print();//可以自动完成对基类的复制
}

在派生类中显示定义拷贝构造赋值运算符函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Base{
public:
Base(long base)
: _base(base)
{}

protected:
long _base = 10;
};


class Derived
: public Base
{
public:
Derived(long base, long derived)
: Base(base)
, _derived(derived)
{}

Derived(const Derived & rhs)
, _derived(rhs._derived)
{
cout << "Derived(const Derived & rhs)" << endl;
}

Derived &operator=(const Derived & rhs){
_derived = rhs._derived;
cout << "Derived& operator=(const Derived &)" << endl;
return *this;
}

void print()const {
cout << "_base" << _base << endl;
cout << "_derived" << _derived << endl;
}
private:
long _derived = 12;
};


void test(){
Derived d1(10,7);
Derived d2 = d1;
d2.print();//反而不能进行正常的复制了,如果在拷贝构造中添加了参数的默认值,会使用了构造函数中参数的默认值了
}

解决:显示的调用基类的拷贝构造函数,赋值运算符函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class Base{
public:
Base(long base)
: _base(base)
{}

protected:
long _base = 10;
};


class Derived
: public Base
{
public:
Derived(long base, long derived)
: Base(base)
, _derived(derived)
{}

Derived(const Derived & rhs)
: Base(rhs)//调用Base的拷贝构造
, _derived(rhs._derived)
{
cout << "Derived(const Derived & rhs)" << endl;
}

Derived &operator=(const Derived & rhs){
//调用Base的赋值运算符函数
Base::operator=(rhs);
_derived = rhs._derived;
cout << "Derived& operator=(const Derived &)" << endl;
return *this;
}

void print()const {
cout << "_base" << _base << endl;
cout << "_derived" << _derived << endl;
}
private:
long _derived = 12;
};


对于这种都是栈上的数据的不在派生类中显示定义拷贝构造和赋值运算符函数是可以自动调用的,但是对于堆上的数据就不行了,如果不写默认是浅拷贝。

分两种情况,第一种是基类管理堆空间的数据,而派生类没管理,则需要在基类中显示定义赋值运算符和拷贝构造,派生类不需要,

第二种是基类派生类都管理,这时派生类就需要显式定义了。