目录



虚函数


普通成员函数前加关键字virtual,称为虚函数


覆盖
  • 子类成员函数和基类的虚函数具有相同函数原型,该成员函数也就是虚函数,无论其是否带有virtual关键字,都对基类虚函数构成覆盖
覆盖条件

  • 函数为成员函数(非静态)
  • 基类使用virtual
  • 原型严格相同

虚函数程序示例:

#include<iostream>
#include<string>
using namespace std;
//基类 Animal
class Animal
{
public:
Animal(string name) :m_name(name) { }
void say()
{
cout << m_name << ":$%^&@" << endl;
}
protected:
string m_name;
};

//派生类 Dog
class Dog :public Animal
{
public:
Dog(string name) :Animal(name) {}
void say()
{
cout << m_name << ":汪汪汪" << endl;
}
};

int main()
{
Animal *a = new Animal("动物");
a->say(); //Animal类说 $%^&@
delete a;

a = new Dog("旺财"); //隐式转换成基类对象
a->say();

delete a;
return 0;
}
打印结果:
动物:$%^&@
旺财:$%^&@

加关键字virtual:

virtual void say()  //函数前加关键字virtual,形成虚函数
{
cout << m_name << ":$%^&@" << endl;
}
int main()
{
Animal *a = new Animal("动物");
a->say();
delete a;

a = new Dog("旺财"); //ISA 隐式转换成基类对象
//a调用的是虚函数,并且形成覆盖,a实际指向Dog类对象 调用Dog::say()
/*
* 基类成员函数前加virtual 该成员函数成为虚函数
* 没有virtual 对象调用函数看声明类型 有虚函数并且形成覆盖,对象调用函数看实际内存中类型
*/
a->say();
delete a;
return 0;
}
打印结果
动物:$%^&@
旺财:汪汪汪

虚函数表

编译器通过指针或引用调用虚函数,不会立即生成函数调用指令,而是用一段代码代替确定真实类型,找到虚函数表从而找到入口地址,根据入口地址调用函数。

C++进阶学习---多肽_子类

#include<iostream>
using namespace std;
class N {
public:
void foo() {
cout << "N::foo" << endl;
}
void bar() {
cout << "N::bar" << endl;
}
int m_a;
int m_b;
};

class A {
public:
virtual void foo() {
cout << "A::foo" << endl;
}
virtual void bar() {
cout << "A::bar" << endl;
}
int m_a;
int m_b;
};

class B :public A {
public:
void foo() {
cout << "B::foo" << endl;
}
void bar() {
cout << "B::bar" << endl;
}
};

int main() {
/*
* 实例化三个对象,在监视里面查看内存
*/
N n;
A a;
B b;
//offsetof(s,m) 计算m相对s的偏移地址
cout << "sizeof(N):" << sizeof(N) << ",m_a:" << offsetof(N, m_a) << ",m_b:" << offsetof(N, m_b) << endl;
cout << "sizeof(A):" << sizeof(A) << ",m_a:" << offsetof(A, m_a) << ",m_b:" << offsetof(A, m_b) << endl;
cout << "sizeof(B):" << sizeof(B) << ",m_a:" << offsetof(B, m_a) << ",m_b:" << offsetof(B, m_b) << endl;
/*
* 从上面可以得知有虚函数的类,比没有虚函数的类多了一个四字节的东东,那个东东就是一个指针,指向虚函数表
* 不管有多少个虚函数,一个对象只有一个指向虚函数表的指针
*/

//自己定义一个虚函数表
typedef void(*VFUN) (void *); // 将void* 重定义为函数指针
typedef VFUN* VPTR; // 重定义指向虚函数表指针的类型

VPTR vf_ptr = *(VPTR*)&a; //拿到对象a的虚函数表指针

cout << vf_ptr << "[" << vf_ptr[0] << "," << vf_ptr[1] << "]" << endl;
//通过虚函数表调用虚函数
vf_ptr[0](&a); //&a相当于创一个this指针过去
vf_ptr[1](&a); //&a相当于创一个this指针过去

cin.get();
return 0;
}

