C++ 多态
多态性是指一个名字,多种语义;或界面相同,多种实现。
静态多态性:通过函数重载或者运算符重载实现,在编译阶段即知道函数的全部调用关系,也称为是编译时的多态性。根据表达式上下文确定该执行哪一个功能。
优点:调用速度快,效率高;缺点:缺乏灵活性。
动态多态性:在运行时实现的多态,即在运行的时候才知道操作所针对的对象。通过虚函数来实现。
重载函数是多态性的一种简单形式。
虚函数允许函数调用与函数体的联系在运行时才进行,称为动态联编。
冠以关键字virtual 的成员函数称为虚函数
实现运行时多态的关键首先是要说明虚函数,另外,必须用
基类指针调用派生类的不同实现版本
当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
例如基类 Shape 被派生为两个类,如下(该代码摘自知乎):
#include<iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
int area()
{
cout << "Parent class area:" <<endl;
return 0;
}
};
class Rectangle:public Shape{
public:
Rectangle( int a=0, int b=0):Shape(a, b){ }
int area ()
{
cout << "Rectangle classarea :" <<endl;
return (width * height);
}
};
class Triangle:public Shape{
public:
Triangle( int a=0, int b=0):Shape(a, b) {}
int area ()
{
cout << "Triangle classarea :" <<endl;
return (width * height / 2);
}
};
// 程序的主函数
int main( )
{
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// 存储矩形的地址
shape = &rec;
// 调用矩形的求面积函数 area
shape->area();
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数 area
shape->area();
return 0;
}
[/demo]
当上面的代码被编译和执行时,它会产生下列结果:
Parent class area
Parent class area
导致错误输出的原因是,调用函数 area() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接 - 函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为 area() 函数在程序编译期间就已经设置好了。
但现在,让我们对程序稍作修改,在 Shape 类中,area() 的声明前放置关键字 virtual,如下所示:
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
virtualint area()
{
cout << "Parent class area:" <<endl;
return 0;
}
};
修改后,当编译和执行前面的实例代码时,它会产生以下结果:
Rectangle class area
Triangle class area
此时,编译器看的是指针的内容,而不是它的类型。因此,由于 tri 和 rec 类的对象的地址存储在 *shape 中,所以会调用各自的 area() 函数。
每个子类都有一个函数 area() 的独立实现。这就是多态的一般使用方式。有了多态,就可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。
虚函数
虚函数 是在基类中使用关键字 virtual 声明的函数。
重载特性:
在派生类中重载基类的虚函数要求函数名、返回类型、参数个数、参数类型和顺序完全相同
如果仅仅返回类型不同,C++认为是错误重载
如果函数原型不同,仅函数名相同,丢失虚特性
虚析构函数
构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数
析构函数可以是虚的。虚析构函数用于指引 delete 运算符正确析构动态对象
[/demo]
#include<iostream>
using namespace std ;
class A
{public:
~A(){ cout << "A::~A() is called.\n" ; }
} ;
class B : public A
{public:
~B(){ cout << "B::~B() is called.\n" ; }
} ;
int main()
{ A*Ap = new B ;
B*Bp2 = new B ;
cout << "delete first object:\n" ;
delete Ap;
cout << "delete second object:\n" ;
delete Bp2 ;
}
[/demo]
当上面的代码被编译和执行时,它会产生下列结果:
delete first object:
B::~B() is called.
A::~A() is called.
delete second object:
B::~B() is called.
A::~A() is called.
纯虚函数
如果要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
class point { /*……*/ } ;
class shape ; // 抽象类
{ point center ;
……
public :
point where ( ) { return center ; }
void move ( point p ) {center = p ; draw ( ) ; }
virtual void rotate ( int ) = 0 ; // 纯虚函数
virtual void draw ( ) = 0 ; // 纯虚函数
} ;
…...
基于以上的代码:
Shape x ; // error,抽象类不能建立对象
shape *p ; //ok,可以声明抽象类的指针
shape f ( ) ; // error, 抽象类不能作为函数返回类型
void g ( shape ) ; //error, 抽象类不能作为传值参数类型
shape & h ( shape &) ; // ok,可以声明抽象类的引用
多态性和虚函数的学习心得:
多态与非多态的区别在于“成员函数调用地址的早绑定和晚绑定”。“早绑定”在编译期就可以确定函数的调用地址,是静态的;“晚绑定”在运行时才能确定函数的调用地址,是动态的。
在面向对象的编程中,“封装”使得代码模块化;“继承”可以扩展以存在的代码;“多态”使得接口重用。虚函数是多态性的c++实现。