C++开发中级


1100 浏览 5 years, 10 months

1.1 扩展类

版权声明: 转载请注明出处 http://www.codingsoho.com/

扩展类

当使用C++编写类定义的时候,可以告诉编译器您的类继承自(或者扩展了)一个已有的类。通过这种方式,您的类自动将包含原始类的数据成员和方法:原始类称为父类(parent class)或者超类(Superclass)。扩展己有类可以让您的类(现在叫做子类或者派生类)只描述与父类不同的那部分内容。

在C++中,为了扩展一个类,可在定义类的时候指定将要扩展的类。为了说明继承的语法,此处使用了两个名为Super以及Sub的类。不要担心——后面有许多有趣的示例。首先考虑Super类的定义:

class Super
{
    public:
        Super();
        void someMethod();
    protected:
        int mProtectedInt;
    private:
        int mPrivateInt;
};

如果想要构建一个名为Sub并且从Super继承的新类,应该使用下面的语法告诉编译器以下信息:Sub派生自Super:

class Sub : public Super
{
    public:
        Sub();
        void someOtherMethod();
};

Sub本身就是一个完整的类,这个类只是刚好共享了Super类的特性而已。现在不要担心public关键字——本章后面将解释其含义。下图显示了Sub与Super间的简单关系。您可以像声明其他对象那样声明Sub类型的对象,甚至可以定义Sub的子类作为第三个类,从而形成类的链条,如下图所示。
Sub不一定是Super唯一的子类。其他类也可以是Super的子类,这些类是Sub的同级类(sibling).如下图所示。

1.客户对于继承的看法

对于客户或者代码的其他部分而言,Sub类型的对象仍然是一个Super类型的对象,因为Sub从Super继承。这意味着Super的所有public方法以及数据成员,以及Sub的所有public方法以及数据成员都是可以使用的。

在调用某个方法时,使用子类的代码不需要知道是继承链中的哪个类定义了这个方法。例如,下面的代码的调用了Sub对象的两个方法,而其中一个方法是在Super类中定义的:

Sub mySub;
mySub.someMethod();
mySub.someOtherMethod();

要知道继承的运行方式是单向的,这一点很重要。Sub类与Super类具有明确的关系,但是Super类并不知道与Sub类有关的任何信息。这意味着Super类型的对象不支持Sub的public()方法以及数据成员,因为Super不是Sub。

下面的代码将无法编译,因为Super类不包含名为someOtherMethod()的public方法:

Super mySupper;
mySupper.someOtherMethod(); //BUG, supper doesn't have a someOtherMethod

从其他代码的观点来看,一个对象既属于定义它的类,又属于所有超类。
指向某个对象的指针或者引用可以指向声明类的对象,也可以指向其任意子类的对象。本章后面将详细介绍这一灵活的主题。此时需要理解的概念是,指向Super的指针可以指向Sub对象,对于引用也是如此。客户仍然只能访问Super中的方法和数据成员,但是通过这种机制,任何操作Super的代码都可以操作Sub。

例如,下面的代码可以正常编译并运行,尽管看上去好像类型并不匹配:

Super* superPointer = new Sub(); //create sub, store it in super pointer

然而,您不能通过Super指针调用Sub类的方法。下面的代码无法运行:

superPointer->someOtherMethod();

编译器会报错,因为尽管对象是Sub类型,并且定义了someOtherMethod()方法,但编译器只是将它看作Super类型,而Super类型没有定义someOtherMethod()方法。这个问题在稍后讨论虚方法的时候再处理。

2.从子类的角度分析继承

对于子类自身而言,其编写方式或行为并没有改变。您仍然可以在子类中定义方法以及数据成员,就像这是一个普通的类。前面Sub类的定义中声明了一个名为someOtherMethod()的方法,因此Sub类增加了一个额外的方法从而扩展了Super类。

子类可以访问在超类中声明的public, protected方法以及数据成员,就好像这些方法以及数据成员是子类自己的,因为从技术上讲,它们属于子类。例如,Sub中someOtherMethod()的实现可以使用数据成员mProtectedlnt,而这个数据成员在Super中声明。下面的代码显示了这一实现,访问超类的数据成员以及方法与访问子类中的数据成员以及方法并无不同之处。

void Sub::someOtherMethod()
{
    cout << "I can access superclass data member mProntectInt." << endl;
    cout << "Its value is " << mProtectedInt << endl;
}

前一章中当介绍访问说明符(public, private以及protected)时,private以及protected的区别可能令人感到迷惑。现在您理解了子类,这一区别就变得更加清晰了。如果类将数据成员以及方法声明为protected,子类就可以访问它们; 如果声明为private,子类就不能访问。

下面的someOtherMethod()实现将无法编译,因为子类试图访问超类的private数据成员:

void Sub::someOtherMethod()
{
    cout << "I can access superclass data member mProntectInt." << endl;
    cout << "Its value is " << mProtectedInt << endl;
    cout << "The value of mPrivateInt is " << mPrivateInt << endl; //BUG
}

private访问说明符让您可以控制子类与超类的交互方式。实际上,大多数数据成员都会被声明为protected,大多数方法或者是public,或者是protected。原因是多数情况下,您或者您的同事可能会扩展类,因此不想将方法或者成员声明为private从而将潜在的利用拒之门外。有时,private说明符可以用来阻止子类访问具有潜在风险的方法。
在编写外部或者未知参与方将会扩展的类也可以使用private,因为这样做可禁止访问从而预防误用。例如,在超类中可以声明private数据成员,从而阻止任何代码(包括子类)直接访问这些数据成员。如果只想让子类改变这些值,可以提供protected setter以及getter()方法。
从子类的观点来看,超类所有public以及protected数据成员以及方法都是可用的。

3.禁用继承(仅限C++11)

C++11允许将类标记为final,这意味着继承这个类会导致编译错误。将类标记为final的方法是直接在类名称的后面使用final关键字。例如,下面的Super类被标记为final:

class Super final
{
    // omit 
}

下面的Sub类试图从Super类继承,但是这样做会导致编译错误,因为Super被标记为final 。

class Sub : Super
{
    // omit 
}

参考代码 Sub\SubInheritExtend.cpp