接第十四章继续讲解
![第十五章丨C++编程宝典:快速上手、深入进阶、挑战高级技巧,助你成为编程达人](https://www.xiciw.com/wp-content/uploads/2024/01/2024012819012084.webp)
8.4.4. 组装电脑系统
原理同上。依赖倒置原则。
#include <iostream>
using namespace std;
class HardDisk
{
public:
virtual void run() = 0;
};
class Memory
{
public:
virtual void run() = 0;
};
class Cpu
{
public:
virtual void run() =0 ;
};
class WDHardDisk:public HardDisk
{
public:
void run()
{
cout<<"我是西数硬盘,500g 5400r/m"<<endl;
}
};
class IntelCpu:public Cpu
{
public:
void run()
{
cout<<"我是 intel Cpu,3.4gh "<<endl;
}
};
class kingStonMem:public Memory
{
public:
void run()
{
cout<<"我是金士顿内存,16g 1333"<<endl;
}
};
class Computer
{
public:
Computer( Cpu *c,Memory *m,HardDisk *d)
:disk(d),mem(m),cpu(c){}
void work()
{
cpu->run();
mem->run();
disk->run();
}
private:
HardDisk *disk;
Memory * mem;
Cpu * cpu;
};
int main()
{
IntelCpu *pc = new IntelCpu;
kingStonMem *pm = new kingStonMem;
WDHardDisk *ph = new WDHardDisk;
Computer cpt(pc,pm,ph);
cpt.work();
delete pc;
delete pm;
delete ph;
return 0;
}
8.4.5. 企业员工信息管理系统
8.4.5.1. 需求
一个小型公司的人员信息管理系统
某小型公司,主要有四类人员:经理、技术人员、销售经理和推销员。现在,需要存储这些人员的姓名、编号、级别、当月薪水.计算月薪总额并显示全部信息。
人员编号基数为 1000,每输入一个人员信息编号顺序加 1。
程序要有对所有人员提升级别的功能。本例中为简单起见,所有人员的初始级别均为 1 级。然后进行升级,经理升为 4 级,技术人员和销售经理升为 3 级,推销员仍为 1级。
月薪计算办法是:经理拿固定月薪 8000 元;技术人员按每小时 100 元领取月薪;推销员的月薪按该推销员当月销售额的 4%提成;销售经理既拿固定月薪也领取销售提成,固定月薪为 5000 元,销售提成为所管辖部门当月销售总额的 5%。
8.4.5.2. 详细设计
父类
属性:
string 姓名、int 编号、int 级别、foat 当月薪水
static starNum;
行为:
void getPay()计算月薪总额
void disInfor()显示全部信息
8.4.5.3. 类设计
![第十五章丨C++编程宝典:快速上手、深入进阶、挑战高级技巧,助你成为编程达人](https://www.xiciw.com/wp-content/uploads/2024/01/2024013115001454.webp)
8.4.5.4.
代码实现
employee.h
#ifndef EMPLOYEE_H
#define EMPLOYEE_H
#include <iostream>
using namespace std;
class Employee
{
public:
Employee();
virtual ~Employee();
virtual void promote(int increment = 0) =0;
virtual void getPay() = 0;
virtual void disInfor() = 0;
protected:
string name;
int num;
int grade;
float salary;
static int startNum;
};
#endif // EMPLOYEE_H
manager.h
#ifndef MANAGER_H
#define MANAGER_H
#include "employee.h"
class Manager:virtual public Employee
{
public:
Manager();
~Manager();
void promote(int increment = 0);
void getPay();
void disInfor();
protected:
float fixSalay;
};
#endif // MANAGER_H
technician.h
#ifndef TECHNICIAN_H
#define TECHNICIAN_H
#include "employee.h"
class Technician:public Employee
{
public:
Technician();
~Technician();
virtual void promote(int increment = 0) ;
virtual void getPay() ;
virtual void disInfor() ;
private:
int moneyPerHour;
int hourCount;
};
#endif // TECHNICIAN_H
saleman.h
#ifndef SALESMAN_H
#define SALESMAN_H
#include "employee.h"
class SalesMan:virtual public Employee
{
public:
SalesMan();
~SalesMan();
virtual void promote(int increment = 0) ;
virtual void getPay();
virtual void disInfor();
protected:
float saleAmount;
float persent;
};
#endif // SALESMAN_H
salesmanager.h
#ifndef SALEMANAGER_H
#define SALEMANAGER_H
#include "salesman.h"
#include "manager.h"
class SaleManager:public SalesMan,public Manager
{
public:
SaleManager();
~SaleManager();
void promote(int increment = 0);
void getPay();
void disInfor();
};
#endif // SALEMANAGER_H
main.cpp
#include <iostream>
#include "employee.h"
#include "technician.h"
#include "manager.h"
#include "salesman.h"
#include "salemanager.h"
using namespace std;
int main()
{
Employee * p[] = {new Manager,new Technician,,new SalesMan,new SaleManager};
for(int i=0; i<sizeof(p)/sizeof(p[0]); i++)
{
p[i]->promote(i);
p[i]->getPay();
cout<<"-----------------"<<endl;
p[i]->disInfor();
cout<<"-----------------"<<endl;
}
return 0;
}
优化补充:
8.4.6. cocos跨平台入口分析
8.4.6.1. 代跨平台设计原理
![第十五章丨C++编程宝典:快速上手、深入进阶、挑战高级技巧,助你成为编程达人](https://www.xiciw.com/wp-content/uploads/2024/01/2024013115071451.png)
8.4.6.2. 类设计
8.4.6.3. 代码实现
CCApplicationProtocol.h
#ifndef CCAPPLICATIONPROTOCOL_H_
#define CCAPPLICATIONPROTOCOL_H_
class CCApplicationProtocol {
public:
CCApplicationProtocol(){}
virtual ~CCApplicationProtocol(){}
virtual bool applicationDidFinishLaunching() = 0;
};
#endif /* CCAPPLICATIONPROTOCOL_H_ */
AppDelegate.h
#ifndef APPDELEGATE_H_
#define APPDELEGATE_H_
#include "CCApplication.h"
class AppDelegate: private CCApplication {
public:
AppDelegate();
virtual ~AppDelegate();
bool applicationDidFinishLaunching();
};
#endif /* APPDELEGATE_H_ */
AppDelegate.cpp
#include "AppDelegate.h"
#include "iostream"
using namespace std;
AppDelegate::AppDelegate() {
// TODO Auto-generated constructor stub
}
AppDelegate::~AppDelegate() {
// TODO Auto-generated destructor stub
}
bool AppDelegate::applicationDidFinishLaunching()
{
cout<<"star game"<<endl;
}
CCApplication.h
#ifndef CCAPPLICATION_H_
#define CCAPPLICATION_H_
#include "CCApplicationProtocol.h"
class CCApplication: public CCApplicationProtocol {
public:
CCApplication();
virtual ~CCApplication();
int run();
static CCApplication * sharedApplication();
static CCApplication * sm_pSharedApplication;
};
#endif /* CCAPPLICATION_H_ */
CCApplication.cpp
#include "CCApplication.h"
#include "stddef.h"
CCApplication * CCApplication::sm_pSharedApplication = NULL;
CCApplication::CCApplication() {
// TODO Auto-generated constructor stub
sm_pSharedApplication = this;
}
CCApplication::~CCApplication() {
// TODO Auto-generated destructor stub
}
CCApplication * CCApplication::sharedApplication()
{
if(sm_pSharedApplication != NULL)
return sm_pSharedApplication;
}
int CCApplication::run()
{
applicationDidFinishLaunching();
}
main.cpp
#include "CCApplication.h"
int main()
{
AppDelegate app;
return CCApplication::sharedApplication()->run();
}
8.5. 多态实现浅浅析
8.5.1. 虚函数表
C++的多态是通过一张虚函数表(Virtual Table)来实现的,简称为 V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。
假设我们有这样的一个类:
class Base {
public:
virtual void f() { cout << "Base::f" << endl; }
virtual void g() { cout << "Base::g" << endl; }
virtual void h() { cout << "Base::h" << endl; }
};
按照上面的说法,我们可以通过 Base 的实例来得到虚函数表。 下面是实际例程:
int main()
{
cout<<sizeof(Base)<<endl; //比平时多出 4 个字节。
typedef void(*Fun)(void);
Base b;
Fun pFun = NULL;
cout << "虚函数表地址:" << (int*)(&b) << endl;
cout << "虚函数表第一个函数地址:" << (int*)*(int*)(&b) << endl;
// Invoke the first virtual function
pFun = (Fun)*((int*)*(int*)(&b));
pFun();
return 0;
}
实际运行经果如下
虚函数表地址:0012FED4
虚函数表 — 第一个函数地址:0044F148
Base::f
通过这个示例,我们可以看到,我们可以通过强行把&b 转成 int *,取得虚函数表的地
址,然后,再次取址就可以得到第一个虚函数的地址了,也就是 Base::f(),这在上面的程序
中得到了验证(把 int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调
用 Base::g()和 Base::h(),其代码如下:
(Fun)*((int*)*(int*)(&b)+0); // Base::f()
(Fun)*((int*)*(int*)(&b)+1); // Base::g()
(Fun)*((int*)*(int*)(&b)+2); // Base::h()
画个图解释一下。如下所示:
![第十五章丨C++编程宝典:快速上手、深入进阶、挑战高级技巧,助你成为编程达人](https://www.xiciw.com/wp-content/uploads/2024/01/2024013115234159.webp)
注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。
下面,我将分别说明“无覆写”和“有覆写”时的虚函数表的样子。没有覆写父类的虚函数是毫无意义的。我之所以要讲述没有覆写的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。
8.5.2. 一般继承(无虚函数覆写)
下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:
![第十五章丨C++编程宝典:快速上手、深入进阶、挑战高级技巧,助你成为编程达人](https://www.xiciw.com/wp-content/uploads/2024/01/2024013115243666.webp)
请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:
对于实例:Derive d; 的虚函数表如下:
![第十五章丨C++编程宝典:快速上手、深入进阶、挑战高级技巧,助你成为编程达人](https://www.xiciw.com/wp-content/uploads/2024/01/2024013115294333.webp)
我们可以看到下面几点:
1)虚函数按照其声明顺序放于表中。
2)父类的虚函数在子类的虚函数前面。
8.5.3. 一般继承(有虚函数覆写)
覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。
![第十五章丨C++编程宝典:快速上手、深入进阶、挑战高级技巧,助你成为编程达人](https://www.xiciw.com/wp-content/uploads/2024/01/2024013115301076.webp)
为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:
![第十五章丨C++编程宝典:快速上手、深入进阶、挑战高级技巧,助你成为编程达人](https://www.xiciw.com/wp-content/uploads/2024/01/2024013115261992.webp)
我们从表中可以看到下面几点,
1)覆写的 f()函数被放到了虚表中原来父类虚函数的位置。
2)没有被覆盖的函数依旧。这样,我们就可以看到对于下面这样的程序,
Base *b = new Derive();
b->f();
由 b 所指的内存中的虚函数表的 f()的位置已经被 Derive::f()函数地址所取代,于是在实际调用发生时,是 Derive::f()被调用了。这就实现了多态。
8.5.4. 静态代码发生了什么?
Base *b = new Derive();
b->f();
当编译器看到这段代码的时候,并不知道 b 真实身份。编译器能作的就是用一段代码
代替这段语句。
1,明确 b 类型。
2,然后能过指针虚函数表的指针 vptr 和偏移量,匹配虚函数的入口。
3,根据入口地址调用虚函数。
8.5.5. 评价多态
1,实现在动态绑定。
2,些许的牺牲了一些空间和效率。
8.5.6. 常见问答
为什么虚函数必须是类的成员函数:
虚函数诞生的目的就是为了实现多态,在类外定义虚函数毫无实际用处。
为什么类的静态成员函数不能为虚函数:
如果定义为虚函数,那么它就是动态绑定的,也就是在派生类中可以被覆盖的,这与静态成员函数的定义(:在内存中只有一份拷贝;通过类名或对象引用访问静态成员)本身就是相矛盾的。
为什么构造函数不能为虚函数:
因为如果构造函数为虚函数的话,它将在执行期间被构造,而执行期则需要对象已经建立,构造函数所完成的工作就是为了建立合适的对象,因此在没有构建好的对象上不可能执行多态(虚函数的目的就在于实现多态性)的工作。在继承体系中,构造的顺序就是从基类到派生类,其目的就在于确保对象能够成功地构建。构造函数同时承担着虚函数表的建立,如果它本身都是虚函数的话,如何确保 vtbl 的构建成功呢?
注意:当基类的构造函数内部有虚函数时,会出现什么情况呢?结果是在构造函数140中,虚函数机制不起作用了,调用虚函数如同调用一般的成员函数一样。当基类的析构函数内部有虚函数时,又如何工作呢?与构造函数相同,只有“局部”的版本被调用。但是,行为相同,原因是不一样的。构造函数只能调用“局部”版本,是因为调用时还没有派生类版本的信息。析构函数则是因为派生类版本的信息已经不可靠了。我们知道,析构函数的调用顺序与构造函数相反,是从派生类的析构函数到基类的析构函数。当某个类的析构函数被调用时,其派生类的析构函数已经被调用了,相应的数据也已被丢失,如果再调用虚函数的派生类的版本,就相当于对一些不可靠的数据进行操作,这是非常危险的。因此,在析构函数中,虚函数机制也是不起作用的。
#include <iostream>
using namespace std;
class A
{
public:
A()
{
p = this;
p->func();
}
virtual void func()
{
cout<<"aaaaaaaaaaaaaaaa"<<endl;
}
private:
A *p;
};
class B:public A
{
public:
void func()
{
cout<<"bbbbbbbbbbbbbbbbb"<<endl;
}
};
int main()
{
B b;
return 0;
}
本章完,下一大章我们讲解模板(Templates)