C++ 第9章 虚函数与多态性

本文详细介绍了C++中的静态联编与动态联编、虚函数与多态性、纯虚函数及抽象类的概念,并提供了丰富的代码示例。重点讨论了如何利用基类指针调用派生类的不同实现,以及虚析构函数的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

9.1 静态联编

联编是指一个程序模块,代码之间互相关联的过程。根据联编的时机,可以分为静态联编和动态联编。

重载函数要根类型,参数进行匹配,一般在编译阶段进行静态联编。
(1)在一个类说明中重载:

class X
{
    Show(int, char);
    Show(double, char *);
};

(2)基类的成员函数在派生类中重载:

class X
{
    public:
        Show();
};
class Y:public X
{
    public:
        Show();
};

X类的成员函数Show()也是Y类的公有成员函数。C++编译时有三种区分方法:
1.根据函数参数特性区分;
2.使用作用域符“:”区分,如:
X::Show();
Y::Show();
3.根据对象区分,如:
X xx; Y yy;
xx.Show();
yy.Show();

9.2 类指针的关系

C++的动态联编依赖虚函数和基类指针实现。
基类指针和派生类指针与基类对象和派生类对象有4种可能匹配的使用方式:
1.直接用基类指针引用基类对象;
2.直接用派生类指针引用派生类对象;
3.用基类指针引用派生类对象;
4.用派生类指针引用基类对象;

9.2.1 用基类指针引用派生类对象

使用基类指针引用派生类对象:

#include <iostream>
#include <cstring>
using namespace std;
class A_class
{
    char name[20]; //私有数据成员
    public:
        void put_name(char *s)
        { strcpy_s(name, s); }
        void show_name()const
        { cout<<name<<endl;}
};

class B_class :public A_class
{
    char phone_num[20];
    public:
        void put_phone(char *num)
        {
            strcpy_s(phone_num, num);
        }
        void show_phone()const
        {
            cout<<phone_num<<endl;
        }
};

int main()
{
    A_class *A_p;
    A_class A_obj;
    B_class B_obj;
    A_p = &A_obj;
    A_p->put_name("Wang xiao hua");
    A_p->show_name();
    A_p = &B_obj;   //基类指针指向派生类对象
    A_p->put_name("Chen ming"); //调用基类成员函数
    A_p->show_name();
    B_obj.put_phone("888888888"); //调用派生类成员函数
    ((B_class *)A_p)->show_phone(); //对基类指针进行强制类型转换
}

9.2.2 用派生类指针引用基类对象

#include <iostream>
#include <cstring>

using namespace std;

class Date
{
    public:
        Date(int y, int m, int d)
        {
            SetDate(y, m, d);
        }
        void SetDate(int y, int m, int d)
        {
            year = y;
            month = m;
            day = d;
        }
        void Print()const
        {
            cout<<year<<"/"<<month<<"/"<<day<<endl;
        }
    protected:
        int year, month, day;
};

class DateTime:public Date
{
    public:
        DateTime(int y, int m, int d, int h, int mi, int s):Date(y, m , d)
        {SetTime(h, mi, s);}
        void SetTime(int h, int mi, int s)
        {
            hours = h;
            minutes = mi;
            seconds = s;
        }
        void Print()const
        {cout<<hours<<":"<<minutes<<":"<<seconds<<endl;}

    private:
        int hours, minutes, seconds;
};


int main()
{
    DateTime dt(2000, 1, 3, 12, 23, 12);
    DateTime *pdt = &dt;
    ((Date)dt).Print(); //对象类型转换,调用基类成员函数
    dt.Print();
    ((Date*)pdt)->Print(); //对象指针类型转换,调用基类成员函数
    pdt->Print();
}


//运行结果:
2000/1/3
12:23:12
2000/1/3
12:23:12

程序演示了用派生类对象和派生类指针调用基类同名成员函数的方法:
((Date)dt) //把派生类对象强制转换成基类对象
((Date*)pdt) //把派生类指针强制转换成基类指针

通过this指针

#include <iostream>
#include <cstring>

using namespace std;
class Date
{
    public:
    Date(int y, int m, int d)
    {SetDate(y,  m, d);}
    void SetDate(int y, int m, int d)
    {
        year = y;
        month = m;
        day = d;
    }
    void Print()
    {cout<<year<<"/"<<month<<"/"<<day<<endl;}
    protected:
    int year;
    int month;
    int day;
};

