多态与继承!

多态的问题:一个指针可以指向父类对象也可以指向子类对象,通过这个指针调用的重写函数时,究竟是调用父类的函数还是子类的函数。

准则:这个指针如果指向父类对象,就调用父类的函数,否则就调用子类的函数。

1函数重写

1.父类中被重写的函数依然会继承个子类,2.默认情况下子类中重写的函数讲隐藏父类中的函数,3.通过作用于分辨符::可以访问到父类中被隐藏的函数。

//函数重写
#include <iostream>
#include <cstdlib>
using namespace std;

class Parent
{
public:
    void print()
    {
        cout<<"I'm Parent..."<<endl;
    }
};
class Child : public Parent
{
public:
    void print()
    {
        cout<<"I'm Child..."<<endl;
    }
};
int main()
{
    Child child;
    child.print();
    child.Parent::print();//通过作用域分辨符访问父类
    cout << "Press the enter key to continue... !" << endl;
    cin.get();
    return EXIT_SUCCESS;
}
View Code

输出:

2.当函数重写遇到赋值兼容性问题。

上个函数中main函数改回如下:

int main()
{
    Child child;
    Parent* pp = &child;
    Parent& rp = child;
    child.print();
    pp->print();
    rp.print();
    cout << "Press the enter key to continue... !" << endl;
    cin.get();
    return EXIT_SUCCESS;
}
View Code

输出:

C++与C相同,是静态编译型语言,在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象,所以编译器认为父类指针指向的是父类对象(根据赋值兼容性原则,这个假设合理),由于程序没有运行,所以不可能知道父类指针指向的具体是父类对象还是子类对象。从程序安全的角度,编译器假设父类指针只指向父类对象,因此编译结果为调用父类的成员函数。

如果父类指针指向的是父类对象则调用父类中定义的函数

如果父类指针指向的是子类对象则调用子类中定义的重写函数。

多态:同样的调用语句有多种不同的表现形态。面向对象中的多态:根据实际的对象类型决定函数调用语句的具体调用目标。

C++中的多态支持

C++通过virtual关键字对多态进行支持 使用virtual声明的函数被重写后即可展现多态特性。让编译器不必从程序安全的角度假设父类指针只指向父类对象,而是根据实际类型指向对应对象。

//函数重写
#include <iostream>
#include <cstdlib>
using namespace std;

class Boss
{
private:
    static Boss* cInstance;
    Boss()
    {

    }
public:
    static Boss* GetInstance()
    {
        if(cInstance == NULL)
        {
            cInstance = new Boss();
        }
        return cInstance;
    }
    int fight()
    {
        return 10;
    }
};
class Master
{
public:
    virtual int eightSwordKill()
    {
        return 8;
    }
};
class NewMaster : public Master
{
public:
    virtual int eightSwordKill()
    {
        return Master::eightSwordKill()*2;
    }
};
void fieldPK(Master* master,Boss* boss)
{
    int k = master->eightSwordKill();
    int b = boss->fight();
    if(k<b)
    {
        cout<<"Mater is killed..."<<endl;
    }
    k = master->eightSwordKill();
    if(k<b)
    {

    }
    else
    {
        cout<<"BOSS is killed..."<<endl;
    }
}
Boss* Boss::cInstance = NULL;
int main()
{
    Boss* boss = Boss::GetInstance();
    cout<<"Mater vs Boss"<<endl;
    Master master;
    NewMaster newmaster;
    fieldPK(&master,boss);
    cout<<"NewMater vs Boss"<<endl;
    fieldPK(&newmaster,boss);
    cout << "Press the enter key to continue... !" << endl;
    cin.get();
    return EXIT_SUCCESS;
}
View Code

重载和重写有什么区别?什么时候是重载,什么时候是重写?

函数重载必须在同一个类中极性,子类无法重载父类的函数,父类同名函数将被覆盖。重载是在编译时期根据参数类型和个数决定调用函数(效率较高)。

