三、继承
1. 什么是继承
继承是类与类之间的关系,是一个很简单很直观的概念,与现实世界中的继承类似,例如儿子继承父亲的财产。
继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程。例如B类 继承于A类,那么 B 就拥有 A 的成员变量和成员函数。被继承的类称为父类或基类,继承的类称为子类或派生类。 子类除了拥有父类的功能之外,还可以定义自己的新成员,以达到扩展的目的。
1. is A 和 has A
大千世界,不是什么东西都能产生继承关系。只有存在某种联系,才能表示有继承关系。如:哺乳动物是动物,狗是哺乳动物,因此,狗是动物,等等。所以在学习继承后面的内容之前,先说说两个术语 is A
和 has A
。
是一种继承关系,指的是类的父子继承关系。表达的是一种方式:这个东西是那个东西的一种。例如:长方体与正方体之间--正方体是长方体的一种。正方体继承了长方体的属性,长方体是父类,正方体是子类。
has-a 是一种组合关系,是关联关系的一种(一个类中有另一个类型的实例),是整体和部分之间的关系(比如汽车和轮胎之间),并且代表的整体对象负责构建和销毁部分对象,代表部分的对象不能共享。
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 | #include <iostream>
#include <string>
using namespace std;
//父类
class Person{
public:
string name;
int age ;
};
//子类
class Student:public Person{
};
int main(){
//子类虽然没有声明name 和 age ,但是继承了person类,等同于自己定义的效果一样
Student s;
s.name = "张三";
s.age = 18;
cout << s.name << " = " << s.age << endl;
return 0 ;
}
|
3. 访问权限回顾
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。继承类型是在继承父类时指定的。 如: class Student : public Person
。 我们几乎不使用 protected 或 private 继承,通常使用 public继承。
表示公有成员,该成员不仅可以在类内可以被访问,在类外也是可以被访问的,是类对外提供的可访问接口;
表示私有成员,该成员仅在类内可以被访问,在类的外面无法访问;
表示保护成员,保护成员在类的外面同样是隐藏状态,无法访问。但是可以在子类中访问。
1. 公有继承(public)
基类成员在派生类中的访问权限保持不变,也就是说,基类中的成员访问权限,在派生类中仍然保持原来的访问权限;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | #include <string>
using namespace std;
class person{
public:
string name;
private:
int age;
};
class student:public person{
//name 和 age保持原有访问权限。
};
int main(){
student s;
s.name = "张三" ;
s.age = 18 ; //编译错误! 无法访问 age
return 0 ;
}
|
2. 私有继承(private)
基类所有成员在派生类中的访问权限都会变为私有(private)权限;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | #include <string>
using namespace std;
class person{
public:
string name;
private:
int age;
};
class student:private person{
//name 和 age保持全部变成private权限
};
int main(){
student s;
s.name = "张三" ;//编译错误! 无法访问 name
s.age = 18 ; //编译错误! 无法访问 age
return 0 ;
}
|
3. 保护继承(protected)
基类的共有成员和保护成员在派生类中的访问权限都会变为保护(protected)权限,在子类中具有访问权限,但是在类的外面则无法访问。
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 | #include <string>
using namespace std;
class person{
public:
string name;
private:
int age;
};
class student: protected person{
//name 和 age保持原有访问权限。
public:
void read(){
s.name = "李四";
s.age = 19 ;
cout << s.age << " 的 " << s.name << " 在看书";
}
};
int main(){
student s;
s.read();
//类的外面无法访问,编译错误。
s.name = "张三" ;
s.age = 18 ;
return 0 ;
}
|
4. 构造和析构
1. 继承状态
构造函数是对象在创建是调用,析构函数是对象在销毁时调用。但是在继承关系下,无论在对象的创建还是销毁,都会执行父类和子类的构造和析构函数。 它们一般会有以下规则:
a. 子类对象在创建时会首先调用父类的构造函数;
b. 父类构造函数执行完毕后,执行子类的构造函数;
c. 当父类的构造函数中有参数时,必须在子类的初始化列表中显示调用;
d. 析构函数执行的顺序是先调用子类的析构函数,再调用父类的析构函数
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 | #include <iostream>
using namespace std;
class person{
public :
person(){
cout << "调用了父类构造函数" << endl;
}
~person(){
cout << "调用了父类析构函数" << endl;
}
}
class student: public person{
public :
student(){
cout << "调用了子类类构造函数" << endl;
}
~student(){
cout << "调用了子类析构函数" << endl;
}
};
int main() {
Student s1
return 0;
}
|
2. 继承和组合
如果在继承状态下,子类中的成员又含有其他类的对象属性,那么他们之间的构造很析构调用顺序,遵循以下原则:
a. 先调用父类的构造函数,再调用组合对象的构造函数,最后调用自己的构造函数;
b. 先调用自己的析构函数,再调用组合对象的析构函数,最后调用父类的析构函数。
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
50
51 | #include <iostream>
using namespace std;
//父类
class Person{
public :
Person(){
cout << "调用了父类构造函数" << endl;
}
~Person(){
cout << "调用了父类析构函数" << endl;
}
};
//其他类
class A{
public :
A(){
cout << "调用A的构造函数" << endl;
}
~A(){
cout << "调用A的析构函数" << endl;
}
};
//子类
class Student: public Person{
public :
Student(){
cout << "调用了子类类构造函数" << endl;
}
~Student(){
cout << "调用了子类析构函数" << endl;
}
public:
A a;
};
int main() {
Student s1(18 , "zhangsan");
return 0;
}
|
3. 调用父类有参构造
继承关系下,子类的默认构造函数会隐式调用父类的默认构造函数,假设父类没有默认的无参构造函数,那么子类需要使用参数初始化列表方式手动调用父类有参构造函数。
一般来说在创建子类对象前,就必须完成父类对象的创建工作,也就是在执行子类构造函数之前,必须先执行父类的构造函数。c++ 使用初始化列表来完成这个工作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | #include <iostream>
using namespace std;
class Person{
private :
int age ;
string name ;
public :
Person(int age , string name){
cout << "调用了父类构造函数" << endl;
this->age = age ;
this->name = name;
}
};
|
子类只能使用初始化列表的方式来访问父类构造
1
2
3
4
5
6
7
8
9
10
11
12 | class Student: public Person{
public :
Student(int age , string name):Person(age ,name){
cout << "调用了子类类构造函数" << endl;
}
};
int main(){
Student s1(18 , "zs");
return 0 ;
}
|
4. 再说初始化列表
初始化列表在三种情况下必须使用:
- 情况一、需要初始化的数据成员是对象,并且对应的类没有无参构造函数
- 情况二、需要初始化const修饰的类成员或初始化引用成员数据;
- 情况三、继承关系下,父类没有无参构造函数情况
初始化列表的赋值顺序是按照类中定义成员的顺序来决定
1. 常量和引用的情况
类中成员为 引用
或者 const修饰
的成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | #include <iostream>
#include <string>
using namespace std;
class stu{
public:
const string name; //常量不允许修改值,所以不允许在构造里面使用 = 赋值
int &age; //
stu(string name , int age):name(name),age(age){
cout << "执行构造函数" <<endl;
}
};
int main(){
stu s1("张三" , 88);
cout << s1.name << " = " << s1.age << endl;
return 0 ;
}
|
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 | #include <iostream>
using namespace std;
class A{
public:
int number;
A(int number):number(number){
cout << "执行了A的构造函数" <<endl;
}
};
class stu{
public:
A a;
stu():a(9){
cout << "执行了stu的构造函数" <<endl;
}
};
int main(){
stu s;
return 0;
}
|
5. 重写父类同名函数
在继承中,有时候父类的函数功能并不够强大,子类在继承之后,可以对其进行增强扩展。 如果还想调用你父类的函数,可以使用 父类::函数名()
访问
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 | #include <iostream>
using namespace std;
class WashMachine{
public:
void wash(){
cout << "洗衣机在洗衣服" << endl;
}
};
class SmartWashMachine : public WashMachine{
public:
void wash(){
cout << "智能洗衣机在洗衣服" << endl;
cout << "开始添加洗衣液~~" << endl;
//调用父类的函数
WashMachine::wash();
}
};
int main(){
SmartWashMachine s;
s.wash();
return 0 ;
}
|
6. 多重继承
C++ 允许存在多继承,也就是一个子类可以同时拥有多个父类。只需要在继承时,使用逗号进行分割即可。
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 | using namespace std;
class Father{
public:
void makeMoeny(){
cout << "赚钱" << endl;
}
};
class Mother{
public:
void makeHomeWork(){
cout << "做家务活" << endl;
}
};
class Son:public Father , public Mother{
};
int main(){
Son s ;
s.makeMoeny();
s.makeHomeWork();
return 0 ;
}
|
1. 多重继承的构造函数
多继承形式下的构造函数和单继承形式基本相同,只是要在子类的构造函数中调用多个父类的构造函数 。 他们调用的顺序由定义子类时,继承的顺序决定。
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 Father{
string name;
public:
Father(string name):name(name){
cout << "执行父亲构造函数" <<endl;
}
};
class Mother{
int age;
public:
Mother(int age):age(age){
cout << "执行母亲构造函数" <<endl;
}
};
class Son:public Father , public Mother{
public:
Son(string name ,int age):Father(name),Mother(age){
cout << "执行孩子构造函数" <<endl;
}
};
int main(){
Son s("无名氏" ,38);
return 0 ;
}
|
7. 类的前置声明
一般来说,类和 变量是一样的,必须先声明然后再使用,如果在某个类里面定义类另一个类的对象变量,那么必须在前面做前置声明,才能编译通过。
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 father; //所有前置声明的类,在某个类中定义的时候,只能定义成引用或者指针。
class son{
public:
//father f0; //因为这行代码,单独拿出来说,会执行B类的无参构造,
//但是编译器到此处的时候,还不知道B这个类的构造长什么样。
father &f1;
father *f2;
son(father &f1 , father *f2):f1(f1),f2(f2){
}
};
class father{
};
int main(){
// father b; //---> 执行B的构造函数。
father f1;
father f2;
son s(f1 ,&f2);
return 0 ;
}
|