多态与虚函数 —— 解决指针调用问题

前情提要

class B 继承 class A,本质上就是:B is a A
大家一定要牢牢记住这句话,因为这篇blog所有的讨论,都是由此生发出来的

那么今天我们要讨论一个什么问题呢?

用指针访问成员函数的优美姿势

用指针point to对象吧

众所周知,在定义指针的时候,指针类型应该和指向的变量类型一致
但是由于派生类和基类之间的奇妙联系,我们可以将基类指针指向派生类
(具体规则见下表)

基类指针派生类指针
基类对象OKERROR
派生类对象OKOK

构造完指针后,我们试着调用一下成员函数吧:

class A{
public:
	void f() {cout<<"f in A is called.\n";}
};
class B:public A{
public:
	void f() {cout<<"f in B is called.\n";}
	void g() {cout<<"g in B is called.\n";}
};

int main() 
{
	A a;      //base class
	B b;      //deriving class
	A *Aptr1=&a;    //base-class pointer to base-class object 
	A *Aptr2=&b;    //base-class pointer to derived-class object 
	B *Bptr=&b;     //derived-class pointer to derived-class object
	Aptr1->f();
	Aptr2->f();
	Bptr->f();
	Bptr->g();
	//Aptr3->g();  ERROR!
	return 0;
}

// Output
f in A is called.
f in A is called.
f in B is called.
g in B is called.

结论1:(对于目前已经学习过的函数类型)调用基类还是派生类的成员函数,取决于句柄的类型,而不是句柄指向的实际对象类型


句柄 handles
句柄调用主要有四种形式:

  • 对象名称 + " . "
  • 对象的引用 + " . "
  • 用指针和 " * " 表示对象 + " . "
  • 对象指针 + " -> "

结论2:通过对象句柄,仅能调用该句柄类型的成员函数


指针调用派生类的重定义函数

之前我们提出:通过指针,只能调用句柄类型的成员函数
在介绍继承与派生时,我们提到过:
在扩展派生类时,可能会定义与基类成员函数名相同的函数,这时我们就将其视为对基类成员函数的重定义

那么有没有方法可以用基类指针访问不同派生类的重定义函数呢?
Fortunately,答案是肯定的:枚举类型enum+switch

应用实例:画板问题
定义一基类Shape,由此派生出长方形,圆形,三角形,菱形等形状。
要求使用vector或array保存若干对象的指针(对象包括长方形,圆形,三角形,菱形等),当需要更新画板时,通过指针调用不同对象各自的draw函数。

#include<iostream>
using namespace std;

enum ShapeType{square,circle,triangle};
class Shape{
public:
	Shape(ShapeType x) {T=x;}
	void draw() {cout<<"This is base class:Shape.\n";}
	ShapeType T;
private:
	int position_x,position_y;
};

class Square:public Shape{
public:
	Square(ShapeType x):Shape(x) {}
	void draw() {cout<<"This is a square.\n";}
private:
	int length;
};

class Circle:public Shape{
public:
	Circle(ShapeType x):Shape(x) {}
	void draw() {cout<<"This is a circle.\n";}
private:
	int radius;
};

class Triangle:public Shape{
public:
	Triangle(ShapeType x):Shape(x) {}
	void draw() {cout<<"This is a triangle.\n";}
private:
	int bottom,hight;
};

int main() 
{
	Square sq(square);
	Circle ci(circle);
	Triangle tr(triangle);
	Shape *pShape[]={&sq,&ci,&tr};
	for (int i=0;i<3;i++) {
		switch (pShape[i]->T) {
		    case square: {
			    Square *ptrSquare=(Square *)pShape[i];
			    ptrSquare->draw();
			    break;
		    }
            case circle: {
			    Circle *ptrCircle=(Circle *)pShape[i];
			    ptrCircle->draw();
			    break;
		    }
		    case triangle: {
			    Triangle *ptrTriangle=(Triangle *)pShape[i];
			    ptrTriangle->draw();
			    break;
			}
		}
	}
	system("pause");
	return 0;
}

// Output
This is a square.
This is a circle.
This is a triangle.

多态与虚函数

多态(Polymorphism):通过指向派生类的基类指针,调用派生类的函数。将不同的子类对象都当作基类来处理,可以屏蔽不同子类对象之间的差异,编写写通用的代码。
虚函数(Virtual Function):当一个类的成员函数被声明为虚函数后,就意味着该成员函数在派生类中可能有不同的实现。

多态是同样的消息(对类的成员函数的调用)被类的不同对象接收时导致的完全不同的行为的一种现象
而虚函数和(非成员)函数重载还有一定的区别:

  • 函数重载: 编译阶段根据参数类型、个数决定调用哪个同名函数代码
  • 虚函数:程序运行过程中根据对象类型来决定执行哪个类的成员函数

C++语言支持两种类型的绑定(根据函数代码被确定的时间)

  • 编译时的多态(静态绑定):编译时刻
  • 运行时的多态(动态绑定):运行时刻

虚函数: 调用哪个(基类/派生类)虚函数,由对象类型而不是句柄类型决定

class Shape{
public:
    virtual void draw() const;
};

class Rectangle:public Shape{
    virtual void draw() const;
    //派生类的virtual关键字可以省略.只要基类声明函数为虚函数,则所有派生类的该函数均为虚函数
    //派生类的同名虚函数会在动态绑定的时候覆盖基类虚函数
};