class DateTime:public Date
{
    public:
    DateTime(int y, int m, int d, int h, int mi, int s):Date(y,m,d)
    {SetTime(h, mi, s);}
    void SetTime(int h, int mi, int s)
    {
        hours = h;
        minutes = mi;
        seconds = s;
    }
    void Print()
    {
        ((Date*)this)->Print(); //对this指针作类型转换
        cout<<hours<<":"<<minutes<<":"<<seconds<<endl;
    }
    private:
        int hours, minutes, seconds;
};

int main()
{
    DateTime dt(2011, 8, 9, 15, 55, 26);
    dt.Print();
}

//运行结果
2011/8/9
15:55:26

9.3 虚函数和动态联编

冠以关键字virtual的成员函数称为虚函数。
实现运行时多态的关键是选要说明虚函数,而且必须用基类指针调用派生类的不同实现版本。尽管可以像调用其他成员函数那样,显式地用对象名来调用一个虚函数,但只有使用同一个基类指针访问虚函数,才称为运行时的多态。

9.3.1 虚函数和基类指针

基类指针不需经过类型转换就可以指向派生类对象,这是一个重要的事实。但是,基类批针虽然可以获取派生类对象的地址,却只能访问派生类从基类继承的成员。
演示基类指针的移动:

#include <iostream>
#include <cstring>
#include <cmath>

using namespace std;

class Base
{
    public:
        Base(char xx)
            { x = xx; }
        void who()
            { cout<<"Base class:"<<x<<endl; }
    protected:
        char x;
};

class First_d : public Base
{
    public:
        First_d(char xx, char yy):Base(xx)
            { y = yy; }
        void who()
            { cout<<"First derived class:"<<x<<","<<y<<endl; }
    protected:
        char y;
};

class Second_d:public First_d
{
    public:
        Second_d(char xx, char yy, char zz):First_d(xx, yy)
        { z = zz; }
        void who()
        {cout<<"Second derived class:"<<x<<","<<y<<","<<z<<endl;}
    protected:
        char z;
};

int main()
{
    Base B_obj('A');
    Base *p;
    First_d F_obj('T', 'O');
    Second_d S_obj('E', 'N', 'D');
    p = &B_obj;
    p->who();
    p = &F_obj;
    p->who();
    p = &S_obj;
    p->who();
    F_obj.who();
    ((Second_d *)p)->who();
}

//运行结果
Base class:A
Base class:T
Base class:E
First derived class:T,O
Second derived class:E,N,D

虚函数的应用

#include <iostream>
#include <cmath>
#include <cstring>

using namespace std;

class Base
{
    public:
        Base(char xx)
            { x = xx; }
        virtual void who()  //说明虚函数
            { cout<<"Base class:"<<x<<"\n";}
    protected:
        char x;
};

class First_d : public Base
{
    public:
        First_d( char xx, char yy):Base(xx)
        { y = yy;}
        void who()  //默认说明虚函数
        {cout<<"First derived class:"<<x<<","<<y<<endl;}
    protected:
        char y;
};

class Second_d : public First_d
{
    public:
        Second_d(char xx, char yy, char zz):First_d( xx, yy )
        { z = zz; }
        void who()  //默认说明虚函数
        { cout<<"Second dervied class:"<<x<<","<<y<<","<<z<<endl; }
    protected:
        char z;
};

int main()
{
    Base B_obj('A');
    Base *p;
    First_d F_obj('T', 'O');
    Second_d S_obj('E', 'N', 'D');
    p = &B_obj;
    p->who();
    p = &F_obj;
    p->who();
    p = &S_obj;
    p->who();
}

//运行结果
Base class:A
First derived class:T,O
Second dervied class:E,N,D

Base类中的who()函数冠以关键字virtual被说明成虚函数。之后,派手类相同界面的成员函数who也默认其具有虚特性而可以省略virtual说明符。
虚函数和基类指针的解释机制实现了程序运行时的单界面,多实现版本。
定义虚函数时注意如下4点:
1.一旦一个成员函数被说明成虚函数,则不管经历多少派生类层次,所有界面相同的重载函数都保持虚特性。因为派生类也是基类。
2.虚函数必须是类的成员函数。不能将虚函数说明为全局(非成员)函数,也不能说明为静态成员函数。因为虚函数的动态联编必须在类层次中依靠this指针实现。
3.不能将友元说明成虚函数,但虚函数可以是另一个类的友元。
4.析构函数可以是虚函数,但构造函数不能是虚函数。

