C++快速入门 (十三) 继承和多态

来源:互联网 发布:ie11不支持ie8的js 编辑:程序博客网 时间:2024/06/06 07:20

一,继承

(1). 继承的基本语法

继承是面向对象的基本特征之一。简单的继承示例如下:

class ExampleBase
{
private :
    int x;
public :
    ExampleBase( ) : x(0)
    {
        cout << "ExampleBase 类构造函数" << endl;
    }
    void SetX ( int n )
    {
        x = n ;
    }
    int GetNum()
    {
        return x;
    }
};
class Example : public ExampleBase
{
private :
    int y;
public :
    Example( ):y(10)
    {
        cout << "Example 类构造函数" << endl;
    }
    int GetNum()
    {
        return y;
    }
};


int _tmain (int argc, _TCHAR* argv [])
{
    Example ex;
    cout << ex.GetNum() << endl;
    return 0;
};
// return:
// ExampleBase 类构造函数
// Example 类构造函数
// Example 类函数
// 10

总结:
  • 创建派生类实例时,构造函数调用从最底层基类开始直至当前类。清理对象时顺序相反。
  • 基类的访问修饰符对派生类依然有效。如 基类中的私有成员即使是派生类中依然无法访问。
  • 派生类中只要有和基类同名的函数,即便是签名不同(参数或返回值不同),对于派生类对象,基类的同名函数将会被隐藏(和重载不同)。
你可以在派生类中通过作用域操作符调用基类的函数

Base ::GetNum();


(2). protected 修饰符

声明为 protected 的成员可以让从其派生的类访问。而在其他地方和声明为 private 一样外界无法访问(可参见下节示例)。

(3). 派生类中显式调用基类的构造函数

当基类中存在多个构造函数时,就有可能需要在派生类中调用指定的基类构造函数。方法如下:

class ExampleBase
{
protected :
    int x;
public :
    ExampleBase( ) : x(0)
    {
        cout << "ExampleBase 类无参数 构造函数" << endl;
    }
    ExampleBase(int n ) : x( n )
    {
        cout << "ExampleBase 类有参数 构造函数" << endl;
    }
    void SetX ( int n )
    {
        cout << "ExampleBase 类函数" << endl;
        x = n ;
    }
    int GetNum()
    {
        return x;
    }
};
class Example : public ExampleBase
{
private :
    int y;
public :
    Example( ):y(10),ExampleBase (15)
    {
        cout << "Example 类构造函数" << endl;
    }
    int GetNum()
    {
        cout << "Example 类函数" << endl;
        return y;
    }
    int SumXy()
    {
        return x + y;
    }
};


(4). 多重继承

C++中允许多重继承,既 一个类可以有多个基类,

class A
{
protected :
    int x;
public :
    A(): x(1)
    {
            cout << "A 类构造函数" << endl;
    }
};
class B
{
protected :
    int y;
public :
    B(): y(2)
    {
        cout << "B 类构造函数" << endl;
    }
};
class C
{
protected :
    int z;
public :
    C(): z(3)
    {
        cout << "C 类构造函数" << endl;
    }
};
class Example : public A , public B, public C
{
public :
    Example( )
    {
        cout << "Example 类构造函数" << endl;
    }
    int SumXyz()
    {
        return x + y + z;
    }
};


int _tmain (int argc, _TCHAR* argv [])
{
    Example ex;
    cout << ex.SumXyz();
};
//return:
//A 类构造函数
//B 类构造函数
//C 类构造函数
//Example 类构造函数
//6

后边还会继续讨论多重继承。

二,多态

(1). 多态的基本理念

多态就是 可以用基类对象表示其派生类对象,在调用该对象上的函数时能正确调用派生类对象的相应函数(动态绑定)的一种能力。
典型的应用场景如: 某个函数的形参为基类类型(指针或引用),当调用时可以为其传入该基类的派生类并能正确调用。很显然一般的函数覆盖是实现不了动态绑定的,于是 C++引入了虚函数来完成动态绑定。

(2). 虚函数 virtual

C++中使用关键字 virtual 声明虚函数,

class Base
{
public :
    virtual void ShowInfo()
    {
        cout << "is Base class" << endl;
    }
};
class ExampleOne : public Base
{
public :
    void ShowInfo()
    {
        cout << "is ExampleOne class" << endl;
    }
};
class ExampleTwo : public Base
{
public :
    void ShowInfo(int x)
    {
        cout << "is ExampleTwo class" << endl;
    }
};
int _tmain (int argc, _TCHAR* argv [])
{
    Base *b = new ExampleOne ;
    b->ShowInfo();
    Base *b2 = new ExampleTwo ;
    b2->ShowInfo();
}
//return:
//is ExampleOne class
//is Base class