打印结果

sizeof(N):8,m_a:0,m_b:4
sizeof(A):12,m_a:4,m_b:8
sizeof(B):12,m_a:4,m_b:8
00CB8B34[00CB141A,00CB142E]
A::foo
A::bar

监视里面也可以很好的看到A,B两个类的对象有一个__vfptr的指针,指向两个虚函数,

C++进阶学习---多肽_子类_02

多态

单台和多态
#include<iostream>
using namespace std;
//单肽,一个函数一个功能
int add(int x, int y)
{
return x + y;
}

int sub(int x, int y)
{
return x - y;
}

//利用函数指针实现多态
int calc(int x, int y, int(*pfun)(int, int))
{
return pfun(x, y);
}

int main()
{
cout << calc(2, 3, add) << endl;
cout << calc(2, 3, sub) << endl;
cin.get();
return 0;
}
多态理解

  • 子类提供了对基类虚函数的有效覆盖,通过指向子类对象的基类指针,或者引用子类对象的基类引用,调用该虚函数,实际上调用的将是子类中的覆盖版本,而非基类中的原始版本
  • 一般函数调用是通过调用对象类型决定,而多态这是通过调用者指针或引用的实际目标对象的类型决定
  • 多态只能通过引用或指针表现,且指针更灵活
  • 除构造和析构函数,通过基类中this指针可以满足多态

其实在虚函数里面就已经讲解到多肽了

#include<iostream>
#include<string>
using namespace std;
//基类 Animal
class Animal
{
public:
Animal(string name) :m_name(name) { }
virtual void say()
{
cout << m_name << ":$%^&@" << endl;
}
protected:
string m_name;
};

//派生类 Dog
class Dog :public Animal
{
public:
Dog(string name) :Animal(name) {}
void say()
{
cout << m_name << ":汪汪汪" << endl;
}
};

int main()
{
Animal *a = new Animal("动物");
a->say(); //Animal 说 $%^&@
delete a;

a = new Dog("旺财"); //隐式转换成基类对象
a->say(); //旺财 说 汪汪汪
delete a;
/*
* 指针a通过指针指向的改变实现多肽
* a指向Animal时说 $%^&@
* a指向Dog时说 汪汪汪
*/
cin.get();
return 0;
}
多肽的好处

  • 应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性
  • 派生类的功能可以被基类的指针或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。

虚析构


只有虚析构,没有虚构造


class A
{
public:
A()
{
cout << "A构造" << endl;
}
~A()
{
cout << "A析构" << endl;
}
};

class B :public A
{
public:
B()
{
p = new int(10);
cout << "B构造" << endl;
}
~B()
{
cout << "B析构" << endl;
delete p;
}

private:
int *p;
};

int main()
{
A* a = new B; //隐式转换为基类对象
delete a;
cin.get();
return 0;
}

不加virtual 修饰~A(),打印结果

A构造
B构造
A析构

内存泄漏了,B类里面的*p指向的堆内存没有释放

解决办法:

class A
{
public:
A()
{
cout << "A构造" << endl;
}
virtual ~A() //虚析构
{
cout << "A析构" << endl;
}
};

析构前面加上关键字virtual构成虚析构

A构造
B构造
B析构
A析构

注意:


  • 如果父类的析构函数不是虚析构,父类的指针指向子类时,delete掉父类的指针,只调动父类的析构函数,而不调动子类的析构函数
  • 如果父类的析构函数是虚析构,父类的指针指向子类时,delete掉父类的指针,先调动子类的析构函数,再调动父类的析构函数

纯虚函数


形如virtual 返回值 函数名(形参表)=0;的虚函数,称为纯虚函数或抽象方法


class Base
{
virtual void fun(int) = 0; //纯虚函数
};

抽象类


  • 至少拥有一个纯虚函数的类成为抽象类
  • 抽象类不能实例化为对象
  • 抽象类子类不对基类中全部纯虚函数提供有效覆盖,子类也是抽象类

