C++类的的继承和多态机制
面向对象编程的关键思想是多态性。多态性字面上可以理解为”许多形态”,之所以称通过继承而相关联的类型为多态类型,是因为在许多情况下可以互换地使用派生类型或基类型的”许多形态”。在C++中,多态性仅用于通过继承而相关联的类型的引用或指针。
继承:
通过继承我们能够定义这样的类,它们对类型之间的关系建模,共享公共的东西,仅仅特化本质上不同的东西。派生类能够继承基类定义的成员,派生类可以无须改变而使用那些与派生类型具体特征不相关的操作,派生类可以重定义那些与派生类相关的成员函数,将函数特化,考虑派生类型的特性。最后,除了从基类继承的成员外,派生类还可以定义更多的成员。
基类也有定义其接口和实现的数据和函数成员。除了我们前面用过的private和public修饰符外,在基类中还可以使用protected访问修饰符(表示其成员可以被派生类对象访问但不能被该类型的普通用户访问)。除此以外,成员函数的前面还可以带有一个保留字virtual, 这个保留字的主要目的是启用动态绑定,表示此函数是一个虚函数。成员函数默认为非虚函数,对于非虚函数的调用在编译时确定。为了指明函数为虚函数其返回类型前面加上保留字virtual。除了构造函数外,任意非static成员函数都可以是虚函数。保留字只在类内部的成员函数声明中出现,不能用在类定义体外部出现的函数定义上。
派生类的定义使用类派生列表来指定基类,类派生列表指定了一个或多个基类,具有如下形式: class 类名 : access-label 基类
这里的access-label是访问级别标号,它可以是如下几种:
公用继承(public inheritance):基类成员保持自己的访问级别,原来是什么级别现在还是什么级别。
受保护继承(protected inheritance): 基类的public和protected成员在派生类中为protected成员。
私有继承(private inheritance): 基类的所有成员在派生类中为private 成员。
派生类继承基类的成员并可以定义自己的附加成员。一般而言,派生类只重定义那些与基类不同或扩展基类行为的方面。
尽管不是必须这样做,派生类一般会重定义所继承的虚函数,如果派生类没有重定义某个虚函数,则使用基类中定义的版本。派生必须对想要重定义的每个继承成员进行声明。派生类中虚函数的声明必须与基类中的定义方式完全匹配,但有一个例外:返回对基类型的引用(或指针)的虚函数。派生类中的虚函数可以返回基类函数所返回类型的派生类的引用(或指针)。
引用和指针的静态类型与动态类型可以不同,这是c++用以支持多态性的基石。通过基类引用或指针调用基类中定义的函数时,我们并不知道执行函数的对象的确切类型,执行函数的对象可能是基类类型的,也可能是派生类型的。如果调用非虚函数,则无论实际对象是什么类型,都执行基类类型所定义的函数。如果调用虚函数,则直到运行时才能确定调用哪个函数,运行的虚函数是引用所绑定的或指针所指向的对象所属类型定义的版本.
而不是由指针或引用本身确定的。
非虚函数总是在编译时根据调用该函数的对象、引用或指针的类型而确定,即使运行时传入的是一个子类的对象,它也会执行该对象基对象中的此方法。
结构默认的继承级别是public,而class默认的继承级别则是private, 如果基类中定义了一个static成员,则整个继承层次中只有一个这样的成员,无论从基类派生出多少个派生类,每个static成员只有一个实例。Static成员遵循常规访问控制:如果成员在基类中为private,则派生类不能访问它,假定可以访问成员,则即可以通过基类访问static成员,也可以通过派生类访问static成员。
复制控制和继承:
像其它任意类一样,派生类也可以使用合成复制控制成员。合成操作对对象的基类部分连同派生部分的成员一起进行复制、赋值和撤销,使用基类的复制构造函数、赋值操作符或析构函数对基类部分进行复制、赋值或撤销。类是否需要定义复制控制成员完全取决于类自身的直接成员,基类可以定义自己的复制控制而派生类使用合成版本,反之亦然。
只包含类类型或内置类型数据成员、不含指针的类一般可以使用合成操作,复制、赋值或撤销这样的成员不需要特殊控制。具有指针成员的类一般需要定义自己的复制控制来管理这些成员。如果派生类定义了自己的复制构造函数,该复制构造函数一般应显式使用基类构造函数初始化对象的基类部分:
class Base { }
class Derived: public Base {
public:
Derived(const Derived& d):Base(d){ }
}
初始化函数Base(d)将派生类对象d转换成它的基类部分的引用,并调用基类复制构造函数,如果省略基类初始化函数,则运行基类默认构造函数初始化对象的基类部分,这样其中的有些成员将不能从子类得到用户给定的初始值。
赋值操作符通常与复制构造函数类似,如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显式赋值:
Derived &Derived::operator=(const Derived &rhs)
{
if(this!=&rhs){
Base::operator=(rhs);
}
Return *this;
}
赋值操作符必须防止自身赋值,假定左右操作数不同,则调用Base类的赋值操作符给基类部分赋值,该操作符可以由类定义,也可以是合成赋值操作符。基类操作符将释放左操作数中基类部分的值,并赋以来自rhs的新值。
派生类析构函数不负责撤销基类对象的成员,编译器总是显式调用派生类对象基类部分的析构函数,每个析构函数只负责清除自己的成员. 对象的撤销顺序与构造顺序相反,首先运行派生类的析构函数,然后按继承层次依次向上调用各基类析构函数。
纯虚函数:
在函数形参表后面写上=0以指定纯虚函数,如 double net_price(std::size_t) const = 0 ; 将函数定义为纯虚函数能够说明,该函数为后代类型提供了可以覆盖的接口,但是这个类中的版本决不会调用。它其实就是java中所说的抽象方法。
含有一个或多个虚函数的类是抽象基类,除了作为抽象基类的派生类的对象的组成部分,不能创建抽象类型的对象。
C++的多重继承和虚继承(转):
C++允许为一个派生类指定多个基类,这样的继承结构被称做多重继承。 举个例子,交通工具类可以派生出汽车和船连个子类,但拥有汽车和船共同特性水陆两用汽车就必须继承来自汽车类与船类的共同属性。当一个派生类要使用多重继承的时候,必须在派生类名和冒号之后列出所有基类的类名,并用逗好分隔。
#include <iostream>
using namespace std;
class Vehicle
{
public:
Vehicle(int weight = 0)
{
Vehicle::weight = weight;
}
void SetWeight(int weight)
{
cout<<"重新设置重量"<<endl;
Vehicle::weight = weight;
}
virtual void ShowMe() = 0; //纯虚函数
protected:
int weight;
};
class Car : public Vehicle //汽车
{
public:
Car(int weight=0,int aird=0) : Vehicle(weight)
{
Car::aird = aird;
}
void ShowMe()
{
cout<<"我是汽车!"<<endl;
}
protected:
int aird;
};
class Boat : public Vehicle //船
{
public:
Boat(int weight=0,float tonnage=0) : Vehicle(weight)
{
Boat::tonnage = tonnage;
}
void ShowMe()
{
cout<<"我是船!"<<endl;
}
protected:
float tonnage;
};
class AmphibianCar : public Car, public Boat //水陆两用汽车,多重继承的体现
{
public:
AmphibianCar(int weight,int aird,float tonnage) : Vehicle(weight),Car(weight,aird),Boat(weight,tonnage)
//多重继承要注意调用基类构造函数
{ }
void ShowMe()
{
cout<<"我是水陆两用汽车!"<<endl;
}
};
int main()
{
AmphibianCar a(4,200,1.35f);//错误
a.SetWeight(3);//错误
system("pause");
}
上面的代码从表面看,看不出有明显的语发错误,但是它是不能够通过编译的。这有是为什么呢? 这是由于多重继承带来的继承的模糊性带来的问题。 当一个派生类要使用多重继承的时候,必须在派生类名和冒号之后列出所有基类的类名,并用逗好分隔。先看如下的图示:
在图中深红色标记出来的地方正是主要问题所在,水陆两用汽车类继承了来自Car类与Boat类的属性与方法,Car类与Boat类同为AmphibianCar类的基类,在内存分配上AmphibianCar获得了来自两个类的SetWeight()成员函数,当我们调用a.SetWeight(3)的时候计算机不知道如何选择分别属于两个基类的被重复拥有了的类成员函SetWeight()。
由于这种模糊问题的存在同样也导致了AmphibianCar a(4,200,1.35f);执行失败,系统会产生Vehicle”不是基或成员的错误。
以上面的代码为例,我们要想让AmphibianCar类既获得一个Vehicle的拷贝,而且又同时共享用Car类与Boat类的数据成员与成员函数就必须通过C++所提供的虚拟继承技术来实现。我们在Car类和Boat类继承Vehicle类出,在前面加上virtual关键字就可以实现虚拟继承,使用虚拟继承后,当系统碰到多重继承的时候就会自动先加入一个Vehicle的拷贝,当再次请求一个Vehicle的拷贝的时候就会被忽略,保证继承类成员函数的唯一性。 修改后的代码如下,注意观察变化:
class Car : virtual public Vehicle//汽车,这里是虚拟继承
{
public:
Car(int weight=0,int aird=0):Vehicle(weight)
{
Car::aird = aird;
cout<<"载入Car类构造函数"<<endl;
}
void ShowMe()
{
cout<<"我是汽车!"<<endl;
}
protected:
int aird;
};
class Boat : virtual public Vehicle//船,这里是虚拟继承
{
public:
Boat(int weight=0,float tonnage=0):Vehicle(weight)
{
Boat::tonnage = tonnage;
cout<<"载入Boat类构造函数"<<endl;
}
void ShowMe()
{
cout<<"我是船!"<<endl;
}
protected:
float tonnage;
};
通过这样的修改就再也不会出现上面所说的问题了。