1.继承的概念
1.1 为什么要有继承
在C++中,每一个类都有属于自己的成员函数与成员变量,但有的时候有两个类的成员函数与成员变量相同,为了避免对此重复定义相同的类,引入继承,子类只需继承父类的成员函数与变量即可
1.2 继承的定义
在创建一个类时,不需要重新编写成员函数与成员变量,只需要指定新建的类继承了一个已有的类即可;
已有的类是父类(基类),新建的类是子类(派生类);
继承是一种服用手段,通过继承关系,子类与基类共享公有的东西,各自实现本质不同的东西
1.3 继承的目的
实现代码的重用,增强代码的复用性
1.4 实现一个简单的继承关系
class Person {
public:
string _name; // 姓名
private:
int id;//身份证号
};
class Student : public Person {//子类student继承父类
protected:
int _num; // 学号
};
如果子类继承了父类,则父类所有的成员函数与成员变量都会变成子类的一部分,但是在父类中私有的被集成到子类中是不可见的(在子类中存在,但是子类不能被访问这些私有成员)
如对上面的继承关系,定义一个父类p1,定义一个子类s,所得监视窗口如下:
从上图可以看出,父类的私有成员被集成到子类中,但是子类不能访问这个私有成员,且也不能通过父类的对象访问该私有成员。
2.继承方式及访问属性
有三种继承方式:公有继承(public)、私有继承(private)、保护继承(protected)
2.1 公有继承
公有继承的特点是父类的公有成员与保护成员作为子类的成员时,都保持原来的状态,而父类的私有成员还是私有的,不能被这个子类的子类访问。例如:
class Person {
public:
string _name; // 姓名
protected:
string sex;//性别
private:
int id;//身份证号
};
class Student : public Person //公有继承父类
{};
监视窗口如下:
2.2 保护继承
保护继承的特点是父类的所有公有成员与保护成员作为子类的成员时,都成为子类的保护成员,父类的私有成员仍是私有的。
2.3 私有继承
保护继承的特点是父类的所有公有成员与保护成员作为子类的成员时,都称为子类的私有成员,且不能被这个子类的子类所访问。
private能够对外部和子类保密,且出了承运所在的类本身可以访问外,别的不能直接访问;protected能对外保密,但允许子类直接访问这些成员。
public、protected、private这三种继承关系下父类成员的在子类的访问关系变化如下所示:
继承方式 | 父类的public 成员 | 父类的protected成员 | 父类的private成员 | 继承引起的访问控制关系变化 |
公有继承 | 仍为public成员 | 仍为protected成员 | 不可见 | 父类的非私有成员在子类的访问属性都不变 |
保护继承 | 为protected成员 | 为protected成员 | 不可见 | 父类的非私有成员都称为子类的保护成员 |
私有继承 | 为private成员 | 为private成员 | 不可见 | 父类的非私有成员都称为子类的私有成员 |
总结:
1.父类的私有成员在子类中不能被访问。若父类成员不想被父类函数访问,但需要在子类能访问,则定义为保护成员
2.不管是哪种继承方式,子类都可以继承父类的公有成员和私有成员,但是父类的私有成员存在但在子类中不能访问。
3.使用关键字class默认的继承方式是private,使用struct时默认的继承方式是public
4.实际运用中一般使用public继承,极少情景使用protected/private继承
3.继承的赋值兼容规则(public)
(1)子类对象可以赋值给父类对象(切割/切片)
在公有继承时会导致子类中有结构的嵌套父类的信息,在子类给父类赋值时,可能会发生类似降级的事情,使得赋值成功。
(2)父类对象不能赋值给之类对象
父类对象给子类对象赋值会使得父类对象的成员函数并不能完全覆盖子类对象的成员变量,编译器不允许这种不安全的操作。
(3)父类的指针/引用可以指向子类对象
这种做法也可行,但是经由该指针只能调用父类所定义的函数,即父类只能访问父类的内容,子类的内容会被保留但不能访问。
(4)子类的指针/引用不能指向父类对象(可通过强转完成)
若没有强转则这种做法因为安全问题不被允许;强转之后会容易发生越界问题,所以一般不提倡。
4.继承的分类
4.1 单继承
(1)定义:一个子类只有一个直接父类称为单继承
(2)举例:
class A//父类
{
public:
int a;
private:
int b;
};
class B :public A//子类
{
public:
char c;
};
int main()
{
A a;
B b;
std::cout << sizeof(a) << std::endl;//8
std::cout << sizeof(b) << std::endl;//12
return 0;
}
监视窗口为:
(3)上述单继承指向图
4.2 多继承
(1)定义:一个子类有两个或两个以上的直接父类称为多继承
(2)举例
class A
{
public:
int a;
private:
int b;
};
class B
{
public:
int c;
private:
char d;
};
class C :public A,public B
{
public:
char e;
};
int main()
{
A a;
B b;
C c;
std::cout << sizeof(a) << std::endl;//8
std::cout << sizeof(b) << std::endl;//8
std::cout << sizeof(c) << std::endl;//20
return 0;
}
监视窗口为:
(3)上述多继承指向图
4.3 菱形继承
(1)上述为简单的多继承,当多继承更为复杂一点时,就容易造成菱形继承。
有A、B、C、D四个类,B和C都单继承A,D又多继承B、C,从而就形成了菱形继承。
(2)举例
class A
{
public:
int a;
private:
int b;
};
class B:public A
{
public:
int c;
private:
char d;
};
class C :public A
{
public:
char e;
};
class D :public B, public C
{
public:
int f;
};
int main()
{
A a;
B b;
C c;
D d;
std::cout << sizeof(a) << std::endl;//8
std::cout << sizeof(b) << std::endl;//16
std::cout << sizeof(c) << std::endl;//12
std::cout << sizeof(d) << std::endl;//28
return 0;
}
(3)上述菱形继承图
(4)菱形继承产生的问题
- 数据冗余:因为B类和C类都继承了A类,所以B类和C类里边都有A的成员变量a、b;而D又同时继承了B类和C类,所以D类里边有两份A的成员变量,但实际上只需要1份就好了,所以造成了数据冗余。
- 二异性:如果对D定义一个对象d,因为D类里边有两份A的成员变量 ,所以通过d访问a或b的时候不确定要访问哪一个,编译器会报错(表名对a的访问不明确)。
(5)菱形问题的解决办法
- 加域限定符:因为d对象里边有两组A的成员变量,所以在要访问时,在成员变量前加域限定符,表名要访问那个类域里的成员变量即可(但是此方法不能解决数据冗余问题)
- 虚继承
a.概念:虚继承就是在两个单继承时加virtual关键字
class A
{
public:
int a;
private:
int b;
};
class B :virtual public A
{
public:
int c;
private:
char d;
};
class C :virtual public A
{
public:
char e;
};
class D :public C, public B
{
public:
int f;
};
int main()
{
A a;
B b;
C c;
D d;
d.a = 1;
std::cout << sizeof(a) << std::endl;//8
std::cout << sizeof(b) << std::endl;//16
std::cout << sizeof(c) << std::endl;//12
std::cout << sizeof(d) << std::endl;//32
return 0;
}
b.监视窗口如下:
监视窗口此时不太准确,但在内存中可准确观察出,此处用监视窗口是方便查看。
监视窗口仍有两份A的成员变量。但是可看到,修改成员变量的值两个都会改变,说明这两份成员变量实际上是同一份。