C++的重要性质:虚函数和多态性
来源:互联网 发布:腾亿网络 编辑:程序博客网 时间:2024/05/16 11:11
1. 封装、继承和this指针
1.1 封装(Encapsulation)
把数据成员声明为private,不允许外界随意存取,只能通过特定的接口来操作,这就是面向对象的封装特性。
1.2 继承(Inheritance)
子类“暗自(implicit)”具备了父类的所有成员变量和成员函数,包括private属性的成员(虽然没有访问权限)。
1.3 this指针
矩形类CRect如下:
class CRect{private: int m_color;public: void setcolor(int color) { m_color=color; }};
有两个CRect对象rect1和rect2,各有各自的m_color成员变量。rect1.setcolor和rect2.setcolor调用的是唯一的CRect::setcolor成员函数,却处理了各自的m_color。
这是因为成员函数是属于类的而不是属于某个对象的,只有一个。成员函数都有一个隐藏参数,名为this指针,当你调用
rect1.setcolor(2);rect2.setcolor(3);
CRect.setcolor(2,(CRect*) &rect1);CRect.setcolor(3,(CRect*) &rect2);
2. 虚函数与多态
2.1 多态性(Polymorphism)
以相同的指令却调用了不同的函数,这种性质成为Polymorphism,意思是“the ability to assume many forms”(多态)。有如下四个类:#include <string.h>class CEmployee //职员{ private: char m_name[30]; public: CEmployee(); CEmployee(const char* nm) { strcpy(m_name, nm); }};//----------------------------------// 时薪职员是一种职员class CWage : public CEmployee{ private : float m_wage;//钟点费 float m_hours;//每周工时 public : CWage(const char* nm) : CEmployee(nm) { m_wage = 250.0; m_hours = 40.0; } void setWage(float wg) { m_wage = wg; } void setHours(float hrs) { m_hours = hrs; } float computePay();};//----------------------// 销售员是一种时薪职员class CSales : public CWage{ private : float m_comm;//佣金 float m_sale;//销售额 public : CSales(const char* nm) : CWage(nm) { m_comm = m_sale = 0.0; } void setCommission(float comm) { m_comm = comm; } void setSales(float sale) { m_sale = sale; } float computePay();};//------------------------// 经理也是一种职员class CManager : public CEmployee{ private : float m_salary;//薪水 public : CManager(const char* nm) : CEmployee(nm) { m_salary = 15000.0; } void setSalary(float salary) { m_salary = salary; } float computePay();};//---------------------------------------------------------------void main(){ CManager aManager("陳美靜"); CSales aSales("侯俊傑"); CWage aWager("曾銘源");}1)则aManageer,aSale和aWager含有的变量如下图:
注意:子类确实继承了父类的private成员,只是没有访问的权限。要访问父类的成员函数,必须使用scope resolution operator(::)明白指出。
a)计算侯俊杰底薪应该是
a.Sales.CWage::computePay();b)计算侯俊杰的全薪应该是
aSales.computePay();2) 父类与子类的转换
//销售员是时薪职员之㆒,因此这样做是合理的:aWager = aSales; // 合理,销售员必定是时薪职员。//这样就不合理:aSales = aWager; // 错误,时薪职员未必是销售员。//如果你㆒定要转换,必须使用指标,并且明显做型别转换(cast)动作 :CWage* pWager;CSales* pSales;CSales aSales("侯俊杰");pWager = &aSales; // 把一个基类指针指向子类的对象,合理且自然。pSales = (CSales *)pWager; // 强迫转型。语法上可以,但不符合现实生活。3)到底会调用那个函数?
看下面代码:
CSales aSales("侯俊杰");CSales* pSales;CWage* pWager;pSales = &aSales;pWager = &aSales; // 以基类指针指向子类对象pWager->setSales(800.0); // 错误(编译器会检测出来),// 因为 CWage 并没有定义 setSales 函数pSales->setSales(800.0); // 正确,调用 CSales::setSales 函数虽然pSales 和pWager 指向同一对象,但却因指针的原始类型不同而使两者之间有了差异。如果你一个“基类指针”指向派生类的对象,那么经由该指针你只能够调用基类所定义的函数。
再看下面的代码:
pWager->computePay(); // 调用 CWage::computePay()pSales->computePay(); // 调用 CSales::computePay()虽然aSales和pWager实际上指向的是同一个对象,但是两者调用computePay却不同。到底应该调用哪个函数必须视指针的类型而定,与指针实际指向的对象无关。
4)总结
- 如果你一个“基类指针”指向派生类的对象,那么经由该指针你只能够调用基类所定义的函数。
- 如果要用一个派生类指针指向一个基类对象,你必须做显式类型转换(explict cast),这种做法不推荐。
- 如果基类和派生类定义了相同名称的成员函数,那么通过指针调用成员函数时,到底会调用哪一个函数,必须视指针的类型而定,与指针实际指向的对象无关。
2.2 虚函数
如果将上述4个类中的computePay函数前都加上virtual保留字:CEmployee* pEmp;CWage aWager("曾铭源");CSales aSales("侯俊杰");CManager aManager("陈美静");pEmp = &aWager;cout << pEmp->computePay(); // 调用的是 CWage::computePaypEmp = &aSales;cout << pEmp->computePay(); // 调用的是 CSales::computePaypEmp = &aManager;cout << pEmp->computePay(); // 调用的是 CManager::computePay我们通过相同的指令“pmp->computePay()”却调用了不同的函数,这就是虚函数的作用:实现多态性,通过指向派生类的基类指针,访问派生类中同名覆盖成员函数。
2.3 类与对象大解剖
为了达到动态绑定的目的,C++编译器通过某个表格,在执行时"间接"调用实际上欲绑定的函数。这样的表格成为虚函数表(常被称为vtable)。每一个内含虚函数的类,编译器都会为它做出一个虚函数表,表中的每一个元素都指向一个虚函数的地址。此外,编译器当然也会类加上一项成员变量,是一个指向该虚函数表的指针(常被成为vptr)。#include <iostream.h>#include <stdio.h>class ClassA{public:int m_data1;int m_data2;void func1() { }void func2() { }virtual void vfunc1() { }virtual void vfunc2() { }};class ClassB : public ClassA{public:int m_data3;void func2() { }virtual void vfunc1() { }};class ClassC : public ClassB{public:int m_data1;int m_data4;void func2() { }virtual void vfunc1() { }};void main(){cout << sizeof(ClassA) << endl;cout << sizeof(ClassB) << endl;cout << sizeof(ClassC) << endl;ClassA a;ClassB b;ClassC c;b.m_data1 = 1;b.m_data2 = 2;b.m_data3 = 3;c.m_data1 = 11;c.m_data2 = 22;c.m_data3 = 33;c.m_data4 = 44;c.ClassA::m_data1 = 111;cout << b.m_data1 << endl;cout << b.m_data2 << endl;cout << b.m_data3 << endl;cout << c.m_data1 << endl;cout << c.m_data2 << endl;cout << c.m_data3 << endl;cout << c.m_data4 << endl;cout << c.ClassA::m_data1 << endl;cout << &b << endl;cout << &(b.m_data1) << endl;cout << &(b.m_data2) << endl;cout << &(b.m_data3) << endl;cout << &c << endl;cout << &(c.m_data1) << endl;cout << &(c.m_data2) << endl;cout << &(c.m_data3) << endl;cout << &(c.m_data4) << endl;cout << &(c.ClassA::m_data1) << endl;}执行结果及说明:
对象a.b.c中的内容如下图所示:
- C++类的成员函数可以想象为C语言中的函数。它时被编译器改过名称(加入了类名::,如上图中灰色框内),并加了一个this指针的参数。所以成员函数并不在对象的内存区块种,成员函数为该类所有的对象共享。
- 如果基类中含有虚函数,那么每一个由此类派生出来的类的对象都一个这么一个vptr。当我们通过这个对象调用虚函数时,事实上是通过vptr找到虚函数表,再找出虚函数的真正地址。
- 派生类会继承基类的虚函数表,当我们再派生类中改写虚函数时,虚函数表就受了影响:表中元素所指的函数地址将不再是基类的函数地址,而是派生类的函数地址。
2.4 虚析构函数
基类的析构函数一般写成虚函数,这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。否则会造成内存泄露。
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; };};ClxBase *pTest = new ClxDerived;pTest->DoSomething();delete pTest;
输出:
Do something in class ClxDerived!Output from the destructor of class ClxDerived!
参考资料:
主要参考 侯俊杰,《深入浅出MFC》第二章 C++的重要性质- C++的重要性质:虚函数和多态性
- 虚函数,实现多态性的重要机制
- c++--多态性和虚函数
- 虚函数重要性质总结
- 反正切函数的一个重要性质
- 多态性和虚函数
- 虚函数和多态性
- 虚函数和多态性
- 虚函数和多态性
- 多态性和虚函数
- 多态性和虚函数
- 多态性和虚函数
- 多态性和虚函数
- 多态性和虚函数
- 虚函数和多态性
- 多态性和虚函数
- 多态性和虚函数
- 多态性和虚函数
- 关于Win7系统hiberfil.sys及pagefile.sys两个隐藏文件
- Encounter ORA-03113 Error when create spfile from pfile
- poj 1625 AC自动机上的DP
- C++笔记(2)小问题
- 白天求生存,晚上谋发展
- C++的重要性质:虚函数和多态性
- C# 如何用socket进行通信
- 百度音乐接口
- 局部线性嵌入(LLE)算法解释
- 查询某个用户的表信息
- spring环境搭建
- 流形学习演示
- ProcMeter3 使用经验
- MyBatis整体预览(一)