9.3.2 虚函数的重载特性

重载一个虚函数时,要求函数名,返回类型,参数个数,参数类型和顺序完全相同,否则会出现如下问题:
1.如果仅仅返回类型不同,其余相同,则C++认为是错误重载,因为只靠返回类型不同的信息进行函数匹配是含糊的。
2.如果函数原型不同,仅函数名相同,则C++认为是一般函数重载,因而丢失虚特性。

虚函数重载特性:

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

class A
{
    public:
        virtual void vf1()
        { cout <<"It is virtual function vf1() of A.\n"; }
        virtual void vf2()
        { cout <<"It is virtual function vf2() of A.\n"; }
        virtual void vf3()
        { cout <<"It is virtual function vf3() of A.\n"; }
        void fun()
        { cout<<"I tis common memeber function A::fun().\n"; }
};

class B : public A
{
    public:
        void vf1()
        { cout<<"Virtual function vf1() overloading of B.\n"; }
        void vf2(int x)
        { cout<<"Virtual function vf2() is :"<<x<<endl; }
        //char vf3(){};
        void fun()
        { cout<<"It is commmon over loading member function B::fun().\n"; }
};

int main()
{
    B b;
    A *Ap = &b; //基类指针指向派生类对象
    Ap->vf1();  //调用B::vf1()
    Ap->vf2();  //调用A::vf2()
    b.vf2(5);   //调用B::vf2()
    Ap->vf3();  //调用A::vf3()
    Ap->fun();  //调用A::fun()
    b.fun();    //调用B::fun()
}

//运行结果
Virtual function vf1() overloading of B.
It is virtual function vf2() of A.
Virtual function vf2() is :5
It is virtual function vf3() of A.
I tis common memeber function A::fun().
It is commmon over loading member function B::fun().

9.3.3 虚析构函数

析构函数可以是虚的。虚析构函数用于动态建立类对象时,指引delete运算符选择正确的析构调用。
删除派生类动态对象:

#include <iostream>
using namespace std;
class A
{
    public:
        ~A()
        {cout<<"A::~A() is called.\n";}
};

class B:public A
{
    public:
    ~B()
    {cout<<"B::~B() is called.\n"; }
};

int main()
{
    A *Ap = new B;      //用基类指针建立派生类的动态对象
    B * Bp2 = new B;    //用派生类指针建立派生类动态对象
    cout<<"delete first object:\n";
    delete Ap;
    Ap = NULL;
    cout<<"delete second object:\n";
    delete Bp2;
    Bp2 = NULL;
}

//运行结果
delete first object:
A::~A() is called.
delete second object:
B::~B() is called.
A::~A() is called.

用虚析构函数删除派生类动态对象:

#include <iostream>
using namespace std;

class A
{
    public:
        virtual ~A()   //虚析构函数
        { cout<<"A::~A() is called.\n";}
};

class B:public A
{
    public :
        ~B()
        {cout<<"B::~B() is called.\n"; }
};

int main()
{
    A *Ap = new B;  //用基类指针建立派生类的动态对象
    B *Bp2 = new B; //用派生类指针建立派生类动态对象
    cout <<"delete first object:\n";
    delete Ap;
    cout <<"delete second object:\n";
    delete Bp2;
}


//运行结果
delete first object:
B::~B() is called.
A::~A() is called.
delete second object:
B::~B() is called.
A::~A() is called.

9.4 纯虚函数和抽象类

一个具有纯虚函数的基类称为抽象类。抽象类机制支持一般概念的表示,也可以用于定义接口。
9.4.1 纯虚函数
纯虚函数是在基类中说明的虚函数,它在该基类中没有实现定义,要求所有派生类都必须定义自已的版本。
纯虚函数的说明形式如下:
virtual 类型 函数名(参数表)= 0;
其中,”函数名”是纯虚函数名,该函数赋值为0,表示没有实现定义。虚函数的实现在它的派生类中定义。

//figure
#include <iostream>
using namespace std;

class Figure
{
    protected:
        double x, y;
    public:
        void set_dim(double i, double j=0)
        {
            x = i;
            y = j;
        }
        virtual void show_area()const = 0;
};

class Triangle:public Figure
{
    public :
        void show_area()const
        {
            cout<<"Tirangle with high "<<x<<"and base "<<y;
            cout<<"has an area or"<<x*0.5*y<<endl;
        }
};

