C++继承
1.继承的介绍
继承是面向对象程序设计的一个重要机制,通过继承,可以利用已有的数据类型来定义新的数据类型,实现代码复用。继承允许在保持原有类特性的基础上进行扩展,增加功能。
已存在的用来派生新类的类称为基类(或父类),由父类派生出的类称为派生类(或子类)。派生继承基类的成员,但派生类对基类成员的访问受继承方式和基类成员所处于的作用域限定符限制。
1. 派生类的定义方式
派生类的在定义时需要在名字后面加上:
+ 作用域限定符 + 父类名
class A
{
//…………
};
class B : public A
{
//…………
};
struct C
{
//…………
};
struct D : public C
{
//…………
};
2. 派生类的作用域限定符
-
派生类对基类的成员的访问权限是一个取小。也就是说继承方式的作用域限定符,和基类里的成员所处于的作用域限定符中取最小的权限由子类继承(public>protected>private)。
-
基类private的成员在派生类中无论以什么方式继承在派生类中都是不可访问的。如果即想让派生类访问某些成员,又不想在类外访问某些成员,就把这些成员放在protected里,protected限定符就是因为继承的需要才被发明的。
-
class的默认继承方式是private,而struct的默认继承方式是public,不过做好显示写出继承方式。
-
**在实际使用中一般都是public继承,很少使用protected和private继承。**因为protected和private继承的成员只能在派生类的类里使用,实际中拓展维护性不强。
3.派生类的切片赋值兼容
每一个派生类对象都可以看成是一个特殊的基类,也就是说派生类可以对基类赋值。
class A
{
//…………
};
class B : public A
{
//…………
};
int main()
{
B b;
A a = b;
A& ret = b;
A* ptr = &b;
}
基类和派生类中共有的成员变量会被赋值,而对于派生类中有但基类中没有的成员变量,编译器会将这些变量进行切割:
4. 派生类的隐藏
在继承体系中,基类和派生类都有独立的作用域。基类和派生类中允许有同名的成员,派生类成员将屏蔽基类对同名成员的访问,也叫重定义。如果是成员函数,只要函数名相同就构成隐藏。在派生类中,如果想调用基类的同名成员需要使用基类::基类成员
的方式来访问。事实上派生的隐藏应当尽量避免。
#include <iostream>
using namespace std;
class A
{
public:
void func()
{
cout << "func in A" << endl;
}
};
class B :public A
{
public:
int func() //只要函数名相同就构成隐藏
{
cout << "func in B" << endl;
return 1;
}
};
int main()
{
B b;
b.func();
b.A::func();
return 0;
}
但要注意基类的静态成员属于当前类,也属于当前类的所有派生类,即静态成员只用一份,基类和派生类共用静态成员。
5. 派生类的默认成员函数
派生类的六大默认成员函数在使用时,涉及到对基类的成员的操作都需要特殊处理。
5.1 构造函数和拷贝构造函数
使用派生类的构造函数在初始化列表时,对于基类中的成员变量的初始化,需要写在基类的名字中:
class A
{
public:
A(int x, int y)
:_x(x),_y(y)
{}
int _x;
char _y;
};
class B :public A
{
public:
B(int x,char y,double z)
:A(x,y),_z(z)
{}
double _z;
};
需要注意的是,在派生类中初始化列表基类的成员时,本质是调用了基类的构造函数,所以基类需要显示写一个构造函数。
对于拷贝构造,其方法也是相同的:
class A
{
public:
int _x;
char _y;
};
class B :public A
{
public:
B(const B& b)
:A(b),_z(b._z)
{}
double _z;
};
需要注意的是,A(b)
中本质上是进行了切片赋值兼容。
5.2 析构函数
如果想在派生类的析构函数中调用基类的析构函数,需要显示写基类名::~::基类名()
来调用。但实际上我们并不会去显示调用基类的析构函数,因为派生类的析构函数会先析构派生类的成员,然后自动调用基类的成员。如果显示去调用基类的析构函数,就会导致基类成员被析构两次,可能因此引发错误。
#include <iostream>
using namespace std;
class A
{
public:
~A()
{
cout << "now ~A()" << endl;
}
};
class B :public A
{
public:
~B()
{
A::~A();
cout << "now ~B()" << endl;
}
};
int main()
{
B b;
return 0;
}
6.多基类造成的菱形继承问题
C++支持一个派生类有多个基类,但如果一个派生类的基类中,有两个以上的它的基类是同一个,就会造成菱形继承问题:
如图,D类发生了菱形继承
对于菱形继承,它会导致两个问题:
-
数据冗余:
D类中同时继承了两次A类,那么D类中就会有两份A类的成员,造成了代码和资源的冗余。
-
二义性:
D类中的两份A类成员可以使用B和C的作用域来分别赋值,这样就导致了一个D类中有两个同名的基类成员赋有不同的值,造成代码的混乱。
解决菱形继承引发的问题的方式是使用虚继承,使用方式是在继承时在继承方式前写一个virtual
,如:
class B: virtual public A
{
//……
};
需要注意的是,虚继承应当写在共有基类的派生类上,即D类发生了菱形继承,那么应当在B类和C类上虚继承A类。
在实践中可以设计多继承,但是切记要避免设计菱形继承,因为太复杂,容易出问题。