c 的一个主要目标就是促进代码重用,缩短代码开发时间。其中继承就是实现该目标的机制之一。
1. 私有继承
私有继承提供的特性与包含相同:获得实现,并不获得接口。两者都可以用来实现has-a
的关系。
私有继承使用关键字private(实际上在继承中private是默认值,因此省略访问限定符也将导致私有继承)。
使用多个基类的继承被称为多重继承,例如:
代码语言:javascript复制class A{};
class B{};
class C : private A,private B{ //多重继承
...
};
2. 保护继承
保护继承是私有继承的变体。保护继承在列出基类的时候使用关键子protected;保护继承使得基类的公有成员和保护成员都将称为派生类的保护成员,其与私有继承一样,基类的接口在派生类中都是可用的,但是在继承层次结构之外是不可用的。两者的最大区别在于派生类再派生出另外一个类的时候,使用私有继承第三代类将不能使用基类的接口,因为基类的公有方法在第二代类中变成了私有方法;但是在使用保护继承,第三代类可以使用基类的接口,因为在第二代类中基类的公有方法变成受保护的,因此在第三代类中可以使用它们。
公有继承、私有继承和保护继承的特点如下:
特征 | 公有继承 | 保护继承 | 私有继承 |
---|---|---|---|
基类的公有成员变成 | 派生类的公有成员 | 派生类的保护成员 | 派生类的私有成员 |
基类的保护成员变成 | 派生类的保护成员 | 派生类的保护成员 | 派生类的私有成员 |
基类的私有成员变成 | 只能通过基类的接口访问 | 只能通过基类接口访问 | 只能通过基类接口访问 |
能否隐式向上转换 | 是 | 是(但只能在派生类中) | 否 |
(注:这里的隐式向上转换只意味着无需进行显式类型转换,就可以将基类指针或引用指向派生类对象)
3. 使用using重新定义访问权限
在使用私有派生和保护派生的时候,基类的公有成员函数将成为私有成员和保护成员。如果希望基类的方法能够在派生类外面可用的话,首先我们能想到的是在派生中定义一个公有方法,在该方法中调用基类的方法,进而实现该效果。另外一种方法就是使用using重新定义访问权限。
代码语言:javascript复制class Base{
...
public:
double min();
double max();
...
};
class A:private Base{
...
public:
using Base::min; //using只使用成员名,不需要圆括号、函数特征标和返回类型
using Base::max;
...
};
//调用
A a;
double min = a.min();
double max = a.max();
4. 多重继承
多重继承描述的是有多个直接基类的类,与单继承一样,公有多重继承表示的都是is-a的关系。而私有多重继承和保护多重继承表示的是has-a的关系。
下面我们看一个例子:
代码语言:javascript复制class Base{...};
class BaseA : public Base{...};
class BaseB : public Base{...};
class Abc : public BaseA,public BaseB{...};
···
Abc abc;
Base * base = &abc; //将出现二义性
针对上例,类Abc
多重继承了BaseA
和BaseB
两个类,由于BaseA
和BaseB
都继承了Base
,因此Abc
包含了两个Base
组件,因此在将派生类类对象的地址赋值给基类指针的时候,会出现二义性。abc
包含两个Base
对象,有两个地址可供选择,所以可以使用类型转换来指定对象:
Base * base1 = (BaseA*)&abc;
Base * base2 = (BaseB*)&abc;
这样虽然可以解决上例子带来的二义性,但使得使用基类指针来引用不同的对象(多态性)复杂化。而且实际应用中Abc
类也不需要包含两个Base
对象。因此c 引入了虚基类的概念来解决该情况下的多重继承。
虚基类使得从多个类(他们的基类相同)派生出来的对象只继承一个基类对象。具体的做法如下:
代码语言:javascript复制class Base{...};
class BaseA : virtual public Base{...};
class BaseB : public virtual Base{...}; //virtual与public的顺序不做要求,两种写法都是对的
class Abc : public BaseA,public BaseB{...};
这样做可以保证Abc
类对象中只会包含Base
类对象的一个副本。从本质上讲,应该是继承的BaseA
和BaseB
类对象共享了一个Base
对象。这样就可以简单的使用多态。
针对虚基类,在设计的时候需要对其类构造函数采用一种新的方法。例如:
代码语言:javascript复制class Base{
int base;
public:
Base(int ba = 0):base(ba){}};
class BaseA : virtual public Base{
int a;
public:
BaseA(int a = 0,int ba = 0): Base(ba),a(a){}
};
class BaseB : virtual public Base{
int b;
public:
BaseC(int b = 0,int ba = 0): Base(ba),b(b) {}
};
class Abc : public BaseA,public BaseB{
int abc;
public:
Abc(int a = 0,int b = 0,int ba = 0; int abc = 0):BaseA(a, ba), BaseB(b, ba), abc(abc){}// flawed
Abc(int a = 0,int b = 0,int ba = 0; int abc = 0):Base(ba), BaseA(a, ba), BaseB(b, ba), abc(abc){}
};
其中,对于:
代码语言:javascript复制Abc(int a = 0,int b = 0,int ba = 0; int abc = 0):BaseA(a, ba), BaseB(b, ba), abc(abc){}// flawed
在Base
是虚基类的时候,Abc
类通过BaseA
或BaseB
的构造函数将参数信息间接传递给Base
时将不起作用,c 在基类是虚的时候,禁止将参数信息通过中间类传递给基类。因此上述ba
的信息必不能传递给子对象Base
,然而编译器会使用Base
的默认构造函数,在构造派生类对象之前构造基类对象组件。如果不希望使用默认构造函数来构造虚基类函对象,则需要显式地调用基类构造函数。因此构造函数如下:
Abc(int a = 0,int b = 0,int ba = 0; int abc = 0):Base(ba), BaseA(a, ba), BaseB(b, ba), abc(abc){}
(注:上述代码显式地调用Base
的构造函数,是合法的,但是对于非虚基类,则是非法的。)
如果类有间接虚基类,则除非只使用该虚基类的默认构造函数,否则必须显式地调用该虚基类的某个构造函数。