cpp继承详解

继承目的

继承的本质是为了复用代码,派生类复用基类的代码,从而减少冗余的代码,使得创建和维护一个引用变得更加容易。继承代表的是 is-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
#include <iostream>
using namespace std;

class A {
public:
A() = default;
A(int a, int b, int c) : pub(a), pro(b), priv(c) {}
int pub;
protected:
int pro;
private:
int priv;
};

class B : public A { // 公有继承
public:
B(int a, int b, int c) {
pub = a;
pro = b;
// pri = c;
// test.cc:20:9: error: ‘pri’ was not declared in this scope
// pri = c;
// ^~~
}
};


int main() {
B b(1, 2, 3);
cout << "b.pub:" << b.pub << endl;
// cout << "b.pro:" << b.pro << endl;
// test.cc:31:27: error: ‘int A::pro’ is protected within this context
// cout << "b.pro:" << b.pro << endl;
// ^~~
// cout << "b.priv:" << b.priv << endl;
// test.cc:32:28: error: ‘int A::priv’ is private within this context
// cout << "b.priv:" << b.priv << endl;
// ^~~~
return 0;
}
1
2
@└────> # ./a.out 
b.pub: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
#include <iostream>
using namespace std;

class A {
public:
A() = default;
A(int a, int b, int c) : pub(a), pro(b), priv(c) {}
int pub;
protected:
int pro;
private:
int priv;
};

class B : protected A { // 保护继承
public:
B(int a, int b, int c) {
pub = a;
pro = b;
}
};

int main() {
B b(1, 2, 3);
cout << "b.pub:" << b.pub << endl;
return 0;
}
1
2
3
4
5
@└────> # g++ test.cc 
test.cc: In function ‘int main()’:
test.cc:25:27: error: ‘int A::pub’ is inaccessible within this context
cout << "b.pub:" << b.pub << endl;
^~~

保护继承会把 公有 => 保护,保护 => 保护, 私有 => 无法访问。所以原先的公有成员也不能以公有形式访问了。

私有继承

私有继承结果和保护一样,公有 => 私有,保护 => 私有,私有 => 无法访问。

覆写规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
using namespace std;

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

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

int main() {
B b;
b.func(10);
return 0;
}
1
2
3
4
5
6
7
8
9
@└────> # g++ test.cc 
test.cc: In function ‘int main()’:
test.cc:20:14: error: no matching function for call to ‘B::func(int)’
b.func(10);
^
test.cc:13:10: note: candidate: ‘void B::func(int, int)’
void func(int a, int b) {
^~~~
test.cc:13:10: note: candidate expects 2 arguments, 1 provided

在基类中的某些函数,如果没有 virtual 关键字,函数名是 func (参数类型我们不管)。如果派生类中也声明了这个成员函数,那在派生类的作用域中,所有和这个函数同名的函数都被隐藏。

如果基类中的函数和派生类中有两个名字一样的函数 func 满足下面的两个条件

  1. 在基类中函数声明的时候有 virtual 关键字
  2. 基类中的函数和派生类中的函数一模一样,包括函数名,参数,返回类型都一样。

那么这就是叫做覆盖(override),这也就是虚函数,多态的性质。其他的情况,只要名字一样,不满足上面覆盖的条件,就是隐藏。

好多人认为,基类中的函数会继承下来和派生类中的同名不同参的函数构成重载。

重载(overload):
必须在一个域中,函数名称相同但是函数参数不同。重载的作用就是同一个函数有不同的行为,因此不是在一个域中的函数是无法构成重载的。

必须在一个域中,而继承是在两个类中了,所以上面的想法是不成立的。所以,相同的函数名的函数,在基类和派生类中的关系只能是覆盖或者隐藏。

隐藏(hide):
指派生类的成员函数隐藏了基类函数的成员函数。隐藏一词可以这么理解:在调用一个类的成员函数的时候,编译器会沿着类的继承链,逐级的向上查找函数的定义,如果找到了那么就停止查找。所以如果一个派生类和一个基类都有同一个同名(暂且不论参数是否相同)的函数,而编译器最终选择了在派生类中的函数,那么这个派生类的成员函数“隐藏”了基类的成员函数,阻止了编译器继续向上查找函数的定义。