class Square:public Figure
{
    public:
        void show_area()const
        {
            cout<<"Square with dimension "<<x<<"*"<<y;
            cout<<" has an area of "<<x*y<<"\n";
        }
};

class Circle:public Figure
{
    public:
        void show_area()const
        {
            cout<<"Circle with radius "<<x;
            cout<<" has an area of "<<3.14*x*x<<endl;
        }
};


//main.cpp
#include "figure.h"

int main()
{
    Triangle t;
    Square s;
    Circle c;
    t.set_dim(10.0, 6.0);
    t.show_area();
    s.set_dim(10.0, 6.0);
    s.show_area();
    c.set_dim(8.0);
    c.show_area();
}

//运行结果
Tirangle with high 10and base 6has an area or30
Square with dimension 10*6 has an area of 60
Circle with radius 8 has an area of 200.96

9.4.2 抽象类

抽象类至少有一个纯虚函数。如果抽象类的一个派生类没有为继承的纯虚函数定义实现版本,那么,它仍然是抽象类。
对抽象类的使用,C++有以下限制:
1.抽象类只能用作其他类的基类;
2.抽象类不能建立对象;
3.抽象类不能用作参数类型,函数返回类型或显示类型转换。但可以说明抽象类的指针和引用;
如,对Figure抽象类的使用:

Figure x;   //错误,抽象类不能建立对象
Figure *p;   //正确,可以说明抽象类的指针
Figure f();  //错误,抽象类不能做为返回类型
void g(Figure); //错误,抽象类不能做为参数类型
Figure &h(Figure &); //正确,可以说明抽象类的引用

使用抽象类指针:

//figure.h
#include <iostream>
using namespace std;

class Figure
{
    protected:
        double x, y;
    public:
        void set_dim(double i, double j=0)
        {
            x = i;
            y = j;
        }
        virtual void show_area()const = 0;
};

class Triangle:public Figure
{
    public :
        void show_area()const
        {
            cout<<"Tirangle with high "<<x<<"and base "<<y;
            cout<<"has an area or"<<x*0.5*y<<endl;
        }
};

class Square:public Figure
{
    public:
        void show_area()const
        {
            cout<<"Square with dimension "<<x<<"*"<<y;
            cout<<" has an area of "<<x*y<<"\n";
        }
};

class Circle:public Figure
{
    public:
        void show_area()const
        {
            cout<<"Circle with radius "<<x;
            cout<<" has an area of "<<3.14*x*x<<endl;
        }
};


//main.cpp
#include "figure.h"

int main()
{
    Figure *p;  //说明抽象类指针
    Triangle t;
    Square s;
    Circle c;
    p=&t;
    p->set_dim(11.0, 12.0); //Triangle::set_dim
    p->show_area();
    p=&s;
    p->set_dim(8.0, 9.5); //Square::set_dim
    p->show_area();
    p=&c;
    p->set_dim(6.0);    //Circle::set_dim
    p->show_area();
}

//运行结果
Tirangle with high 11and base 12has an area or66
Square with dimension 8*9.5 has an area of 76
Circle with radius 6 has an area of 113.04

使用抽象类引用,以不同数制形式输出正整数:

#include <iostream>

using namespace std;

class Number
{
    public:
        Number(int i)
        { val = i;}
        virtual void Show()const = 0;
    protected:
        int val;
};

class Hex_type:public Number
{
    public:
        Hex_type(int i):Number(i){}
        void Show()const
        { cout<<"Hexadecimal:"<<hex<<val<<endl;}
};

class Dec_type:public Number
{
    public:
        Dec_type(int i):Number(i){}
        void Show()const
        { cout<<"Decimal:"<<dec<<val<<endl;}
};

class Oct_type:public Number
{
    public:
        Oct_type(int i):Number(i){}
        void Show()const
        { cout<<"Octal:"<<oct<<val<<endl;}
};

void fun(Number & n)    //普通函数定义抽象类的引用参数
{
    n.Show();
}

int main()
{
    Dec_type n1(50);
    fun(n1);
    Hex_type n2(60);
    fun(n2);
    Oct_type n3(80);
    fun(n3);
}

//运行结果
Decimal:50
Hexadecimal:3c
Octal:120

9.5 虚函数和多态性的应用

9.5.1 一个实例

9.5.2 异质链表
为了能够将一些不同类对象统一组织在一个数据结构中,可以定义抽象类指针数组或链表。由于这种表中具有不同类类型元素(它们都有共同的基类),所以称为”异质链表”。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SongYuLong的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值