函数重写必须发生于父类和子类之间,并且父类与子类中函数必须有完全相同的原形,使用virtual声明之后能够产生多态。多态是在运行期间根据具体对象的类型决定调用函数(效率较低)。

是否可以将类的每个成员函数都声明为虚函数?:出于效率考虑,没有必要将所有的成员函数都声明为虚函数。

C++中多态的实现原理:

1 当类中声明虚函数时,编译器会在类中生成一个虚函数表。

2 虚函数表是一个存储类成员函数指针的数据结构。

3 虚函数表是有编译器自动生成与维护的。

4 virtual成员函数会被编译器放入虚函数表里

5 存在虚函数时,每个对象中都有一个指向虚函数表的指针。

编译器确定func()是否虚函数,如果是,编译器在对象VPTR(虚函数表指针)所指向的虚函数表中查找func(),并调用,查找和调用在运行时完成(查找并调用过程是由编译器产生的代码,在程序运行的时候才调用,)。

                                           如果不是,编译器直接可以确定并找到被调用的成员函数。

查找的过程需要通过寻址才能确定真正应该调用的函数,而普通成员函数就是在编译时就确定了调用的函数。在效率上,虚函数的效率要低的多。

对象中的VPTR指针什么时候被初始化?

对象在创建的时候由编译器对VPTR指针进行初始化,只有当对象的构造完全结束后VPTR的指向才最终确定,父类对象的VPTR指向父类虚拟函数包,子类对象的VPTR指向子类的虚拟函数表。

结论:构造函数中调用虚拟函数无法实现多态。

 

#include <iostream>
#include <cstdlib>
using namespace std;

class Parent
{
public:
    Parent()
    {
        func();
    }
    virtual void func()
    {
         cout<<"void Parent::func()"<<endl;
    }
};
class Child:public Parent
{
public:
   void func()
    {
        cout<<"void Child::func()"<<endl;
    }
};
int main()
{
    Parent p;
    Child c;

    cout << "Press the enter key to contiune..." << endl;
    cin.get();
    return 0;
}
View Code

 

分析:Child c;先执行父类构造函数,在这之前先要初始化父类的VPTR,所以VPTR指向父类,父类的构造函数通过VPTR(指向父类)调用父类的func(),接着子类调用自己的默认构造函数,在执行构造函数之前,初始化VPTR,VPTR指向子类。

添加Child的构造函数

class Child:public Parent
{
public:
    Child()
    {
        func();
    }
   void func()
    {
        cout<<"void Child::func()"<<endl;
    }
};

则输出为:

 

纯虚函数:纯虚函数是只声明函数原型,而故意不定义函数体的虚函数。

面向对象中的抽象类:

1. 抽象类可用于表示现实世界中的抽象概念;

2. 抽象类是一种只能定义类型,而不能产生对象的类

3. 抽象类只能被继承并重写相关函数

4. 抽象类的直接特征是纯虚函数

抽象类与纯虚函数:

1.抽象类不能用于定义对象 2.抽象类只能用于定义指针和引用 3.抽象中的纯虚函数必须被子类重写

 

//Shape类为抽象类,其中的area()为纯虚函数
#include <iostream>
#include <cstdlib>
using namespace std;

class Shape
{
public:
    virtual double area() = 0;//area是纯虚函数, =0告诉编译器,这个函数故意只声明不定义
};
class Rectangle : public Shape
{
    double m_a;
    double m_b;
public:
    Rectangle(double a,double b)
    {
        m_a = a;
        m_b = b;
    }
    double area()
    {
        return m_a*m_b;
    }
};
class Circle : public Shape
{
    double m_r;
public:
    Circle(double r)
    {
        m_r = r;
    }
    double area()
    {
        return 3.14*m_r*m_r;
    }
};
double area(Shape* s)
{
    cout<<s->area()<<endl;
}
int main()
{
    Rectangle rect(2,3);
    Circle circle(4);
    Shape s;//编译不通过 
    area(&rect);
    area(&circle);
    return 0;
}
View Code

 

转载于:https://www.cnblogs.com/Rainingday/p/7565542.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值