美文网首页
多态与虚函数

多态与虚函数

作者: 薛落花随泪绽放 | 来源:发表于2022-09-30 17:01 被阅读0次

多态的基本概念

实现了多态机制的程序,可以使用同一个名字完成不同的功能。

多态分为编译时多态和运行时多态。

多态

静态多态在编译期间就可以确定函数的调用地址,并产生代码。

函数调用与代码入口地址的绑定需要在运行时刻才能确定,称为动态联编或动态绑定。

在类之间满足赋值兼容的前提下,实现动态绑定必须满足以下两个条件

  • 必须声明虚函数
  • 通过基类类型的引用或者指针调用虚函数。

虚函数

所谓虚函数,就是在函数声明时前面加了virtual关键字的成员函数。
声明虚函数成员的格式

virtual 函数返回值类型 函数名(形参表);

通过基类指针实现多态

程序6-1 通过基类指针实现多态示例

#include <iostream>
using namespace std;

class A
{
    public:
        virtual void Print()
        {
            cout<<"A::Print"<<endl;
        }
};

class B:public A
{
    public:
        virtual void Print()
        {
            cout<<"B::Print"<<endl;
        }
};

class D:public A
{
    public:
        virtual void Print()
        {
            cout<<"D::Print"<<endl;
        }
};

class E:public B
{
    public:
        virtual void Print()
        {
            cout<<"E::Print"<<endl;
        }
};

int main()
{
    A a; B b; D d; E e;
    A *pa = &a;             //基类指针pa指向基类对象a
    B *pb = &b;             //派生类指针pb指向派生类对象B
    pa->Print();        //多态,目前指向基类对象,调用a.Print(),输出A::Print
    pa = pb;            //派生类指针赋给基类指针,pa指向派生类对象b
    pa->Print();        //多态,目前指向派生类对象,调用b.Print(),输出B::Print
    pa = &d;            //基类指针pa指向派生类对象d
    pa->Print();        //多态,目前指向派生类对象,调用d.Print(),输出D::Print
    pa = &e;            //基类指针pa指向派生类对象e
    pa->Print();        //多态,目前指向派生类对象,调用e.Print(),输出E::Print
    return 0; 
}

A::Print
B::Print
D::Print
E::Print

程序6-2 用基类指针访问基类对象及派生类对象

#include <iostream>
#include <string>
using namespace std;
class A
{
    public:
        void put_name(string s)
        {
            name = s;
        }
        virtual void print_name() const
        {
            cout<<"A::"<<name<<"\n";
        }
        string name;
};

class B:public A
{
    public:
        void put_name(string s)
        {
            put_name(s);
        }
        virtual void print_name() const
        {
            cout<<"B::"<<name<<","<<A::name<<"\n"; 
        }
        void put_phone(string num)
        {
            phone_num = num;
        }
        void print_phone() const
        {
            cout<<phone_num<<"\n";
        }
        string phone_num;           //派生类中的成员变量 
};

int main()
{
    A *A_p;
    A A_obj;
    B B_obj;
    A_p = &A_obj;       //基类指针指向基类对象
    A_p->put_name("多态示例_名字");       //赋给基类对象A_obj
    cout<<"A_p->print_name的输出内容:\t";
    A_p->print_name();          //使用指针输出,A_obj中的值
    cout<<"A_obj.print_name()的输出内容:\t";
    A_obj.print_name(); //使用对象输出
    
    A_p = &B_obj;       //基类指针指向派生类对象 
    A_p->put_name("另一个名字");     //多态,赋给派生类对象B_obj 
    cout<<"A_p->print_name()的输出内容:\t";
    A_p->print_name();      //多态,使用指针输出,B_obj中的值,继承的name的值 
    cout<<"B_obj.print_name()的输出内容:\t";
    B_obj.print_name();     //使用对象输出,B_obj中的值,继承的name值 
    B_obj.put_phone("电话号码999"); //派生类对象赋值 
    
    cout<<"((B *)A_p)->print_phone()的输出内容:\t";
    ((B*)A_p)->print_phone();   //强制转换基类指针,输出的是派生类对象中的值 
    cout<<"B_obj.print_phone()的输出内容:\t\t";
    B_obj.print_phone();    //输出的是派生类对象中的值 
    return 0; 
}