虚函数用于继承层次中的基类和派生类,以实现多态
派生类中覆盖的虚函数和基类中的虚函数函数签名和返回值必须相同,函数定义时不需要virtual关键词

调用虚函数的两种方式:

  • 通过指针(或引用)调用,程序会在执行时(execution time)根据对象类型动态选择合适的派生类函数——动态绑定(dynamic binding)
  • 通过对象名和点操作符调用,程序在编译时(compile time)根据对象类型确定函数——静态绑定(static binding)

注意:

  • 只有类成员才能声明为虚函数
  • 静态成员函数不能声明为虚函数
  • 构造函数不能是虚函数
  • 析构函数可以是虚函数,并且通常声明为虚函数 (注意基类和派生类的析构函数不同名)
总结一下:
  • 只有通过引用或指针来访问对象的虚函数时才进行动态绑定
  • 如果句柄是引用或指针,通过成员函数调用虚函数时,采用动态绑定
  • 只要访问的是非虚函数,都采用静态绑定 (由句柄类型决定)

特殊情况:

  • 通过 类名+:: 访问对象成员函数时采用静态绑定
  • 基类构造函数中对虚函数的调用采用静态绑定

所以我们要怎么判断调用成员函数别时,使用的是静态绑定还是动态绑定呢?
在这里插入图片描述


多态与虚函数的应有举例

class A{
public:
    A() {f();}   //构造函数里的都是静态绑定
    ~A();
    virtual void f();
    //虚函数允许重定义,调用时会被动态绑定
    void g();
    void h() {f(); g();}
};

class B:public A{
public:
    ~B();
    void f();
    void g();
};

int main()
{
    A a;       //调用A::A()和A::f
    a.f();     //调用A::f
    a.g();     //调用A::g
    a.h();     //调用A::h,A::f和A::g
    
    B b;       //调用A::A(),A::f和B::B()
    b.f();     //调用B::f
    b.g();     //调用B::g
    b.h();     //调用A::h,B::f和A::g

    A *p=&a;
    p->f();    //调用A::f
    p->g();    //调用A::g
    p->h();    //调用A::h,A::f和A::g

    p=&b;
    p->f();    //调用B::f,虚函数调用时会被动态绑定
    p->A::f(); //调用A::f,有作用域解析符
    p->g();    //调用A::g,静态绑定,调用句柄类型的成员函数
    p->h();    //调用A::h,B::f,A::g

    p=new B;   //调用A::A(),A::f,B::B
    delete p;  //调用A::~A()
}
class A{
public:
    void f() {cout<<"1"<<endl;}
    virtual void g() {cout<<"2"<<endl;}   
    //虚函数允许重定义,调用时会被动态绑定
};

class B:public A{
public:
    void f() {cout<<"3"<<endl;}
    void g() {cout<<"4"<<endl;}
};

int main() 
{
    A a;
    B b;
    A *Aptr=&a;
    Aptr->f(); Aptr->g();

    Aptr=&b;
    Aptr->f();  //静态绑定,调用句柄类型的成员函数
    Aptr->g();  //虚函数调用时会被动态绑定

    B *Bptr=(B *)&a;
    Bptr->f();  //静态绑定,调用句柄类型的成员函数
    Bptr->g();  //虚函数调用时会被动态绑定
    
    return 0;
}

// Output
1
2
1
4
3
2

//Shape.h
#ifndef SHAPE_H
#define SHAPE_H
class Shape{
public:
	Shape(int _x=0,int _y=0):x(_x),y(_y) {}
	virtual ~Shape() {}
	virtual void show() const=0;
	void setX(int z) {x=z;}
	void setY(int z) {y=z;}
	int getX() const{return x;}
	int getY() const{return y;}
private:
	int x,y;
};
#endif
//Point.h
#ifndef POINT_H
#define POINT_H
#include<iostream>
#include"Shape.h"
using namespace std;
class Point:public Shape{
public:
	Point(int _x,int _y):Shape(_x,_y) {}
	~Point() {}
	void show() const {
		cout<<"this is a Point, x="<<getX()<<" y="<<getY()<<endl;
	}
};
#endif
//Circle.h
#ifndef CIRCLE_H
#define CIRCLE_H
#include<iostream>
#include"Shape.h"
#include"Point.h"
using namespace std;
class Circle:public Shape{
public:
	Circle(Point A,int r):center(A),radius(r) {}
	~Circle() {}
	void show() const {
		cout<<"this is a Circle\n";
		cout<<"center is ";
		center.show();
		cout<<"radius is "<<radius<<endl;
	}
private:
	Point center;
	int radius;
};
#endif
class ShapeSet{
public:
	ShapeSet (int s=0):size(s) {pshapes=new Shape*[s];}
	Shape* &operator[](int index) {return pshapes[index];}
	int size;
	Shape** pshapes;
};

int main()
{
	ShapeSet shapeset(5);
	shapeset[0]=new Point(100,100);
	shapeset[1]=new Point(200,200);
	shapeset[2]=new Point(150,150);
	shapeset[3]=new Circle(*(static_cast<Point*>(shapeset[2])),50);
	for (int i=0;i<4;i++) (shapeset[i])->show();
	return 0;	
}

// Output
this is a Point, x=100 y= 100
this is a Point, x=200 y= 200
this is a Point, x=150 y= 150
this is a Circle
center is this is a Point, x=150 y= 150
radius is 50

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值