三个一流的设计继承
通过继承可以派生新类从现有的类。派生类继承了基类的功能,包含方法。
財产要比自己白手起家easy一样。通过继承派生出的类通常比设计新类要easy得多。
以下是可以通过继承完毕的一些工作。
①能够在已有类的基础上加入功能。
②能够给类加入数据。
③能够改动类方法的行为。
C++有三种继承方式:公有继承、保护继承和私有继承。
一、公有继承
公有继承是最经常使用的方式。它建立一种is-a关系。即派生类对象也是一个基类对象。能够对基类对象运行的不论什么操作,也能够对派生类对象运行。
①公有继承不建立has-a关系。比如,午餐可能包括水果。可是通常午餐并非水果,因此不能从水果类派生出午餐类。
②公有继承不能建立is-like-a关系,也就是说,它不採用明喻。
人们通常说律师就是鲨鱼,可是律师并非鲨鱼,因此不能从鲨鱼派生出律师。
③公有继承不建立is-implemented-as-a(作为......来实现)关系。比如。能够用数组来实现堆栈,可是从数组类派生出堆栈类时不合适的,由于堆栈不是数组,至少数组索引不是堆栈的属性。正确做法是:通过让堆栈包括一个私有数组对象成员。来隐藏数组实现。
④公有继承不建立uses-a关系。
比如,计算机能够使用激光打印机,可是从Computer类派生出Printer类是没有意义的。只是能够使用友元函数或类来处理Printer对象和Computer对象之间的关系。
多态公有继承
实现机制:
①在派生类中又一次定义基类的方法。
②使用虚方法。
比如:
class A
{
public:
virtual ~A(){}
virtual void show()
{
cout << "A" << endl;
}
};
class B:public A
{
public:
void show()
{
cout << "B" << endl;
}
};
class C:public B
{
public:
void show()
{
cout << "C" << endl;
}
};
int main()
{
C c;
A a;
A *pA = new C;
B b;
B *pB = new C;
a.show();
b.show();
c.show();
pA->show();
pB->show();
delete pA。
delete pB;
}
1)能够看出基类中的方法show()在派生类中的行为是不同的,程序将使用对象类型来确定使用哪个版本号。比如:
a.show();//use A::show()
b.show();//use B::show()
c.show();//use C::show()
2)使用virtual之后。假设方法是通过引用或者指针而不是对象引用的,它将确定使用那一种方法。
假设没有使用keywordvirtual。程序将依据引用类型或指针类型选择方法。假设使用了virtual,程序将依据引用或指针指向的对象的类型来选择方法。比如:
假设基类中的show()没有使用virtual,那么
pA->show();//use A::show()
pB->show();//use B::show()
假设基类中的show()使用了virtual,那么
pA->show();//use C::show()
pB->show();//use C::show()
大家可能会疑惑,类B作为类C的基类。当中的方法show()没有被声明为virtual,可是为什么相同调用的是对象c中的方法。
这里我们必须清楚一点。方法在基类中被声明为虚拟后,它在派生类中将自己主动成为虚方法。
3)基类声明类一个虚拟析构函数,这样做是为了确保释放派生对象时,依照正确的顺序调用析构函数。当使用delete释放由new分配的对象时,假设析构函数不是虚拟的,则将仅仅调用相应于指针类型的析构函数,比如上面的仅仅调用类型A和类型B的析构函数,即使指针指向的是一个C对象。
假设析构函数是虚拟的。将调用相应对象类型的析构函数。因此假设指针指向的C对象,将调用C的析构函数,然后子宫调用基类的析构函数。因此。使用虚拟析构函数能够确保正确的析构函数序列被调用。
4)private和protected之间的差别仅仅有在基类派生类中才会表现出来。
派生类的成员能够直接訪问基类的保护成员。但不能直接訪问基类的私有成员。因此对于外部世界来说。保护成员的行为与私有成员类似;但对于派生类来说。保护成员的行为与公有成员相似。
5)构造函数不能是虚函数。创建派生类对象时,将调用派生类的构造函数,而不是基类的构造函数,然后。派生类的构造函数将使用基类的一个构造函数。这样的顺序不同于继承机制。
因此。派生类不继承基类的构造函数,所以将类构造函数声明为虚拟的没有什么意义。
二、私有继承
C++的一个主要目标是促进代码重用。公有继承是实现这样的目标的机制之中的一个。但并非惟一的机制。假设一个类本身是还有一个类的对象。这样的方法称为包括、组合或层次化。
还能够通过使用私有或保护继承,实现这样的包括关系。
通常,包括、私有继承和保护继承用于实现has-a关系。即新的类将包括还有一个类的对象。
使用私有继承,基类的公有成员和保护成员都将成为派生类的私有成员。这意味着基类方法将不会称为派生类对象公有接口的一部分,但能够在派生类的成员函数中使用它们。
使用公有继承。基类的公有方法将成为派生类的公有方法。
简而言之,派生类将继承基类的接口;这是is-a关系的一部分。
使用私有继承。基类的公有方法将成为派生类的私有方法。简而言之,派生类不继承基类的接口。这样的不全然继承是has-a关系的一部分。
包括将对象作为一个命名的成员对象加入到类中,而私有继承将对象作为一个未被命名的继承对象加入到类中。
包括:
#include <string>
#include <valarray>
using namespace std;
class student
{
public:
double Average() const;
private:
typedef valarray<double> ArrayDb;
string name;//contained object
ArrayDb scores;//contained object
};
私有继承:
class student:private string,private valarray<double>
{
public:
double Average() const;
const string& Name() const;
...
};
1)初始化基类组件
对于构造函数。包括将使用这种构造函数:
student(const char* str,const double* pd,int n)
:name(str),scores(pd,n){}
对于继承类,它使用类名而不是成员名来标识构造函数:
student(const char* str,const double* pd,int n)
:string(str),valarray<double>(pd,n){}
2)訪问基类的方法
包括使用对象来调用方法:
double student::Average() const
{
if (scores.size() > 0)
return scores.sum()/scores.size();
else
return 0;
}
而私有继承使得可以使用类名和作用域解析操作符来调用基类的方法:
double student::Average() const
{
if (valarray<double>::size() > 0)
return valarray<double>::sum()/valarray<double>::size();
else
return 0;
}
3)訪问基类对象
使用私有继承时。该string对象没有名称。那么,student类的代码怎样訪问内部的string对象呢?答案是使用强制类型转换。因为student类是从string类派生而来的,因此能够通过强制类型转换,将student对象转换为string对象。结果为继承而来的string对象。
const string& student::Name() const
{
return (const string&) *this;
}
上述方法返回一个引用。该应用指向用于调用该方法的student对象中的继承而来的string对象。
4)訪问基类的友元函数
用类名显示地限定函数名不适合于友元函数,这是由于友元不属于类。只是能够通过显示地转换为基类来调用正确的函数。
比如,对于以下的友元函数定义:
ostream& operator << (ostream& os,const student& stu)
{
os << "scores for " << (const string&)stu << ":\n";
...
}
注意:引用stu不会自己主动转换为string引用,根本原因在于。在私有继承中。在不进行显式类型转换的情况下,不能将指向派生类的引用或指针赋给基类引用或指针。
三、保护继承
保护继承是私有继承的变体。
保护继承在列出基类时使用keywordprotected。
使用保护继承时,基类的公有成员和保护成员都将成为派生类的保护成员。当从派生类派生(公有)出还有一个类时,私有继承和保护继承之间的主要差别便呈现出来了。
使用私有继承时,第三代将不能使用基类的接口。这是由于基类的公有方法在派生类中将变成私有方法。使用保护继承时,基类的公有方法在第三代中将变成受保护的,因这第三代派生类可以使用它们。
版权声明:本文博客原创文章。博客,未经同意,不得转载。