您的位置:首页 > 编程语言 > C语言/C++

C++面向对象程序设计之灵魂——多态性

2016-06-16 11:20 204 查看
多态性是面向对象程序设计的一个重要特性。如果一个语言只支持类而不支持多态,是不能称为面向对象语言的,只能说是基于对象的。

多态性

一、定义

  好多书上写的比较字面的解释是:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为。但是这读起来总感觉很难理解。

  我对它的理解就是:通过不同的对象在调用一个同名函数时,会产生不同的功能。当然这个同名函数本身是具有不同功能的。

二、分类

静态多态:

  编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
  比如一个简单的函数重载:

int Add(int l,int r)
{
return l + r;
}
float Add(float l, float r)
{
return l + r;
}
int main()
{
cout << Add(1, 2) << endl;           //通过实参类型调用int Add()
cout << Add(2.0f, 3.1f) << endl;     //通过实参类型调用float Add()
system("pause");
return 0;
}
动态多态:

  不在编译时确定调用的那个函数,而是在程序运行时动态的确定对象的类型是,然后根据实际类型调用相应的方法。动态的多态性就是通过虚函数的实现的。
  在这里有必要介绍一下什么是静态绑定(早绑定)和动态绑定(晚绑定):在编译器编译期间就可以确定函数的调用地址,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定,此时是将虚函数和对应的对象绑定在一起。
动态绑定的条件
1.必须是将对象和虚函数绑定
2.通过基类的指针或者引用来调用一个新对象
使用virtual关键字修饰函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。

class Base
{
public:
virtual void Display()
{
cout << "In The Base()" << endl;
}
};
class Derived :public Base
{
public:
virtual void Display()
{
cout << "In The Derived()" << endl;
}
};
int main()
{
Base* pb = new Base;   //pb指向Base类的对象
pb->Display();         //将虚函数和Base类的对象绑定
pb = new Derived;      //基类指针指向一个Derived的新对象
pb->Display();         //将虚函数和Derived类的对象绑定
system("pause");
return 0;
}
执行结果:

  这就是动态多态性通过虚函数的实现,通过指向不同对象的基类指针pb调用同一个同名函数,从而实现不同的功能。
三、函数重载、同名隐藏和覆盖(重写)
  之前已经了解过函数重载和同名隐藏,在多态中又出现了一种新的同名类型——覆盖(也称作重写),那么在类的继承体系中他们三个同名函数的区别到底是什么。

class Base
{
public:
void Add(int l,int r)
{
cout << l + r << endl;
}
void Add(float l, float r)           //重载,属于同一作用域
{
cout << l + r << endl;
}
void Test1(int t1)
{
cout << "Base::Test1()" << endl;
}
virtual void Test2(int t1)
{
cout << "Base::Test2()" << endl;
}
virtual void Display()
{
cout << "In The Base()" << endl;
}
};
class Derived :public Base
{
public:
void Test1(int t1)                           //隐藏,基类中没有virtual关键字
{
cout << "Derived::Test1()" << endl;
}
virtual void Test2(int t1,int t2)            //隐藏,基类中有virtual关键字,参数不同
{
cout << "Derived::Test2()" << endl;
}
virtual void Display()                      //覆盖,基类中有virtual关键字,参数相同
{
cout << "In The Derived()" << endl;
}
};
纯虚函数

一、定义
  纯虚函数是指在基类中声明时被初始化为0的函数。纯虚函数在基类中没有定义,要求在派生类中实现。
二、一般形式
  virtual 函数类型 函数名(参数列表)=0;
三、注意
1.纯虚函数没有函数体。
2.最后面的“=0”不表示函数返回值为0,只是告诉编译系统该函数为纯虚函数。
四、抽象类
  含有纯虚函数的类叫做抽象类,抽象类不能用来定义对象(不能实例化)。
注意
  抽象类不能定义对象,但是可以定义指向抽象类数据的指针。
  如果在派生类中没有对纯虚函数定义,则这个派生类也是抽象类,不能用来定义对象。
总结
1、派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)
2、基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。
  派生类中函数名的前边可以不加virtual关键字。
3、只有类的成员函数才能定义为虚函数,静态成员函数不能定义为虚函数。
  静态成员函数是属于类的对象公有的,没有this指针,不能实现动态绑定。
4、如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。
5、构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容

易混淆。
  虚函数的访问是通过虚表来实现的,在对象未创建完成之前,无法将虚表指针填进对象中,从而无法完成虚函数的访问。
赋值运算符本来是用来进行同类型对象的赋值,然而不同类型的函数也可以定义为虚函数,所有会出现类型二义性问题。
6、不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会

出现未定义的行为。
7、最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构

函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)
避免内存泄露的问题。如果基类和派生类中都有动态开辟内存,在不将析构函数声明为虚函数的前提下,系统只会释放基类中动态开辟的空间,而忽略派生类中动态开辟的空间。通过将基类的析构函数声明为虚函数可以解决。

  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: