c++面向对象程序设计基础教程————多态性和虚函数(二)

简介: c++面向对象程序设计基础教程————多态性和虚函数(二)

成员函数中调用虚函数


一个基类或派生类的成员函数中可以直接调用该类等级中的虚函数;


e107f41cf0a543fc00e6af71b55a078d_5d868835744f4d5d87f03b02a3f601f3.png


#include<iostream>
using namespace std;
class Base
{
public:
  virtual void func1()         //虚函数
  {
  cout << "Tis is Base class";
  }
  void func2() { func1(); }     //调用了虚函数;
};
//一般类中的成员都是通过对象来调用,但是这里用虚函数就可以,除了虚函数,还有就是使用静态static关键字,表明他已经被被分配内存;
class Subclass :public Base
{
public:
  virtual void func1()
  {
  cout << "This is Subclass func1()";
  }
};
int main()
{
  Subclass sc;
  sc.func2();
  return 0;
}


上面的例子中,我们可以得出,在满足公有继承的情况下,成员函数中调用虚函数使用的是动态联编;


构造函数和析构函数中调用虚函数


在构造函数和析构函数中调用虚函数时,采用静态联编,即它们所调用的虚函数是自己的类定义的函数。如果在自己的类中没有实现这个函数,则调用的时基类中虚函数,但绝不是任何在派生类中重新定义的虚函数。这是因为在建立派生类的对象时,它所包含的基类成员在派生类中定义的成员建立之前被建立。在对象撤销时,该对象所包含的在派生类中定义的成员要先与基类成员的撤销。

示例代码:


#include<iostream>
using namespace std;
class Base
{
public:
  Base() { func1(); }       //构造函数调用虚函数,静态联编;
  virtual void func1()         //虚函数;
  {
  cout << "This is Base func1()" << endl;
  }
  virtual void func2()
  {
  cout << "This is Base func2()" << endl;
  }
  ~Base() { func2(); }     //析构函数调用虚函数,静态联编;
};
class Subclass :public Base
{
public:
  virtual void fnc1()
  {
  cout << "This is Subclass func1()" << endl;
  }
  virtual void func2()
  {
  cout << "This is Subclass func2()" << endl;
  }
};
int main()
{
  Subclass sc;
  cout << "Exit main" << endl;
  return 0;
}


e2c9579f5812253059275ab70ebae8a5_66e0eaea07be44e6ad659cf248f23ad4.png


上面的程序中,我们定义了一个派生类对象,派生类中没有构造函数,所以调用了基类中的构造函数,基类中的构造函数调用了它自身定义的虚函数,这里是静态联编。


纯虚函数和抽象类


当虚函数没有被派生类重新定义时,将使用基类中的虚函数。然而基类常用来表示一些抽象的概念,基类中没有有意义的虚函数定义。另外,在某些情况下,需要一个虚函数被所有派生类覆盖。为了处理上述两种情况,可以将虚函数定义为纯虚函数,相应的类就变成了抽象类。


纯虚函数


如果不能在基类中给出有意义的虚函数的实现,但又必须让基类为派生类提供一个公共界面函数。这时可以将它说明为纯虚函数,留给派生类去实现,说明纯虚函数的格式:

virtual 返回类型 函数名(参数)=0;

纯虚函数的定义是在虚函数定义的基础上再让函数等于0,这只是表示纯虚函数的形式,并不是说他的返回值为0;


d34b4fe8893b9797bdc2de0b37d3e0bc_5c3903102e6f4775aeecd312b7759bbc.png


#include<iostream>
const double PI = 3.14;
using namespace std;
class Figure
{
public:
  Figure() {};
  virtual double area()const = 0;    //定义为纯虚函数;如果不是纯虚函数,就要返回一个double值;
};
class Circle :public Figure
{
public:
  Circle(double myr) { R = myr; }
  virtual double area()const { return PI * R * R; }         //定义为虚函数;
protected:
  double R;
};
class Rectangle :public Figure
{
public:
  Rectangle(double myl, double myw) { L = myl; W = myw; }
  virtual double area()const { return L * W; }   //定义为虚函数;
private:
  double L ,W;
};
void fun(Figure& p)
{
  cout << p.area() << endl;
}
int main()
{
  Circle c(3.0);
  cout << "Area of cicle is";
  fun(c);
  Rectangle rec(4.0, 5.0);
  cout << "Area of rectangle is";
  fun(rec);
  return 0;
}


上面的代码中,Figure中的虚函数仅仅起到为派生类提供接口的作用。


