C++_虚函数
虚函数
虚函数的说明
虚函数允许函数调用与函数体之间的联系在运行时才建立,也就是在运行时才决定如何动作,即所谓的动态联编。
虚函数是动态联编的基础。虚函数是成员函数,而且是非static的成员函数。
说明虚函数的方法
virtual<类型说明符><函数名>(<参数表>)
其中,被关键字virtual说明的函数称为虚函数
如果某类中的一个成员函数被说明为虚函数,这就意味着该成员函数在派生类中可能有不同的实现。当使用这个成员函数操作指针或引用所标识对象时,对该成员函数调用采取动态联编方式,即在运行时进行关联或束定。
动态联编只能通过指针或引用标识对象来操作虚函数。如果采用一般类型的标识对象来操作虚函数,则将采用静态联编方式调用虚函数
派生类中对基类的虚函数进行替换时,要求派生类中说明的虚函数与基类中的被替换的虚函数之间满足如下条件:
- (1)与基类的虚函数有相同的参数个数;
- (2)其参数的类型与基类的虚函数的对应参数类型相同;
- (3)其返回值或者与基类虚函数的相同,或者都返回指针或引用,并且派生类虚函数所返回的指针或引用的基类型是基类中被替换的虚函数所返回的指针或引用的基类型的子类型。
总结动态联编的实现需要如下三个条件:
- (1)要有说明的虚函数;
- (2)调用虚函数操作的是指向对象的指针或者对象引用;或者是由成员函数调用虚函数;
- (3)子类型关系的建立
构造函数中调用虚函数时,采用静态联编即构造函数调用的虚函数是自己类中实现的虚函数,如果自己类中没有实现这个虚函数,则调用基类中的虚函数,而不是任何派生类中实现的虚函数。
虚函数的作用和定义
1. 虚函数的作用
虚函数首先是基类中的成员函数,在其前面加上virtual关键字, 并在派生类中被重载。
虚函数同派生类的结合可使C++支持运行时的多态性,实现了在基类定义派生类所拥有的通用接口,而在派生类定义具体的实现方法,即常说的“同一接口,多种方法”,它帮助程序员处理越来越复杂的程序。
虚函数的引例1
#include<iostream.h>
class A{
public:
    void show(){ cout<<"A"; }
};
class B:public A {
public:
    void show(){ cout<<"B"; }
};
main(){ 
    A a,*pc;
    B b;
    pc=&a; pc->show();
    pc=&b; pc->show();
    return 0;
}
AA
虚函数的引例2
#include<iostream.h>
class Base{
public:
    Base(int x,int y) {
        a=x;
        b=y;
    }
    void show() {
        cout<<"Base----------\n";
        cout<<a<<" "<<b<<endl;
    }
private:
    int a, b;
};
class Derived : public Base{
public:
    Derived(int x,int y,int z): Base(x, y)
    { c=z; }
    void show() {
        cout<< "Derived---------\n";
        cout<<"c="<<c<<endl;
    }
private:
    int c;
};
void main(){
    Base mb(60,60),*pc;
    Derived mc(10,20,30);
    pc=&mb;
    pc->show();
    pc=&mc;
    pc->show();
}
Base----------------
60 60
Base----------------
10 20
执行语句:pc=&mc;后,指针pc已经指向了对象mc,但它所调用的函数show(),仍然是基类对象的show(),显然这不是我们所期望的。这个程序的错误是由C++静态联编机制造成的。
对于上面的程序,静态联编机制首先将基类对象的指针pc与基类的成员函数show()连接在一起,这样,不管指针pc指向哪个对象,pc->show()调用的总是基类中的成员函数show()
为解决这一问题, C++引入了虚函数的概念。
例 虚函数的作用
#include<iostream.h>
class Base {
public:
    Base(int x,int y)
    { a=x; b=y; }
    virtual void show() //定义虚函数show()
    { cout<<"Base----------\n"; cout<<a<<" "<<b<<endl;}
private:
    int a,b;
};
class Derived : public Base {
public:
    Derived(int x,int y,int z):Base(x,y){c=z; }
    void show() //重新定义虚函数show()
    { cout<< "Derived---------\n"; cout<<c<<endl;}
private:
    int c;
};
void main(){
    Base mb(60,60),*pc;
    Derived mc(10,20,30);
    pc=&mb;
    pc->show(); //调用基类Base的show()版本
    pc=&mc;
    pc->show(); //调用派生类Derived的show()版本
}
程序运行结果如下:
Base---------
60 60
Derived--------
30
2. 虚函数的定义
在基类中定义虚函数的方法如下:
virtual 函数类型 函数名(形参表)
{
    // 函数体
}
派生类中重新定义时,其函数原型,包括返回类型、函数名、参数个数、参数类型的顺序,都必须与其基类中的原型完全相同。
例 虚函数的定义举例
#include<iostream.h>
class Grandam {
public:
    virtual void introduce_self() // 定义虚函数introduce_self()
    { cout<<"I am grandam."<<endl; }
};
class Mother:public Grandam {
public:
    void introduce_self() // 重新定义虚函数introduce_self()
    { cout<<"I am mother."<<endl;}
};
class Daughter:public Mother {
public:
    void introduce_self() // 重新定义虚函数introduce_self()
    { cout<<"I am daughter."<<endl;}
};
void main(){
    Grandam *ptr;
    Grandam g;
    Mother m;
    Daughter d;
    ptr=&g;
    ptr->introduce_self();//调用基类Grandam的introduce_self()
    ptr=&m;
    ptr->introduce_self();// 调用派生类Mother的introduce_self()
    ptr=&d;
    ptr->introduce_self(); //调用派生类
    // Daughter的introduce_self()
}
I am grandam.
I am mother.
I am daughter.
程序只在基类Grandam中显式定义了introduce_self()为虚函数。
C++规定,如果在派生类中,没有用virtual显示地给出虚函数声明,这时系统就会遵循以下的规则来判断一个成员函数是不是虚函数:
- 该函数与基类的虚函数有相同的名称。
- 该函数与基类的虚函数有相同的参数个数及相同的对应参数类型。
- 该函数与基类的虚函数有相同的返回类型或者满足赋值兼容规则的指针、引用型的返回类型。
派生类的函数满足了上述条件,就被自动确定为虚函数。
例:定义和访问虚函数
#include<iostream.h>
class base { //定义基类base
public:
    virtual void who( ) //虚函数声明
    { cout<<"this is the class of base !"<<endl ; }
};
class derive1: public base { //定义派生类derive1
public:
    void who( ) //重新定义虚函数
    { cout<<"this is the class of derive1 !"<<endl ; }
};
class derive2: public base { //定义派生类derive2
public:
    void who( ) //重新定义虚函数
    { cout<<"this is the class of derive2 !"<<endl; }
};
main( ){
    base obj, *ptr; //声明基类对象obj、指针ptr
    derive1 obj1; //声明派生类1的对象obj1
    derive2 obj2; //声明派生类2的对象obj2
    ptr = &obj; //基类指针指向基类对象
    ptr->who( ); //调用基类成员函数
    ptr = &obj1; //基类指针指向派生类1对象
    ptr->who( ); //调用派生类1成员函数
    ptr = &obj2; //基类指针指向派生类2对象
    ptr->who( ); //调用派生类2成员函数
    return 0;
}
程序运行结果为:
this is the class of base!
this is the class of derive1!
this is the class of derive2!
如果在基类中去掉关键词virtual,则程序运行结果为:
this is the class of base!
this is the class of base!
this is the class of base!
虚函数定义的几点说明
- 通过定义虚函数来使用C++提供的多态机制时,派生类应该从它的基类公有派生。赋值兼容规则成立的前提条件是派生类从其基类公有派生。
- 必须首先在基类中定义虚函数。在实际应用中,应该在类等级内需要具有动态多态性的几个层次中的最高层类内首先声明虚函数。
- 在派生类对基类中声明的虚函数进行重新定义时,关键字virtual可以写也可以不写。
- 使用对象名和点运算符的方式也可以调用虚函数,但是这种调用在编译时进行的是静态联编,它没有充分利用虚函数的特性。只有通过基类指针访问虚函数时才能获得运行时的多态性。
- 一个虚函数无论被公有继承多少次,它仍然保持其虚函数的特性。
- 虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态成员函数,因为虚函数调用要靠特定的对象来决定该激活哪个函数,而静态成员函数不受限于某个对象。但是虚函数可以在另一个类中被声明为友元函数。
- 内联函数不能是虚函数,因为内联函数是不能在运行中动态确定其位置的。即使虚函数在类的内部定义,编译时仍将其看作是非内联的。
- 构造函数不能是虚函数。因为虚函数作为运行过程中多态的基础,主要是针对对象的,而构造函数是在对象产生之前运行的,因此虚构造函数是没有意义的。
- 析构函数可以是虚函数,而且通常说明为虚函数。
3. 虚析构函数
- 问题:在程序用带指针参数的delete运算符撤销对象时,会发生一个情况:系统会只执行基类的析构函数,而不执行派生类的析构函数。
- 解决方法:将基类的析构函数声明为虚函数。
析构函数设置为虚函数后,在使用指针引用时可以动态联编,实现运行时的多态,保证使用基类类型的指针能够调用适当的析构函数针对不同的对象进行清理工作。
如果一个类的析构函数是虚函数,那么,由它派生而来的所有派生类的析构函数也是虚析构函数,不管它是否使用了关键字virtual进行说明。
虚析构函数的定义格式:
virtual ~类名();
例:使用虚析构函数举例
#include<iostream.h>
class Grandam{
public:
    Grandam() { }
    virtual ~Grandam() {
        cout<<"This is Grandam∷~Grandam()."<<endl;
    }
};
class Mother: public Grandam{
public:
    Mother() { }
    ~Mother() {
        cout<<"This is Mother∷~Mother()."<<endl;
    }
};
void main(){
    Grandam *f;
    f=new Mother;
    delete f;
}
运行结果为:
This is Mother∷~Mother().
This is Grandam∷~Grandam().
虚函数与重载函数的关系
在一个派生类中重新定义基类的虚函数是函数重载的另一种形式,但它不同于一般的函数重载。
- 普通的函数重载时,其函数的参数个数或参数类型必须有所不同, 函数的返回类型也可以不同。
- 当重载一个虚函数时,也就是说在派生类中重新定义虚函数时, 要求函数名、 返回类型、 参数个数、 参数的类型和顺序与基类中的虚函数原型完全相同。
- 如果仅仅返回类型不同,其余均相同,系统会给出错误信息;
- 若仅仅函数名相同,而参数的个数、类型或顺序不同,系统将它作为普通的函数重载,这时将丢失虚函数的特性。
例:虚函数与重载函数的比较
#include<iostream.h>
class Base{
public:
    virtual void func1();
    virtual void func2();
    virtual void func3();
    void func4();
};
class Derived: public Base {
public:
    virtual void func1();
    void func2(int x);
    char func3();
    void func4();
};
void Base∷func1()
{ cout<<"--Base func1--\n"; }
void Base∷func2()
{ cout<<"--Base func2--\n"; }
void Base∷func3()
{ cout<<"--Base func3--\n"; }
void Base∷func4()
{ cout<<"--Base func4--\n"; }
void Derived∷func1()
{ cout<<"--Derived func1--\n";}
void Derived∷func2(int x)
{ cout<<"--Derived func2--\n";}
void Derived∷func4()
{ cout<<"--Derived func4--\n";}
void main(){
    Base d1, *bp;
    Derived d2;
    bp=&d2;
    bp->func1(); //调用Derived∷func1()
    bp->func2(); //调用Base∷func2()
    bp->func4(); //调用Base∷func4()
}
--Derived func1--
--Base func2—
--Base func4--
多继承与虚函数
例:多继承情况下的虚函数调用
#include<iostream.h>
class Base1{
public:
    virtual void fun() //定义fun()是虚函数
    { cout<<"--Base1--\n"; }
};
class Base2{
public:
    void fun() //定义fun()为普通的成员函数
    { cout<<"--Base2--\n"; }
};
class Derived: public Base1,public Base2{
public:
    void fun()
    { cout<<"--Derived--\n"; }
};
void main(){
    Base1 obj1,*ptr1;
    Base2 obj2,*ptr2;
    Derived obj3;
    ptr1=&obj1;
    ptr1->fun();
    ptr2=&obj2;
    ptr2->fun();
    ptr1=&obj3;
    ptr1->fun();
    ptr2=&obj3;
    ptr2->fun();
}
--Base1--
--Base2--
--Derived--
--Base2—
例:应用C++的多态性,计算三角形、矩形和圆的面积
#include<iostream.h>
class Figure { // 定义一个公共基类
public:
    Figure(double a,double b){
        x=a;
        y=b;
    }
    virtual void show_area() {
        cout<<"No area computation defined";
        cout<<"for this class.\n";
    }
protected:
    double x,y;
};
class Triangle: public Figure { // 定义三角形派生类
public:
    Triangle(double a,double b): Figure(a,b) { };
    void show_area() { // 虚函数重定义,用作求三角形的面积
        cout<<"Triangle with height "<<x;
        cout<<" and base "<<y<<" has an area of ";
        cout<<x*y*0.5<<endl;
    }
};
class Square: public Figure { // 定义矩形派生类
public:
    Square(double a,double b): Figure(a,b) { };
    void show_area() { // 虚函数重定义,用作求矩形的面积
        cout<<"Square with dimension "<<x;
        cout<<" * "<<y<<" has an area of ";
        cout<<x*y<<endl;
    }
};
class Circle: public Figure { // 定义圆派生类
public:
    Circle(double a): Figure(a,a) { };
    void show_area() { // 虚函数重定义,用作求圆的面积
        cout<<"Circle with radius "<<x;
        cout<<" has an area of ";
        cout<<x*x*3.1416<<endl;
    }
};
main(){
    Figure *p; // 定义基类指针p
    Triangle t(10.0,6.0); // 定义三角形类对象t
    Square s(10.0,6.0); // 定义矩形类对象s
    Circle c(10.0); // 定义圆类对象c
    p=&t;
    p->show_area(); // 计算三角形面积
    p=&s;
    p->show_area(); // 计算矩形面积
    p=&c;
    p->show_area(); // 计算圆面积
    return 0;
}
Triangle with height 10 and base 6 has an area of 30
Square with dimension 10*6 has an area of 60
Circle with radius 10 has an area of 314.163.
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号