多态(静态多态+动态多态)
多态是面向对象的三大特征(封装,继承,多态)之一。
教科书定义:指相同对象接收不同消息或不同对象接到相同消息产生不同的动作。简单来说就是当发出一条命令时,不同的对象接收到同样的命令时,所做出的动作是不同的。
静态多态(早绑定)
如下代码,两个一样的函数名,但是参数不同,在调用时,程序会根据参数不同调用不同函数。因为程序很早就把这种情况编译进去,这就情况就叫静态多态。
class Rect
{
public:
int calcArea(int width);
int calcArea(int width, int height);
};
int main()
{
Rect rect;
rect.calcArea(10);
rect.calcArea(10, 20);
return 0;
}
动态多态(晚绑定)
动态多态必须以封装和继承为基础,动态多态起码要有两个类,一个是子类,一个是父类,当然也可以有三个类,只有有三个类的时候我们的动态多态才能表现的比较明显,以下代码为例
class Circle :public Shape
{
public:
Circle(double r);
double calcArea();
private:
double m_dR;
};
double Circle::calArea()
{
return 3.14*m_dR*m_dR;
};
double Rect::calcArea()
{
return m_dWidth*m_dHeight;
};
int main()
{
Shape *shape1 = new Circle(4,.0);
Shape *shape2 = new Rect(3.0,5.0);
shape1->calcArea();//用到的都是父类的计算面积,屏幕上会打印出claArea()
shape2->calcArea();
//....
return 0;
}
可以利用virtual虚函数来实现多态。如下代码,输出的就是计算的圆的面积
class Shape
{
public:
virtual double calcArea()//虚函数
{
cout << "clacArea" << endl;
return 0;
}
};
class Circle :public Shape
{
public:
Circle(double r);
virtual double calcArea();//virtual不是必须,系统会自动加一个virtual
private:
double m_dR;
};
但是动态多态容易引起内存泄漏的问题,如下代码
代码1:
class Shape
{
public:
Shape();
virtual double calcArea();
};
class Circle :public Shape
{
public:
Circle(int x, int y, double r);
~Circle();
virtual double calcArea();
private:
double m_dR;
Coordinate *m_pCenter; //多定义了一个指针的数据成员
};
Circle::Circle(int x, int y, double r)
{
m_pCenter = new Coordinate(x, y);
m_dR = r;
}
Circle::~Circle
{
delete m_pCenter;
m_pCenter = NULL;
}
int main()
{
Shape *shape1 = new Circle(3, 5, 4.0);
shape1->calArea();
delete shape1;//想借助父类的指针去销毁子类的指针时,会出问题,只执行父类的析构
shape1 = NULL;
return 0;
}
如上代码,我们在Circle中多定义了一个成员指针*m_pCenter,当我们在main()函数中delete shape1时,由于它是父类的指针,销毁时他只会调用父类的析构函数,而不会调用子类的析构函数,这就造成了内存的泄漏。这时我们要引入虚析构函数,同样是用virtual关键字修饰析构函数。代码示例如下:
代码1.1
class Shape
{
public:
Shape();
virtual ~shape();//虚析构函数
virtual double calcArea();
};
class Circle :public Shape
{
public:
Circle(int x, int y, double r);
virtual ~Circle();
virtual double calcArea();
private:
double m_dR;
Coordinate *m_pCenter; //多定义了一个指针的数据成员
};
这个时候我们再用代码1中的main()函数,这个时候我们再使用delete,如果我们这个时候在delete后面跟上父类指针的时候,父类指针指向的是哪个对象,那么哪个对象的析构函数就先得以执行,然后再执行父类的。这样就保证了内存不会泄漏。
同样vitual的使用也有一些限制
1.普通函数(全局函数)不能是虚函数,要是类中的成员函数。
2.静态成员函数不能是虚函数
3.内联函数不能是虚函数
4.构造函数不能是虚函数
虚函数的实现原理
先介绍一下函数指针:之前我们知道如果通过指针指向对象,我们叫他对象指针,那么指针当然也可以指向函数,函数的本质是一段二进制代码,它写在内存当中,我们可以通过指针来指向这段代码的开头,那么计算机就会从开头一直执行,直到函数的结尾,然后再返回。函数的指针和普通的指针本质上时一样的,如下图
class Shape
{
public:
virtual double calcArea()//虚函数
{
return 0;
}
protected:
int m_iEge;
};
class Circle :public Shape
{
public:
Circle(double r);
private:
double m_dR;
};
我们看如上的代码,此时的虚函数如何实现呢?(等我搞懂了再说—–