C++面向对象程序设计之灵魂——多态性
2016-06-16 11:20
204 查看
多态性是面向对象程序设计的一个重要特性。如果一个语言只支持类而不支持多态,是不能称为面向对象语言的,只能说是基于对象的。
多态性
一、定义
好多书上写的比较字面的解释是:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为。但是这读起来总感觉很难理解。
我对它的理解就是:通过不同的对象在调用一个同名函数时,会产生不同的功能。当然这个同名函数本身是具有不同功能的。
二、分类
静态多态:
编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
比如一个简单的函数重载:
不在编译时确定调用的那个函数,而是在程序运行时动态的确定对象的类型是,然后根据实际类型调用相应的方法。动态的多态性就是通过虚函数的实现的。
在这里有必要介绍一下什么是静态绑定(早绑定)和动态绑定(晚绑定):在编译器编译期间就可以确定函数的调用地址,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定,此时是将虚函数和对应的对象绑定在一起。
动态绑定的条件
1.必须是将对象和虚函数绑定
2.通过基类的指针或者引用来调用一个新对象
使用virtual关键字修饰函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。
这就是动态多态性通过虚函数的实现,通过指向不同对象的基类指针pb调用同一个同名函数,从而实现不同的功能。
三、函数重载、同名隐藏和覆盖(重写)
之前已经了解过函数重载和同名隐藏,在多态中又出现了一种新的同名类型——覆盖(也称作重写),那么在类的继承体系中他们三个同名函数的区别到底是什么。
一、定义
纯虚函数是指在基类中声明时被初始化为0的函数。纯虚函数在基类中没有定义,要求在派生类中实现。
二、一般形式
virtual 函数类型 函数名(参数列表)=0;
三、注意
1.纯虚函数没有函数体。
2.最后面的“=0”不表示函数返回值为0,只是告诉编译系统该函数为纯虚函数。
四、抽象类
含有纯虚函数的类叫做抽象类,抽象类不能用来定义对象(不能实例化)。
注意
抽象类不能定义对象,但是可以定义指向抽象类数据的指针。
如果在派生类中没有对纯虚函数定义,则这个派生类也是抽象类,不能用来定义对象。
总结
1、派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)
2、基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。
派生类中函数名的前边可以不加virtual关键字。
3、只有类的成员函数才能定义为虚函数,静态成员函数不能定义为虚函数。
静态成员函数是属于类的对象公有的,没有this指针,不能实现动态绑定。
4、如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。
5、构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容
易混淆。
虚函数的访问是通过虚表来实现的,在对象未创建完成之前,无法将虚表指针填进对象中,从而无法完成虚函数的访问。
赋值运算符本来是用来进行同类型对象的赋值,然而不同类型的函数也可以定义为虚函数,所有会出现类型二义性问题。
6、不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会
出现未定义的行为。
7、最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构
函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)
避免内存泄露的问题。如果基类和派生类中都有动态开辟内存,在不将析构函数声明为虚函数的前提下,系统只会释放基类中动态开辟的空间,而忽略派生类中动态开辟的空间。通过将基类的析构函数声明为虚函数可以解决。
多态性
一、定义
好多书上写的比较字面的解释是:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为。但是这读起来总感觉很难理解。
我对它的理解就是:通过不同的对象在调用一个同名函数时,会产生不同的功能。当然这个同名函数本身是具有不同功能的。
二、分类
静态多态:
编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误。
比如一个简单的函数重载:
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、最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构
函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)
避免内存泄露的问题。如果基类和派生类中都有动态开辟内存,在不将析构函数声明为虚函数的前提下,系统只会释放基类中动态开辟的空间,而忽略派生类中动态开辟的空间。通过将基类的析构函数声明为虚函数可以解决。
相关文章推荐
- C++ 中MessageBox的常见用
- C++俄罗斯方块游戏 无需图形库的俄罗斯方块
- C++类的成员函数的形参列表后面的const
- c++中如何用string实现CString格式化的功能
- C++ 回调函数 --函数的接口 讲解
- C变量及函数存储类型
- C++ explicit的作用
- 如何让捕鱼游戏打中的鱼变红。
- 文章标题
- [C语言]模拟实现strcpy/strncpy/strcat/strncat/strcmp/strncmp/strstr
- FindFirstFile函数
- 处理C++源代码的程序(3)
- 处理C++源代码的程序(2)
- 处理C++源代码的程序
- c++类与对象,构造函数
- 浅析过滤敏感词过滤算法(C++)
- 文件——字节统计
- 如何在非托管C++中调用托管C#中的回调函数
- C++.NET编程体验
- C# 调用 C++