A_p->print_name的输出内容:     A::多态示例_名字
A_obj.print_name()的输出内容:  A::多态示例_名字
A_p->print_name()的输出内容:   B::另一个名字,另一个名字
B_obj.print_name()的输出内容:  B::另一个名字,另一个名字
((B *)A_p)->print_phone()的输出内容:   电话号码999
B_obj.print_phone()的输出内容:         电话号码999

通过基类引用实现多态

通过基类指针调用虚函数时可以实现多态,通过基类的引用调用虚函数的语句也是多态的。

程序6-3 基类引用实现多态

#include <iostream>
using namespace std;

class A
{
    public:
        virtual void Print()
        {
            cout<<"A::Print"<<endl;
        }
};

class B:public A
{
    public:
        virtual void Print()
        {
            cout<<"B::Print"<<endl;
        }
};

void PrintInfo(A &r)
{
    r.Print();      //多态,使用基类引用调用哪个Print()取决于r引用了哪个类的对象 
}

int main()
{
    A a;
    B b;
    PrintInfo(a);
    PrintInfo(b);
    return 0;
}

A::Print
B::Print

*多态的实现原理

多态的关键在于通过基类指针或引用调用一个虚函数时,编译阶段不能确定到底调用的时基类还是派生类的函数,运行时才能确定。

程序6-4 多态机制下对象存储空间的大小

#include <iostream>
using namespace std;

class A
{
    public:
        int i;
        virtual void func(){}
        virtual void func2(){}
};

class B:public A
{
    int j;
    void func(){}
};

int main()
{
    cout<<sizeof(A)<<","<<sizeof(B);
    return 0;
}

16,16

多态实例

在面向对象的程序设计中,使用多态能够增强程序的可扩充性。

使用多态也能起到精简代码的作用。

程序6-5 使用多态出来图形示例

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

class CShape        //基类:图形类 
{
    protected:
        double acreage;     //图形的面积,子类可以访问
    public:
        CShape()
        {
//          cout<<"基类构造函数"<<endl;   
        };
        virtual ~CShape()
        {
//          cout<<"基类析构函数"<<endl;   
        };
        virtual double CalAcr()         //计算面积,虚函数 
        {
            return 0;
        };
        virtual void setAcreage(double acre){};     //设置面积值,虚函数
        virtual void PrintInfo(){}; //显示信息,虚函数
};

class CRectangle:public CShape      //派生类:矩形类 
{
    double width,high;      //矩形的宽度和高度
    public:
        CRectangle(double w,double h)
        {
//          cout<<"矩形带参构造函数"<<endl; 
            width = w;
            high = h;
        };
        CRectangle()
        {
//          cout<<"矩形无参构造函数"<<endl;
            width = 0;
            high = 0;
        };
        ~CRectangle()
        {
//          cout<<"矩形析构函数"<<endl;
        };
        virtual double CalAcr();    //继承的函数,虚函数
        virtual void PrintInfo();   //继承的函数,虚函数
        virtual void setAcreage(double);    //继承的函数,虚函数 
};

class CCircle:public CShape     //派生类:圆形类 
{
    double radius;      //半径,私有的
    public:
         CCircle(double r)
         {
//          cout<<"圆形带参构造函数"<<endl;
            radius = r;
         };
         CCircle()
         {
//          cout<<"圆形无参构造函数"<<endl;
            radius = 0;
        };
         ~CCircle()
         {
//          cout<<"圆形析构函数"<<endl;
        };
        virtual double CalAcr();    //继承的函数,虚函数
        virtual void PrintInfo();   //继承的函数,虚函数
        virtual void setAcreage(double);    //继承的函数,虚函数 
};

