0%

C++:继承

单继承

1.继承的写法和权限问题

class 子类:继承方法 父类

  1. 继承方法:父类中的属性在子类中的最低权限

    权限由低到高:public protected private

    eg:class boy:public woman——woman类的属性在boy类的最低权限为public

  2. 父类的私有属性对于子类来说是不可访问的

类的访问权限有三种:

  1. public 公共权限: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问
  2. protected 保护权限: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问
  3. private 私有权限:只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问。

三种权限的区别:

public:可以被任意实体访问
protected:只允许本类及子类的成员函数访问
private:只允许本类的成员函数访问

2.继承中构造函数的写法

子类的构造函数必须调用父类的构造函数

不想写的时候,习惯在父类中增加一个无参构造函数

如:直接Boy boy;——报错,显示是已删除的构造函数

​ 解决方法:在woman类中新增无参构造函数Woman(){}

测试代码:

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
48
49
#include <iostream>
using namespace std;
class Woman
{
public:
Woman(string name,int age):name(name),age(age){
cout<<"调用基类带参构造函数"<<endl;
}//初始化参数列表
Woman(){
cout<<"调用基类无参构造函数"<<endl;
};
void print(){
cout<<"Woman "<<name<<" "<<age<<endl;
}
protected:
string name;
int age;
};
class Boy:protected Woman//Woman类的属性在boy类的最低权限是protected
{
public:
Boy(){
cout<<"调用子类无参构造函数"<<endl;
}
//采用初始化列表的方式调用父类的构造函数
Boy(string name,int age):Woman(name,age){
cout<<"调用子类带参构造函数"<<endl;
}
void print(){
cout<<"Boy "<<name<<" "<<age<<endl;
}
protected:
//子类隐含有以下属性
//void print();
//string name;
//int age;
};
int main(){
Woman woman1;
cout<<"============================"<<endl;
Woman woman2("Amy",40);
cout<<"============================"<<endl;
Boy boy1;
cout<<"============================"<<endl;
Boy boy2("Jack",10);
cout<<"============================"<<endl;
woman2.print();
boy2.print();
};

结果:

image-20240510131814280

总结:子类构造函数一定会调用对应的父类构造函数

构造顺序和析构顺序

正常情况下,构造函数和析构函数顺序相反

测试代码:

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
#include <iostream>
using namespace std;
class A{
public:
A(){
cout<<"A";
}
~A(){
cout<<"~A";
}
};
class B:public A{
public:
B(){
cout<<"B";
}
~B(){
cout<<"~B";
}
};
int main(){
A a;//A
B b;//AB
//~B~A~A
};

结果:

1
AAB~B~A~A

注意:有delete的时候

1
2
3
B *p=new B;
B b;
delete p;

结果:

1
2
3
ABAB~B~A~B~A
//第一个~B~A是对象p的,什么delete就结束对象的生命周期
//b的析构是因为程序结束,生命周期结束自动调用析构函数

类的继承的遗传性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A{
public:
int a;
A(int a):a(a){}
};
class B{
public:
//int a;
int b;
B(int a,int b):A(a),b(b){}
};
class C{
public:
//int a;
//int b;
int c;
C(int a,int b,int c):B(a,b),c(c){}
};
  1. B继承A,C继承B——则C不仅包含B的属性,也包含A的属性
  2. 构造函数的写法:初始化参数列表—调用直接父类的构造函数+初始化其他属性

继承中的同名问题

父类和子类同名问题:

  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
#include <iostream>
using namespace std;
class Woman
{
public:
Woman(string name,int age):name(name),age(age){}
void print(){
cout<<"Woman "<<name<<" "<<age<<endl;
}
protected:
string name;
int age;
};
class Boy:public Woman
{
public:
Boy(string name,int age,string mmName,int mmAge):name(name),age(age),Woman(mmName,mmAge){}
void print(){
//1.通常都是就近原则,调用本类的
cout<<"Boy "<<name<<" "<<age<<endl;
//2.用类名限定,调用对应类的属性
cout<<"类名限定 "<<Woman::name<<" "<< Woman::age<<endl;
}
protected:
string name;
int age;
};
int main(){
Boy coolboy("boy",18,"woman",40);
coolboy.print();//Boy boy 18 类名限定 woman 40
coolboy.Woman.print();//Woman woman 40
Woman *p=new Boy("Woman_Boy",19,"Woman",41);
p->print();//Woman WomanPtr 41
};

