首页 > 代码库 > C++语言笔记系列之十八——虚函数(1)

C++语言笔记系列之十八——虚函数(1)

1.C++中的多态
(1)多态性:同一个函数的调用可以进行不同的操作,函数重载是实现多态的一种手段。
(2)联编:在编译阶段进行联接,即是在编译阶段将一个函数的调用点和函数的定义点联接起来。
A.静态联编:在编译阶段就完成的函数联编——函数重载。
B.动态联编:在程序的运行阶段由系统自动选择具体的函数——虚函数。
注:C++的多态主要指的就是动态联编。
2.虚函数
(1)虚函数是在函数的定义时将其声明为虚函数即可。
(2)说明:virtual 数据类型 函数名(参数表) {函数体}
A.目的:当通过基类指针调用虚函数时,系统进行动态联编。
B.在派生类中定义了和基类名称相同、参数个数相同、参数类型相同、返回值类型相同的函数时,若基类中的同名成员函数是虚函数,派生类的同名成员函数将自动被虚化。
C.通过对象访问虚函数时采用静态联编。动态联编只能够通过指向对象的指针和对象的引用来完成。
3.例子
example 1

#include <iostream.h>

class Point(
{
private:
    float x, y;
public:
    void setpoint(float i, float j)
    {
        x = i; y = j;
    }
    float area() {return 0.0;}//等价于:float area()=0;
};
const float PI=3.1416;
class Circle:pulic Point
{
private:
    float rad;
public:
    void setrad(float r) {rad = r;}
    float area() {return PI*rad*rad;}
};
int main()
{
    Point p;
    Circle c;
    float a = p.area();
    cout<<a<<endl;
    c.setrad(2.5);
    a = c.area();
    cout<<a<<endl;
}
分析:
float a=p.area()调用的是Point类的area函数:p是基类Point的对象;对象调用函数时采用静态联编;Point类的对象调用area,area一定是Point类的area;a=c.area()调用的是Circle类的area函数,因为就近原则。
修改main函数:

int main()
{
    Point *p;
    Circle c;
    float a;
    c.setrad(2.5);
    p = &c;
    a = p->area();//该函数属于静态联编
    cout<<a<<endl;
}
程序输出:
0.0
分析:尽管基类指针指向了派生类对象,但只可以通过基类的指针引用派生类对象从基类继承过去的成员。如果要想基类指针能够指向派生类的成员,那么就要使用虚函数,从而实现动态联编。
4.四种静态联编
(1)基类指针指向基类对象,采用静态联编调用基类成员。
(2)派生类的指针指向派生类的对象采用静态联编,调用派生类成员,若派生类没有的成员,调用基类的该成员。
(3)基类指针指向派生类对象,采用静态联编,调用基类成员。
(4)对象引用成员一定是静态联编,基类对象引用基类成员,派生类对象引用派生类成员,若派生类没有则可以引用基类成员。
注:派生类指针不可指向基类,引用也不可以。
5.动态联编
(1)条件:基类的同名函数被声明为虚函数;基类指针指向派生类对象。
(2)作用:使用动态联编,使得一个基类指针可以访问多个派生类的成员函数,动态联编只能通过指针或引用来实现(前提是虚函数机制)。
example 2

#include <iostream.h>

class Point
{
public:
    virtual double area() {return 0.0;}
};
class Rectangle:public Point
{
    double length, width;
public:
    Rectangle(double l, double w):length(l), width(w) {}
    double area() {return length*width;}
};
class Circle:public Point
{
    double radium;
public:
    Circle(double r) {radium = r;}
    double area() {return 3.1416*radium*radium;}
};
class Triangle:public Point
{
    double x, y, z;
public:
    Triangle(double a, double b, double c)
    {x = a; y = b; z = c;}
    double area() {return (x+y+z)*0.5;}
};
int main()
{
    Point *p;
    Rectangle r(5.0, 2.0);
    Circle c(6.0);
    Triangle t(3.0, 4.0, 5.0);
    p = &r;
    cout<<p->area()<<endl;
    p = &c;
    cout<<p->area()<<endl;
    p = &t;
    cout<<p->area()<<endl;
}
程序输出:
10
113.098
6
注:基类的虚函数也可以被调用,调用方法:加类的作用域(强制采用静态联编)。
示例:
p = &r;
p->area();//调用Rectangle类的area函数
p->Point::area();//强制采用静态联编,调用Point类作用下的area函数
6.虚函数的访问权限
(1)派生类中的虚函数不影响动态联编,基类的虚函数是保证动态联编的必要条件。
(2)一个类中的虚函数只对派生类重定义的函数有影响,对它的基类成员无影响。
example 3

#include <iostream.h>

class A
{
public:
    virtual void fun1()
    {cout<<"fun1()...fun2()"<<endl; fun2();}
    void fun2()
    {cout<<"fun2()...fun3()"<<endl; fun3();}
    virtual void fun3()
    {cout<<"fun3()...fun4()"<<endl; fun4();}
    virtual void fun4()
    {cout<<"fun4()...fun5()"<<endl; fun5();}
    void fun5() {cout<<"The end."<<endl;}
};
class B:public A
{
    void fun3()
    {cout<<"fun3...fun4"<<endl; fun4();}
    void fun4()
    {cout<<"fun4...fun5"<<endl; fun5();}
    void fun5()
    {cout<<"All done."<<endl;}
};
int main()
{
    A *thing;
    thing = new A;
    thing->fun1();
    thing = new B;
    thing->fun1();
}
程序输出:
fun1()...fun2()
fun2()...fun3()
fun3()...fun4()
fun4()...fun5()
The end.
fun1()...fun2()
fun2()...fun3()
fun3...fun4
fun4...fun5
All done.
7.若基类和派生类中的同名函数参数个数不同,参数类型不同,基类中的成员函数尽管是虚函数,但将丢失虚特性——采用静态联编。
基类和派生类中的同名成员函数,若出现基类中的同名成员函数非虚,派生类中同名成员函数是虚函数,那么也将采用静态联编。
example 4

#include <iostream.h>

class Base
{
public:
    virtual void fun1() {cout<<"Base fun1."<<endl;}
    virtual void fun2() {cout<<"Base fun2."<<endl;}
    void fun3() {cout<<"Base fun3."<<endl;}
    void fun4() {cout<<"Base fun4."<<endl;}
};
class Device:public Base
{
public:
    virtual void fun1()
    {cout<<"Device fun1."<<endl;}
    virtual void fun2()
    {cout<<"Device fun2."<<endl;}
    virtual void fun3()
    {cout<<"Device fun3."<<endl;}
    virtual void fun4()
    {cout<<"Device fun4."<<endl;}
};
int main()
{
    Base *pb;
    Device d;
    pb = &d;
    pb->fun1();
    pb->fun2();
    pb->fun3();
    pb->fun4();
}
程序输出:
Device fun1.
Device fun2.
Base fun3.
Base fun4.