class CTriangle:public CShape       //派生类:三角形类 
{
    double a,b,c;       //三条边的长度,私有的
    public:
        CTriangle(double a,double b,double c)
        {
//          cout<<"三角形带参构造函数"<<endl;
            this->a = a;
            this->b = b;
            this->c = c;
        };
        CTriangle()
        {
//          cout<<"三角形无参构造函数"<<endl;
            a = 0;
            b = 0;
            c = 0;
        };
        ~CTriangle()
        {
//          cout<<"三角形析构函数"<<endl;
        };
        virtual double CalAcr();    //继承的函数,虚函数
        virtual void PrintInfo();   //继承的函数,虚函数
        virtual void setAcreage(double);    //继承的函数,虚函数 
};

double CRectangle::CalAcr()     //计算矩形面积 
{
    return width * high;
}

void CRectangle::PrintInfo()        //输出矩形信息 
{
    cout<<"矩形。\t宽度="<<this->width<<",高度="<<this->high<<",面积="<<this->acreage<<endl; 
}

void CRectangle::setAcreage(double acre)        //设置面积值 
{
    acreage = acre;     //访问基类保护成员 
}

double CCircle::CalAcr()        //计算圆形的面积 
{
    return 3.14*radius*radius;
}

void CCircle::PrintInfo()       //输出圆形信息 
{
    cout<<"圆。\t半径="<<this->radius<<",面积="<<this->acreage<<endl;
}

void CCircle::setAcreage(double acre)
{
    acreage = acre;
} 

double CTriangle::CalAcr()      //根据海伦公式计算三角形面积 
{
    double p = (a+b+c)/2.0;
    return sqrt(p*(p-a)*(p-b)*(p-c));
}

void CTriangle::PrintInfo()
{
    cout<<"三角形。三条边分别是:"<<this->a<<","<<this->b<<","<<this->c<<",面积="<<this->acreage<<endl;
}

void CTriangle::setAcreage(double acre)
{
    acreage = acre;
}

CShape *pShapes[100];           //用来存储各种几何形状,最对100个
int main()
{
    int i,n;
    double temp1,temp2,temp3;
    CRectangle *pr;
    CCircle *pc;
    CTriangle *pt;
    cin>>n;
    for(i=0;i<n;++i)
    {
        char c;
        cin>>c;
        switch(c)
        {
            case 'R':case 'r':      //矩形
                cin>>temp1>>temp2;
                pr = new CRectangle(temp1,temp2);
                pr->setAcreage(pr->CalAcr());
                pShapes[i] = pr;
                break;
            case 'C':case 'c':
                cin>>temp1;         //圆形
                pc = new CCircle(temp1);
                pc->setAcreage(pc->CalAcr());
                pShapes[i] = pc;
                break;
            case 'T':case 't':      //三角形
                cin>>temp1>>temp2>>temp3;
                pt = new CTriangle(temp1,temp2,temp3);
                pt = new CTriangle(temp1,temp2,temp3);
                pt->setAcreage(pt->CalAcr());
                pShapes[i]= pt;
                break; 
        }   
    }   
    if(n == 1) cout<<"共有"<<n<<"种图形,它是:"<<endl;
    else cout<<"共有"<<n<<"种图形,分别是:"<<endl;
    for(i=0;i<n;++i)
    {
        pShapes[i]->PrintInfo();
        delete pShapes[i];      //释放空间 
    }
    return 0; 
} 

3
C 6
R 7.6 8.2
T 3 4 5
共有3种图形,分别是:
圆。    半径=6,面积=113.04
矩形。  宽度=7.6,高度=8.2,面积=62.32
三角形。三条边分别是:3,4,5,面积=6

多态的使用

类的成员函数直接按可以互相调用。

在普通成员函数(静态成员函数、构造函数和析构函数除外)中调用其他虚成员函数也是允许的,并且是多态的。
程序6-6 在成员函数中调用虚函数