抽象类


一个类可以说明多个纯虚函数,对于包含有纯虚函数的类被成为抽象类。一个抽象类只能作为基类来派生新类,不能说明抽象类的对象。应为抽象类中有一个或者多个函数没有被定义,也不能用作参数类型,函数返回类型或显示类型类型。但剋以说明指向抽象类对象的指针(和引用),以支持运行时的多态性;


例如:
Figure fig; //错误;
Figure func1();//错误;
int func2(Figure);  //错误;
void func&(Figure&p);//正确;


虚析构函数


一个类中经所有的成员函数总是有益的,它除了会增加一些资源开销,没有其他坏处。但设置虚函数有许多限制,一般只有非静态的成员函数才可以说明为虚函数。除此之外,析构函数也可以是虚函数,并且最好在动态联编的时候说明为虚函数;


虚析构函数的定义和使用

c++目前不支持构造虚构造函数。由于析构函数不能有参数,所以一个类只能有一个虚析构函数;


格式:

virtual ~<类名>()

入宫一个类的析构函数是虚函数,那么,由它派生而来的所有子类的析构函数也是虚函数。如果析构函数是虚函数,则这个调用是动态联编;


f4d4a1a23b2b19938f730ae109d94b5d_9eacf8a73feb45b69fb45ee5b3992d34.png


#include<iostream>
using namespace std;
class Base
{
public:
  Base(){}
  virtual ~Base() { cout << "Base destructor is called" << endl; }
};
class Subclass :public Base
{
public:
  Subclass(){}
  ~Subclass();
};
Subclass::~Subclass()
{
  cout << "Subclass destructor is called" << endl;
}
int main()
{
  Base* b = new Subclass;
  delete b;
  return 0;
}


dc6f3b03b1674b53fd3b56350b363f63_973ac7861bfc420c9558019aa49d1eed.png


上面的程序中,我们构造了虚析构函数,那么我们构造虚析构函数的意义又在哪里?


虚析构函数的必要性


如果我们在类中定义了一个虚函数,我们在定义了一个对象后,如果要删除这个对象(delete),那么就会自动调用析构函数,也就是说,为了满足能够删除所有的成员函数,也就是构造虚析构函数;


#include<iostream>
using namespace std;
class Base
{
public:
  Base(){}
  ~Base() { cout << "Base destructor is called" << endl; }
};
class Subclass :public Base
{
public:
  Subclass();
  ~Subclass();
private:
  int* ptr;
};
Subclass::Subclass()
{
  ptr = new int;
}
Subclass::~Subclass()
{
  cout << "Subclass destructor is called" << endl;
  delete ptr;
}
int main()
{
  Base* b = new Subclass;
  delete b;
  return 0;
}


上面的程序我们会发现,用delete删除对象的时候,只调用了基类的析构函数,尾调用派生类的析构函数,,这造成ptr的内存未得到释放,现在我们将~Base()改为虚析构函数;看它的运行:


为什么把析构函数改为虚函数就会成功?这是因为将析构函数改为虚函数后,在撤销对象的时候,会先执行派生类的析构函数,由于这里采用了动态联编,所以才会得到这个结果;


应用实例


编写一个小型公司的工资管理程序,该公司主要有4类人员:经理。兼职技术人员,销售人员,销售经理。经理固定月薪8000,尖子技术人员100元每小时,销售员为当月销售额的4%,销售经理保底工资为5000,加上部门销售额的5%;

代码实现:


#include<iostream>
#include<string>
using namespace std;
//基类Employee的定义
class Employee
{
protected:
  int no;  //编号;     
  string name; //姓名
  float salary;  //月星总额
  static int totalno;  //编号最大值
public:
  Employee()
  {
  no = totalno++;        //输入员工号加1;
  cout << "职工姓名:";
  cin >> name;
  salary = 0.0;   //总额赋值为0;
  }
  virtual void pay() = 0;   //计算月薪函数;
  virtual void display() = 0;  //输出员工信息函数;
};
//兼职技术人员类Technician的定义
class Technician :public Employee
{
private:
  float hourlyrate;    //每小时酬金;
  int workhours;   //当月工作时数;
public:
  Technician()   //构造函数;
  {
  hourlyrate = 100;         //每小时酬薪100;
  cout << name << "本月工作时间:";
  cin >> workhours;
  }
  void pay()
  {
  salary = hourlyrate * workhours;    //计算月薪,按小时计算;
  }
  void display()
  {
  cout << "兼职技术员:" << name << ",编号:";
  cout << no << ",本月工资:" << salary << endl;
  }
};
//销售人员类Salesman的定义
class Salesman :virtual public Employee         //派生类,销售员;
{
protected:
  float commrate;     //按销售额提取酬金的百分比;
  float sales;         //当月销售额;
public:
  Salesman()        //构造函数;
  {
  commrate = 0.04f;     //销售提成比例为4%;
  cout << name << "本月销售额:";
  cin >> sales;
  }
  void pay()           //计算销售员月薪函数;
  {
  salary = sales * commrate;     //月薪=本月销售额*销售提成比例;
  }
  void display()  //显示销售员信息函数;
  {
  cout << "销售员:" << name << ",编号:";
  cout << no << ",本月工资:" << salary << endl;
  }
};
class Manager :virtual public Employee
{
protected:
  float monthlypay;      //固定月薪;
public:
  Manager()
  {
  monthlypay = 8000;
  }
  void pay()           //计算经理月薪总数;
  {
  salary = monthlypay;          //月薪总额;
  }
  void display()
  {
  cout << "经理:" << name << "编号";
  cout << no << ",本月工资:" << salary << endl;
  }
};
//销售经理类Salesmanager的定义:
class Salesmanager :public Manager, public Salesman
{
public:
  Salesmanager()
  {
  monthlypay = 5000;
  commrate = 0.005f;
  cout << name << "所有部门月销售量:";
  cin >> sales;
  }
  void pay()
  {
  salary = monthlypay + commrate * sales;
  }
  void display()
  {
  cout << "销售经理:" << name << ",编号:" << no << ",本月工资:" << salary << endl;
  }
};
int Employee::totalno = 10000;
//主函数;
int main()
{
  Manager m1;
  Technician t1;
  Salesman s1;
  Salesmanager sm1;
  Employee* em[4] = { &m1,&t1,&s1,&sm1 };
  cout << "上述人员的基本信息:" << endl;
  for (int i = 0; i < 4; i++)
  {
  em[i]->pay();
  em[i]->display();
  }
  return 0;
}


b49fc4718fa8edb779e7bcf8e90241cf_59504be5b21144f19b41e22aba494128.png


上面的程序就是一个简单的应用,公司的工资管理系统。c++的灵魂就是动态联编,所以掌握好动态联编是学好c++的基础。

相关文章
|
15天前
|
编译器 开发工具 C++
Dev-C++详细安装教程及中文设置(附带安装包链接)
Dev-C++详细安装教程及中文设置(附带安装包链接)
40 0
|
27天前
|
C++
9. C++虚函数与多态
9. C++虚函数与多态
26 0
|
1月前
|
程序员 API 数据库
【Cmake工程 库相关教程 】深入理解CMake工程C/C++ 库管理技巧
【Cmake工程 库相关教程 】深入理解CMake工程C/C++ 库管理技巧
60 0
|
1月前
|
存储 并行计算 前端开发
【C++ 函数 基础教程 第五篇】C++深度解析:函数包裹与异步计算的艺术(二)
【C++ 函数 基础教程 第五篇】C++深度解析:函数包裹与异步计算的艺术
39 1
|
1月前
|
数据安全/隐私保护 C++ 容器
【C++ 函数 基础教程 第五篇】C++深度解析:函数包裹与异步计算的艺术(一)
【C++ 函数 基础教程 第五篇】C++深度解析:函数包裹与异步计算的艺术
47 0
|
29天前
|
Java 程序员 Maven
【C/C++ CommonAPI入门篇】深入浅出:CommonAPI C++ D-Bus Tools 完全使用教程指南
【C/C++ CommonAPI入门篇】深入浅出:CommonAPI C++ D-Bus Tools 完全使用教程指南
58 0
|
7天前
|
C++
面向对象的C++题目以及解法2
面向对象的C++题目以及解法2
13 1
|
17天前
|
存储 人工智能 机器人
【C++面向对象】C++图书管理系统 (源码)【独一无二】
【C++面向对象】C++图书管理系统 (源码)【独一无二】
|
22天前
|
存储 人工智能 BI
【C++面向对象】C++银行卡管理系统(源码+论文)【独一无二】
【C++面向对象】C++银行卡管理系统(源码+论文)【独一无二】
|
29天前
|
存储 程序员 编译器
【C++ 模板类与虚函数】解析C++中的多态与泛型
【C++ 模板类与虚函数】解析C++中的多态与泛型
46 0

热门文章

最新文章