class Base
{
virtual void foo(int) = 0; //纯虚函数
virtual void fun(int) = 0;
};//抽象类

//抽象类子类不对基类中全部纯虚函数提供有效覆盖,子类也是抽象类
class B:public Base
{
void foo(int) {}
};
  • 不能实例化抽象类

纯抽象类


全部由纯虚函数构成的抽象类成为纯抽象类或接口


继承多肽示例程序

某小型公司,主要有四类员工( Employee ):经理( Manager )、技术人员( Technician )、销售经理( SalesManager )和推销员( SalesMan )。

现在 , 需要存储这些人员的姓名( name )、编号( id )、当月薪水( salary )。计算月薪总额并显示全部信息。人员编号基数为 1000 ,每输入一个人员工信息编号顺序加 1 。

月薪计算办法是:

经理拿固定月薪 8000 元;

技术人员按每小时 100 元领取月薪;

推销员的月薪按该推销员当月销售额的 4% 提成;

销售经理既拿固定月薪也领取销售提成,固定月薪为 5000 元,销售提成为所管辖部门当月销售总额的 5% 。

继承关系图:

C++进阶学习---多肽_子类_03

#include<string>
#include<iostream>
using namespace std;
class Employee
{
protected:
string m_name;
int m_id;
float m_salary;
static int Id;
public:
Employee() :m_name(""), m_salary(0.0f), m_id(++Id){}
virtual void getSalary() = 0;//纯虚函数
void show()
{
cout << "姓名:" << m_name << endl;
cout << "员工ID:" << m_id << endl;
cout << "工资:" << m_salary << endl << endl;
}
virtual ~Employee() {}
}; //员工类

int Employee::Id = 1000;

class Manager : virtual public Employee
{
protected:
float m_baseSalary; //基本工资
public:
Manager(string name)
{
m_name = name;
m_baseSalary = 8000.0f; //经理拿固定月薪 8000 元
}
void getSalary()
{
m_salary = m_baseSalary;
}
~Manager() {}
}; //经理类

class Technician : public Employee
{
private:
int m_hour;
public:
Technician(string name, int hour)
{
m_name = name;
m_hour = hour;
}
void getSalary()
{
m_salary = m_hour * 70; //技术人员按每小时 100 元领取月薪
}
~Technician() {}
}; //技术人员

class SalesMan : virtual public Employee
{
private:
float m_Count; //销售额
public:
SalesMan(string name, float Count)
{
m_name = name;
m_Count = Count;
m_partCount += m_Count;
}
void getSalary()
{
m_salary = m_Count * 0.04; //推销员的月薪按该推销员当月销售额的 4% 提成
}
~SalesMan() {}
protected:
static float m_partCount; //总销售
}; //推销员
float SalesMan::m_partCount = 0.0f;

class SalesManager : public Manager, public SalesMan
{
public:
SalesManager(string name) :Manager(name), SalesMan(name, 0)
{
m_name = name;
m_baseSalary = 5000;
}
void getSalary()
{
//销售经理既拿固定月薪也领取销售提成,固定月薪为 5000 元,销售提成为所管辖部门当月销售总额的 5% 。
m_salary = m_baseSalary + m_partCount * 0.05;
}
~SalesManager() {}
}; //销售经理

int main()
{
Employee* emp[5] = { 0, };
emp[0] = new Manager("经理");
emp[0]->getSalary();
emp[0]->show();

emp[1] = new Technician("技术人员", 99);
emp[1]->getSalary();
emp[1]->show();

emp[2] = new SalesMan("销售人员", 60000);
emp[2]->getSalary();
emp[2]->show();

emp[3] = new SalesMan("销售人员", 900000);
emp[3]->getSalary();
emp[3]->show();

emp[4] = new SalesManager("销售经理");
emp[4]->getSalary();
emp[4]->show();

for (int i = 0; i < 5; ++i)
{
delete emp[i];
}
getchar();
return 0;
}