#include <iostream>
using namespace std;
class CBase
{
    public:
        void func1()        //不是虚函数
        {
            cout<<"CBase::func1()"<<endl;
            func2();        //在成员函数中调用虚函数
            func3();    
        }
        virtual void func2()
        {
            cout<<"CBase::func2()"<<endl;
        }
        void func3()
        {
            cout<<"CBase::func3()"<<endl;
        }
}; 

class CDerived:public CBase
{
    public:
        virtual void func2()
        {
            cout<<"CDerived::func2()"<<endl;
        }
        void func3()
        {
            cout<<"CDerived::func3()"<<endl;
        }
};

int main()
{
    CDerived d;
    d.func1();
    return 0;
}

CBase::func1()
CDerived::func2()
CBase::func3()

程序6-7 在构造函数与析构函数中调用虚函数

#include <iostream>
using namespace std;
class A
{
    public:
        virtual void hello()
        {
            cout<<"A::hello"<<endl;
        }
        virtual void bye()
        {
            cout<<"A::bye"<<endl;
        }
};

class B:public A
{
    public:
        virtual void hello()
        {
            cout<<"B::hello"<<endl;
        }
        B()
        {
            hello();
        }
        ~B()
        {
            bye();
        }
};

class C:public B
{
    public:
        virtual void hello()
        {
            cout<<"C::hello"<<endl;
        }
};

int main()
{
    C obj;
    return 0;
}

B::hello
A::bye

实现多态时,必须满足的条件是:使用基类指针或引用来调用基类中声明的虚函数。

程序6-8 多态与非多态的对比

#include <iostream>
using namespace std;

class A
{
    public:
        void func1()
        {
            cout<<"A::func1"<<endl;
        }
        virtual void func2()
        {
            cout<<"A::func2"<<endl;
        }
};

class B:public A
{
    public:
        virtual void func1()
        {
            cout<<"B::func1"<<endl;
        }
        void func2()
        {
            cout<<"B::func2"<<endl;
        }
};

class C:public B
{
    public:
        void func1()
        {
            cout<<"C::func1"<<endl;
        }
        void func2()
        {
            cout<<"C::func2"<<endl;
        }
};

int main()
{
    C obj;
    A *pa = &obj;
    B *pb = &obj;
    pa->func2();    //多态 
    pa->func1();    //不是多态 
    pb->func1();    //多态 
    return 0;
}

C::func2
A::func1
C::func1

虚析构函数

声明虚析构函数的格式

virtual ~类名();

虚析构函数没有返回值类型,没有参数。

如果一个类的析构函数是虚函数,则由它派生的所有子类的析构函数也是虚析构函数。

程序6-9 不使用虚析构函数的情况

#include <iostream>
using namespace std;
class ABase
{
    public:
        ABase()
        {
            cout<<"ABase 构造函数"<<endl;
        }
        ~ABase()
        {
            cout<<"ABase 析构函数"<<endl;
        }
};

class Derived:public ABase
{
    public:
        int w,h;
        Derived()
        {
            cout<<"Derived 构造函数"<<endl;
            w = 4;
            h = 7;
        }
        ~Derived()
        {
            cout<<"Derived 析构函数"<<endl;
        }
};

int main()
{
    ABase *p = new Derived();
    delete p;
    return 0;
}

ABase 构造函数
Derived 构造函数
ABase 析构函数

改写,将基类析构函数修改为虚析构函数。

virtual ~ABase()
        {
            cout<<"ABase 析构函数"<<endl;
        }

ABase 构造函数
Derived 构造函数
Derived 析构函数
ABase 析构函数

纯虚函数喝抽象类

纯虚函数

纯虚函数是声明在基类中的虚函数,没有具体的定义,而由各派生类根据识记需要给出各自的定义。

声明纯虚函数的格式

virtual 函数类型 函数名(参数表)  = 0;

抽象类

包含纯虚函数的类称为抽象类。