结果:

1
2
3
4
Boy boy 18
类名限定 woman 40
Woman woman 40
Woman WomanPtr 41

总结:

  1. 通常情况下,都是就近原则,优先调用本类的数据和方法

  2. 如果加上类名限定,则调用对应类的数据和方法

  3. 如果是指针,没有virtual的情况下,也是就近原则

  4. 特殊情况:父类指针被子类对象初始化,没有virtual的情况看类型,有virtual的情况看对象

    Woman *p=new Boy("Woman_Boy",19,"Woman",41);p->print();

    所以调用的p对应的类型Woman的函数

    注意:不允许子类指针被父类对象初始化//除非进行指针类型转换

多继承

  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
31
32
33
34
35
36
37
38
#include <iostream>
using namespace std;
class Woman
{
public:
Woman(string WomanFName,string WomanSName):WomanFName(WomanFName),WomanSName(WomanSName){}
protected:
string WomanFName;//姓
string WomanSName;//名
};
class Man
{
public:
Man(string ManFName,string ManSName):ManFName(ManFName),ManSName(ManSName){}
protected:
string ManFName;
string ManSName;
};
//多继承
class Son:public Woman,public Man
{
public:
//多继承要调用多个父类的构造函数
Son(string ManFName,string ManSName,string WomanFName,string WomanSName,string SonSName)
:Man(ManFName,ManSName),Woman(WomanFName,WomanSName),SonFName(ManFName+WomanFName),SonSName(SonSName){}
void print(){
cout<<"mother:"<<WomanFName+WomanSName<<endl;
cout<<"father:"<<ManFName+ManSName<<endl;
cout<<"Son:"<<SonFName+SonSName<<endl;
}
protected:
string SonFName;
string SonSName;
};
int main(){
Son son("欧","明明","阳","丽丽","修");
son.print();
};
1
2
3
mother:阳丽丽
father:欧明明
Son:欧阳修

虚继承

菱形继承:B、C继承A,D继承B、C

存在二义性:例如A-int a;B-//int a;C-//int a;D-//int a;二义性:D的a来自谁

image-20240510145208543

解决方法一:加上类型限定

屏幕截图 2024-05-10 145254

解决方法二:使用虚继承

1
2
3
4
5
6
7
8
9
10
11
12
13
class A {
public:
int a;
A(int a) : a(a) {}
};
class B : virtual public A {
public:
B(int a) : A(a) {}
};
class C : virtual public A {
public:
C(int a) : A(a) {}
};

image-20240510150049091

image-20240510150104291

PS:几个类共享一份数据

image-20240517090644926

  • 类B和类C都虚继承自类A。虚继承意味着它们不会直接包含类A的实例,而是会包含一个指向类A的共享基类的指针(这通常被称为虚基类指针)。所以sizeof(B)=8,sizeof(C)=8
  • 类D继承了类B和类C,D需要包含B和C的所有成员,所以sizeof(D)=16

虚函数

虚函数——用virtual修饰的函数

注意:构造函数不能为虚函数

1.虚函数对类的内存的影响

image-20240510150956262

2.纯虚函数

抽象类:具有纯虚函数的类

特性:不能创建对象,但可以创建对象类型指针

image-20240510151251920

子类继承抽象类,必须实现所有的纯虚函数才能实例化。

3.虚析构函数

image-20240510151908866

结果:

1
2
3
4
父类构造函数
子类构造函数
父类析构函数
//发现没有调用子类的析构函数

解决方法:使用虚析构函数

——注意:虚函数一旦被继承,不管被继承多少次,永远为虚函数

image-20240510152117958

结果:

1
2
3
4
父类构造函数
子类构造函数
子类析构函数
父类析构函数