天天看点

动态特性与静态

动态特性

在绝大多数情况下,程序的功能是在编译的时候就确定下来的,我们称为静态特性。反之,如果程序的功能是在运行时刻才确定下来的,则称为动态特性。

动态特性是面向对象语言最强大的功能之一,因为它在语言层面上支持程序的可扩展性,而可扩展性是软件设计追求的重要目标之一。

c++虚函数、抽象基类、动态绑定、多态构成了出色的动态特性。

1.虚函数,动态绑定:

相同对象收到不同消息或不同对象收到相同消息时产生的不同的动作。

静态多态 vs 动态多态

[-:>静态多态也叫做早绑定

class Rect //矩形类{
public:
int calcArea(int width);
int calcArea(int width,int height);
};
           

如上面的代码,他们函数名相同,参数个数不同,一看就是互为重载的两个函数

1 int main()
2 {
3 Rect.rect;
4 rect.calcArea(10);
5 rect.calcArea(10,20);
6 return 0;
7 }
           

程序在编译阶段根据参数个数确定调用哪个函数。这种情况叫做静态多态(早绑定)

[-:>动态多态也叫做晚绑定

比如计算面积 当给圆形计算面积时使用圆形面积的计算公式,给矩形计算面积时使用矩形面积的计算公式。也就是说有一个计算面积的形状基类,圆形和矩形类派生自形状类,圆形与矩形的类各有自己的计算面积的方法。可见动态多态是以封装和继承为基础的。

1 class Shape//形状类 2 {
3 public:
4 double calcArea()
5 {
6 cout<<"calcArea"<<endl;
7 return 0;
8 }
9 };
10 class Circle:public Shape //公有继承自形状类的圆形类11 {
12 public:
13 Circle(double r);
14 double calcArea();
15 private:
16 double m_dR;
17 };
18 double Circle::calcArea()
19 {
20 return 3.14*m_dR*m_dR;
21 }
22 class Rect:public Shape //公有继承自形状类的矩形类23 {
24 public:
25 Rect(double width,double height);
26 double calArea();
27 private:
28 double m_dWidth;
29 double m_dHeight;
30 };
31 double Rect::calcArea()
32 {
33 return m_dWidth*m_dHeight;
34 }
35 int main()
36 {
37 Shape *shape1=new Circle(4.0);
38 Shape *shape2=new Rect(3.0,5.0);
39 shape1->calcArea();
40 shape2->calcArea();
41 .......
42 return 0;
43 }
           

如果打印结果的话,以上程序结果会打印两行"calcArea",因为调用到的都是父类的calcArea函数,并不是我们想要的那样去分别调用各自的计算面积的函数。如果要想实现动态多态则必须使用虚函数

关键字 virtual ->虚函数

用virtual去修饰成员函数使其成为虚函数

所以以上函数的修改部分如下

class Shape
{
public:
virtual double calcArea(){...}//虚函数
.... //其他部分private:
....
};
....
class Circle:public Shape
{
public:
Circle(double r);
virtual double calcArea();//此处的virtual不是必须的,如果不加,系统会自动加
//上,如果加上则会在后续的时候看的比较明显(推荐加上) ....
private:
....
};
....
class Rect:public Shape
{
Rect(double width,double height);
virtual double calcArea();
private
....
};
....
           

这样就可以达到预期的结果了

多态中存在的问题

[-:>内存泄漏,一个很严重的问题

例如上面的程序中,如果在圆形的类中定义一个圆心的坐标,并且坐标是在堆中申请的内存,则在mian函数中通过父类指针操作子类对象的成员函数的时候是没有问题的,可是在销毁对象内存的时候则只是执行了父类的析构函数,子类的析构函数却没有执行,这会导致内存泄漏。部分代码如下(想去借助父类指针去销毁子类对象的时候去不能去销毁子类对象)

如果delete后边跟父类的指针则只会执行父类的析构函数,如果delete后面跟的是子类的指针,那么它即会执行子类的析构函数,也会执行父类的析构函数

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->calcArea();
delete shape1;
shape1=NULL;
return 0;
}
           

