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++的基础。

相关文章
|
2月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
90 11
|
3月前
|
存储 安全 编译器
【C++核心】一文理解C++面向对象(超级详细!)
这篇文章详细讲解了C++面向对象的核心概念,包括类和对象、封装、继承、多态等。
30 2
|
2月前
|
存储 编译器 C语言
【C++】初识面向对象:类与对象详解
【C++】初识面向对象:类与对象详解
|
4月前
|
编译器 C++ 索引
C++虚拟成员-虚函数
C++虚拟成员-虚函数
|
4月前
|
存储 安全 数据处理
【C++】C++ 超市会员卡管理系统(面向对象)(源码+数据)【独一无二】
【C++】C++ 超市会员卡管理系统(面向对象)(源码+数据)【独一无二】
108 1
|
4月前
|
算法 数据可视化 C++
【C++】C++ 学生信息管理系统(源码+面向对象)【独一无二】
【C++】C++ 学生信息管理系统(源码+面向对象)【独一无二】
|
5月前
|
存储 开发框架 Java
|
5月前
|
Java C++ iOS开发
|
6月前
|
存储 JavaScript 前端开发
程序与技术分享:C++程序设计实验考试准备资料(2019级秋学期)
程序与技术分享:C++程序设计实验考试准备资料(2019级秋学期)
|
25天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
38 2