静态绑定与动态绑定
静态绑定:
-编译时就能确定一条函数调用语句要调用的函数
-在程序编译时多态性体现在函数和运算符的重载上
动态绑定:
-运行时才能确定函数调用语句调用的函数
-程序运行时的多态性通过继承和虚函数来体现
多态
什么是多态?
顾名思义就是同一个事物在不同场景下的多种形态。
多态性的概念
多态性是面向对象程序设计的重要特征之一。
多态性概念:具有继承关系的类,其对象对同一个函数调用可以作出不同的响应
-同一个函数调用——同一条函数调用语句
-不同的响应——执行不同的函数
多态性的优点
①多态性有助于更好地对程序进行抽象
-控制模块能专注于一般性问题的处理
-具体的操作交给具体的对象去做
②多态性有助于提高程序的可扩展性
-可以把控制模块与被操作的对象分开
-可以添加已定义类的新对象,并能管理该对象
-可以添加新类(已有类的派生类)的新对象,并能管理该对象
那什么时候会用到多态呢?
当我们需要隐藏子类对象的多余属性和方法时,所以利用父类指针指向子类对象时,就可以完美避免误操作调用了子类对象的属性和方法。
静态多态
我们以前说过的函数重载就是一个简单的静态多态
示例:
int Add(int left, int right)
{
return left + right;
}
double Add(double left, int right)
{
return left + right;
}
int main()
{
Add(10, 20);
Add(10.0,20);
return 0;
}
虚函数(动态多态)
虚函数的概念
概念:在基类中冠以关键字 virtual 的成员函数
虚函数的定义:virtual 函数类型 函数名称(参数列表);
-注意:如果一个函数在基类中被声明为虚函数,则他在所有派生类中都是虚函数(包括重定义函数)
虚函数的作用:用指针/引用调用时会发生动态的绑定。
注意:①只有通过基类指针或引用调用虚函数才能引发动态绑定
②声明时要加virtual,定义时不用加。
实例
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout<<"A::construct"<<endl;
}
virtual void print()
{
cout<<"This is A"<<endl;
}
};
class B : public A
{
public:
B()
{
cout<<"B::construct"<<endl;
}
void print()
{
cout<<"This is B"<<endl;
}
};
int main()
{
A a;
a.print();
B b;
b.print();
/*结果:
A::construct
This is A
A::construct
B::construct
This is B*/
//B b;
//A *a; //调用B中的print
//a = &b;
//a->print();
/*结果:
A::construct
B::construct
This is B*/
//B b;
//A &a = b; //调用B中的print
//a.print();
/*结果:
A::construct
B::construct
This is B*/
//B b;
//A a; //虚函数不会调用B中的print
//a = b;
//a.print();
/*结果:
A::construct
B::construct
A::construct
This is A*/
return 0;
}
虚函数的注意事项
- 在类体系中访问一个虚函数时,应使用指向基类类型的指针或基类类型的引用,以满足运行时多态性的要求。当然也可以像调用普通成员函数那样利用对象名来调用一个函数。
- 在派生类中重新定义虚函数时,必须保证该函数的值和参数与基类中的说明完全一致,否则就属于重载。
- 若在派生类中没有重新定义虚函数,则该类的对象将使用其基类中的虚函数代码。
- 虚函数必须是类的一个成员函数,不能是友元,但它可以是另一个类的友元。虚函数不得是一个静态成员。
虚函数和重载函数的区别(重点)
- 成员函数被重载需要在相同范围(同一个类中),而虚函数要求在不同的范围(一个在派生类,一个在基类)
- 重载函数要求函数有相同函数名称,并有不同的参数序列;而虚函数则要求函数名、返回值类型和参数序列完全相同
- 重载函数可以是成员函数或友员函数,而虚函数只能是成员函数
- 重载函数的调用是以所传递参数序列的差别作为调用不同函数的依据;虚函数是根据对象的不同去调用不同类的虚函数
- 虚函数在运行时表现出多态功能,这是C++的精髓;而重载函数则在编译时表现出多态性
虚析构函数
析构函数可以声明为虚函数:
-程序会根据基类指针指向的对象的类型确定要调用的析构函数
动态对象的创建和撤销
动态对象的创建:
new ClassName(…);
ClassName指明了要调用的构造函数
动态对象的撤销:
delete 基类指针;
虚析构函数作用
虚析构函数的作用就是:当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用
下面用一个简单的例子来说明吧
#include<iostream>
using namespace std;
class ClxBase
{
public :
ClxBase(){};
virtual ~ ClxBase(){};
virtual void DoSomething()
{cout<<" Do something in class ClxBase! "<<endl;};
};
class ClxDerived:public ClxBase
{
public :
ClxDerived(){};
~ ClxDerived()
{cout<<" Output from the destructor of class ClxDerived! "<<endl;};
void DoSomething()
{cout<<" Do something in class ClxDerived!"<<endl;};
};
int main()
{
ClxBase * pTest = new ClxDerived;
pTest -> DoSomething();
delete pTest;
return 0;
}
执行结果是这样的:
Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
但是,如果把类ClxBase析构函数前的virtual去掉,那输出结果就是下面的样子了:
Do something in class ClxDerived!
也就是说,类ClxDerived的析构函数根本没有被调用!一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。当然,这并不意味着要把所有类的析构函数都写成虚函数。
注意事项
①基类的析构函数为虚函数,所有派生类的析构函数都是虚函数
②构造函数不得是虚函数
③如果要操作具有继承关系的类的动态对象,最好使用虚析构函数。特别是在析构函数需要完成一些有意义的操作——比如释放内存
纯虚函数
实现多态性的前提: ①需要有共同的基类 ②需要在基类中定义共同的接口 ③接口要定义为虚函数
如果基类的接口没办法实现怎么办? 解决方法:①不实现这些接口:纯虚函数 ②包含纯虚函数的类:抽象基类
纯虚函数定义
在基类中不能给出有意义的虚函数定义,这时可以把它说明成纯虚函数,把它的定义留给派生类来做。(简单的说:①纯虚函数是被标明为不具体实现的虚函数②纯虚函数的实现留给该基类的派生类去做。)
virtual 类型 函数名(参数名)=0;
抽象类
如果一个类中至少有一个纯虚函数,那么这个类被成为抽象类(abstract class)。
抽象类存在的意义是作为其它类的基类,也叫抽象基类。
主要作用:取若干类的共同行为,形成更清晰的概念层次。使用抽象类符合程序设计中的单选原则(single choice principle)。
注意:
①抽象类不能用于直接创建对象实例,可以声明抽象类的指针和引用
②可使用指向抽象类的指针支持运行时多态性
③派生类中必须重载基类中的纯虚函数,否则它仍将被看作一个抽象类
实例
#include<iostream>
#define PI 3.14
using namespace std;
class Shape
{
public:
virtual double area() const = 0;
virtual void show() const = 0;
};
class Circle :public Shape
{
public:
Circle(double=0.0,double=0.0,double=1.0);
double area() const;
void show() const;
private:
double x,y;
double r;
};
Circle::Circle(double a, double b, double c)
{
x = a;
y = b;
r = c;
}
double Circle::area() const
{
return PI*r*r;
}
void Circle::show() const
{
cout<<"I am a Circle: ";
}
class Rectangle :public Shape
{
public:
Rectangle(double = 1.0, double = 1.0);
double area() const;
void show() const;
private:
double length;
double width;
};
Rectangle::Rectangle(double a, double b)
{
length = a;
width = b;
}
double Rectangle::area() const
{
return length*width;
}
void Rectangle::show() const
{
cout<<" I am a Rectangle: ";
}
void callArea(Shape &obj)
{
obj.show();
cout<<"area = "<<obj.area()<<endl;
}
int main()
{
Circle cir(0.0, 0.0, 2.5);
Rectangle rec(2.4, 5.3);
callArea(cir);
callArea(rec);
return 0;
}
执行结果:
I am a Circle: area = 19.625
I am a Rectangle: area = 12.72