可见我们必须要去解决这个问题,不解决这个问题当使用的时候都会造成内存泄漏。面对这种情况则需要引入虚析构函数

虚析构函数

关键字 virtual ->析构函数

之前是使用virtual去修饰成员函数,这里使用virtual去修饰析构函数,部分代码如下

1 class Shape
2 {
3 public:
4 ....
5 virtual ~Shape();
6 private:
7 ....
8 };
9 class Circle:public Shape
10 {
11 public:
12 virtual ~Circle();//与虚函数相同,此处virtual可以不写,系统将会自动添加,建议写上13 ....
14 };
15 ....
           

这样父类指针指向的是哪个对象,哪个对象的构造函数就会先执行,然后执行父类的构造函数。销毁的时候子类的析构函数也会执行。

virtual关键字可以修饰普通的成员函数,也可以修饰析构函数,但并不是没有限制

virtual在函数中的使用限制

普通函数不能是虚函数,也就是说这个函数必须是某一个类的成员函数,不可以是一个全局函数,否则会导致编译错误。

静态成员函数不能是虚函数 static成员函数是和类同生共处的,他不属于任何对象,使用virtual也将导致错误。

内联函数不能是虚函数 如果修饰内联函数 如果内联函数被virtual修饰,计算机会忽略inline使它变成存粹的虚函数。

构造函数不能是虚函数,否则会出现编译错误。

2.抽象类:

纯虚函数:

纯虚函数的定义

1 class Shape
    2 {
    3 public:
    4 virtual double calcArea()//虚函数5 {....}
    6 virtual double calcPerimeter()=0;//纯虚函数7 ....
    8 };
           

纯虚函数没有函数体,同时在定义的时候函数名后面要加“=0”。

纯虚函数的实现原理:

在虚函数原理的基础上,虚函数表中,虚函数的地址是一个有意义的值,如果是纯虚函数就实实在在的写一个0。

含有纯虚函数的类被称为抽象类

含有纯虚函数的类被称为抽象类,比如上面代码中的类就是一个抽象类,包含一个计算周长的纯虚函数。哪怕只有一个纯虚函数,那么这个类也是一个抽象类,纯虚函数没有函数体,所以抽象类不允许实例化对象,抽象类的子类也可以是一个抽象类。抽象类子类只有把抽象类当中的所有的纯虚函数都做了实现才可以实例化对象。

对于抽象的类来说,我们往往不希望它能实例化,因为实例化之后也没什么用,而对于一些具体的类来说,我们要求必须实现那些要求(纯虚函数),使之成为有具体动作的类。

近含有纯虚函数的类称为接口类

如果在抽象类当中仅含有纯虚函数而不含其他任何东西,我们称之为接口类。

没有任何数据成员

仅有成员函数

成员函数都是纯虚函数

class Shape
{
virtual double calcArea()=0//计算面积
virtual double calcPerimeter()=0//计算周长
};
           

实际的工作中接口类更多的表达一种能力或协议

比如

1 class Flyable//会飞 2 {
3 public:
4 virtual void takeoff()=0;//起飞 5 virtual void land()=0;//降落 6 };
7 class Bird:public Flyable
8 {
9 public:
10 ....
11 virtual void takeoff(){....}
12 virtual void land(){....}
13 private:
14 ....
15 };
16 void flyMatch(Flyable *a,Flyable *b)//飞行比赛
17 //要求传入一个会飞对象的指针,此时鸟类的对象指针可以传入进来18 {
19 ....
20 a->takeoff();
21 b->takeoff();
22 a->land();
23 b->land();
           

3.多态

C++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。如果对象类型是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数

1:用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数。

2:存在虚函数的类都有一个一维的虚函数表叫做虚表,类的对象有一个指向虚表开始的虚指针。虚表是和类对应的,虚表指针是和对象对应的。

3:多态性是一个接口多种实现,是面向对象的核心,分为类的多态性和函数的多态性。

4:多态用虚函数来实现,结合动态绑定.

5:纯虚函数是虚函数再加上 = 0;

6:抽象类是指包括至少一个纯虚函数的类。

动态特性与静态

PEACE!