纯虚函数和函数体为空的虚函数的区别:

  • 纯虚函数没有函数体,而空的虚函数的函数体为空。
  • 纯虚函数所在的类是抽象类,不能直接进行实例化;而空的虚函数所在的类是可以实例化的。
    共同特点是:都可以派生出新的类,然后在新类中给出虚函数的实现,而且这种新的实现具有多态特征。

程序6-10 抽象类示例

#include <iostream>
using namespace std;

class A
{
    private:
        int a;
    public:
        virtual void print() = 0;
        void func1()
        {
            cout<<"A_func1"<<endl;
        }
};

class B:public A
{
    public:
        void print();
        void func1()
        {
            cout<<"B_func1"<<endl;
        }
};

void B::print()
{
    cout<<"B_print"<<endl;
}

int main()
{
//  A a;        //错误,抽象类不能实例化
//  A *p = new A;   //错误,不能创建类A的实例
//  A b[2];     //错误,不能声明抽象类的数组
    A *pa;      //正确,可以声明抽象类的指针
    A *pb = new B;
    pb->print();
    B b;
    A *pc = &b;
    pc->func1();
    return 0;
}

B_print
A_func1

虚基类

定义虚基类的格式

class 派生类名 : virtual 派生方式 基类名
{
  派生类体
};

程序6-11 虚基类

#include <iostream>
using namespace std;
class A
{
    public:
        int a;
        void showa()
        {
            cout<<"a="<<a<<endl;
        }
};

class B:virtual public A
{
    public:
        int b;
};

class C:virtual public A
{
    public:
        int c;
};

class D:public B,public C
{
    //派生类D的两个基类B、C具有共同的基类A,
    //采用了虚继承,从而使类D的对象中只包含着类A的1个实例
    public:
        int d; 
};

int main()
{
    D Dobj;
    Dobj.a = 11;
    Dobj.b = 22;
    Dobj.showa();
    cout<<"Dobj.b="<<Dobj.b<<endl;
}

a=11
Dobj.b=22

相关文章

  • 查漏补缺

    C++虚函数: 多态: 静态多态(重载)、动态多态(虚函数) 虚函数 虚函数表:编译器为每个类创建了一个虚函数表...

  • 多态与虚函数

    多态与虚函数 注意 在成员函数(静态成员、构造函数和析构函数除外)中调用同类的虚函数的语句是多态的。 在构造函数和...

  • 虚函数与多态

    题目1 题目2

  • 多态与虚函数

    多态的基本概念 实现了多态机制的程序,可以使用同一个名字完成不同的功能。 多态分为编译时多态和运行时多态。 多态 ...

  • GeekBand-C++面向对象高级编程(下)-Week2

    对象模型:虚函数表(vtbl)与虚表指针(vptr) 我们知道,C++中,可以通过虚函数来实现多态性,而虚函数是通...

  • 2020-07-06----《C++类的学习》

    函数重载:同名不同参。 C++类的特点:封装、继承、多态。 //多态与函数重载是啥关系? 虚函数:和软件架构相关 ...

  • C++虚函数注意事项以及构成多态的条件

    C++ 虚函数对于多态具有决定性的作用,有虚函数才能构成多态。 虚函数注意事项 只需要在虚函数的声明处加上 vir...

  • 深刻剖析之c++博客文章

    三大特性 封装、继承、多态 多态 C++ 虚函数表解析C++多态的实现原理 介绍了类的多态(虚函数和动态/迟绑定)...

  • c++虚函数与虚表初步

    虚指针与虚表 虚表和虚函数是为了实现动态多态的机制,由编译器实现 当一个类本身定义了虚函数,或其父类有虚函数时,编...

  • 课时63 多态基本概念

    多态与虚函数多态是一种泛型编程思想虚函数是实现这个思想的语法基础即同样的代码,实现不同的功能核心:父类的指针,调用...

网友评论

      本文标题:多态与虚函数

      本文链接:https://www.haomeiwen.com/subject/oysaortx.html