总结:
  • 只有虚函数才具有多态(动态绑定)性。
  • 只有派生类函数和基类中的虚函数签名完全相同时才会具有多态性,
  • 用于覆盖基类虚函数的派生类函数也可以声明为虚函数。
  • 析构函数也可以为多态的,所以一般的基类析构函数应为虚函数。
  • 构造函数或析构函数中调用虚函数时,将只会调用其类型上的版本。

顺便提一句。即便是已经实现了动态绑定,也可以用如下语法显式的指定应该调用哪个类的函数

b->Base::ShowInfo();


(3). 纯虚函数

在虚函数后加 "= 0" 时,该虚函数就为纯虚函数。因为纯虚函数没有方法体自然就没法调用执行。这样的类是不完全的,所以拥有纯虚函数的类会变成了抽象类(不能被实例化的类)。

class Base
{
public :
    virtual void ShowInfo() = 0;
};

当一个类继承自抽象类并且未覆盖其纯虚函数时,该类也会变成抽象类(显而易见)。

(4). 虚继承

当一个派生类间接继承多个相同基类时,该基类会在内存中有多个副本。

class Base
{
public :
    int num;
};
class MidOne : public Base { };
class MidTwo : public Base { };
class Example : public MidOne , public MidTwo { };
int _tmain (int argc, _TCHAR* argv [])
{
    Example *ex = new Example ;
    cout << ex->num;  
    // error C2385: 对“num”的访问不明确, 因为有多个 Base的副本
}

可以通过 继承时添加 修饰符 virtual ,使内存只存储一个该基类的副本。

(5). 动态绑定原理

当类中有虚函数时(动态绑定基础),会生成一个保存该类所有虚函数(包括继承来未被覆盖的)地址的虚表(vtable),然后用一个指针(vptr)指向虚表,并将(大多数情况)该指针放在类的首地址。当将派生对象赋给基类对象时实际只是改变了基类对象的引用(指向派生类对象地址),这样就可以调用正确的虚函数,从而实现动态绑定。

class Base
{
public :
    virtual void ShowInfo()
    {
        cout << "is Base" << endl;
    }
    virtual void Say()
    {
        cout << "is Base say" << endl;
    }
};
class Example : virtual public Base
{
public :
    virtual void ShowInfo()
    {
        cout << "is Example" << endl;
    }
};

typedef void (*py)();
int _tmain (int argc, _TCHAR* argv [])
{
    Base *b = new Example ;
    void* vptr = ( void *)*(unsigned long*)b;
    unsigned char *p = (unsigned char *)vptr;
    p += sizeof( void *) * 0;
    py vtable = ( py ) (void *)*(unsigned long *)p;
    vtable();
    p += sizeof( void *) * 1;
    py vtable2 = ( py ) (void *)*(unsigned long *)p;
    vtable2();
}
//return:
// is Example
// is Base say


三,动态绑定对象切割

(1). 切割

在某种情况下即便当前基类是一个指向派生类的引用(多态),也会将其转换为真正的基类,而派生类部分将被切掉。

(2). 值类型形参

形参为值类型时,动态绑定类型将被切割,转换为纯基类类型。

class Base
{
public :
    virtual void ShowInfo()
    {
        cout << "is Base" << endl;
    }
};
class Example : virtual public Base
{
public :
    virtual void ShowInfo()
    {
        cout << "is Example" << endl;
    }
};
void Test ( Base b )
{
    b.ShowInfo();
}
int _tmain (int argc, _TCHAR* argv [])
{
    Base *ex = new Example ;
    Test(*ex);
}
//return:
//is Base


(3). 容器和数组

当将动态绑定的类型存入容器(对象而非指针),也会被切割,转换为纯基类类型。

int _tmain (int argc, _TCHAR* argv [])
{
    Base *ex = new Example ;
    Base baseArray[3] = {};
    baseArray[0] = *ex;
    baseArray[0].ShowInfo();

    Base *ex2 = new Example ;
    Vector< Base > vr;
    vr.push_back(*ex2);
    vr[0].ShowInfo();
}
//return:
// is Base
// is Base





-

<原创文章 转载请注明出处 http://blog.csdn.net/meiwm 谢谢>


作者:meiwm
出处:http://blog.csdn.net/meiwm
本文为原创,本文版权归作者所有。欢迎转载,但请务必保留此段声明,且在文章页面明显位置给出原文连接,谢谢合作。

-