多态(Polymorphism),是面向对象程序设计(OOP,Object-Oriented Programming)的重要特征。
多态的定义:
多态的实现方式有:
(1)函数重载
(2)运算符重载
(3)模版
(4)虚函数
前三种我们称为静态绑定,第四种称为动态绑定。
静态绑定:绑定过程出现在编译阶段,在编译期就已确定要调用的函数;
动态绑定:绑定过程工作在运行阶段时执行,在程序运行时才确定将要调用的函数。
(只有通过基类指针或引用调用虚函数才能引发动态绑定)
虚函数的概念:在基类中冠以关键字“virtual”的成员函数。
虚函数的定义:
(1)virtual 函数返回值类型函数名称 (参数列表)
(2)如果一个函数在基类中被声明为虚函数,则他在所有派生类中都是虚函数。
值得注意的是:
虚函数不能声明为静态函数,也不能是友元函数。
基类指针指向派生类对象:
//
// VF2.cpp
// C++
//
// Created by Cheng Sun on 2017/6/21.
// Copyright © 2017年 Cheng Sun. All rights reserved.
//
#include <stdio.h>
#include <iostream>
using namespace std;
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()..." << endl;
}
virtual void Func2()
{
cout << "Base::Func2()..." << endl;
}
void Func3()
{
cout << "Base::Func3()..." << endl;
}
};
class Derived : public Base
{
public:
/*virtual*/ void Func1()
{
cout << "Derived::Func1()..." << endl;
}
/*virtual*/ void Func2()
{
cout << "Derived::Func2()..." << endl;
}
void Func3()
{
cout << "Derived::Func3()..." << endl;
}
};
int main()
{
Base* p;
Derived d;
p = &d; // 基类指针指向派生类对象
p->Func1(); //Func1是虚函数,基类指针指向派生类对象,调用的是派生类对象的虚函数。
p->Func2();
p->Func3(); //Func3非虚函数,根据p指针实际类型来调用相应类的成员函数。
return 0;
}
程序运行结果:
Derived::Func1()...
Derived::Func2()...
Base::Func3()...
Program ended with exit code: 0
虚析构函数
同样的道理,也反映在析构函数的应用上。当基类指针指向派生类对象,我们delete基类指针时,若基类的析构函数的不是虚函数,则只会释放基类对象的内存空间。想要同时释放派生类的内存空间,则需要将析构函数设为虚函数。(同样的,只需要将基类的析构函数设为虚函数,则派生类的析构函数也会自动变成虚函数。)
//
// VF2.cpp
// C++
//
// Created by Cheng Sun on 2017/6/21.
// Copyright © 2017年 Cheng Sun. All rights reserved.
//
#include <stdio.h>
#include <iostream>
using namespace std;
class Base
{
public:
virtual void Func1()
{
cout << "Base::Func1()..." << endl;
}
virtual void Func2()
{
cout << "Base::Func2()..." << endl;
}
void Func3()
{
cout << "Base::Func3()..." << endl;
}
Base()
{
cout << "Base()..." << endl;
}
virtual ~Base()
{
cout << "~Base()..." << endl;
}
};
class Derived : public Base
{
public:
/*virtual*/ void Func1()
{
cout << "Derived::Func1()..." << endl;
}
/*virtual*/ void Func2()
{
cout << "Derived::Func2()..." << endl;
}
void Func3()
{
cout << "Derived::Func3()..." << endl;
}
Derived()
{
cout << "Derived()..." << endl;
}
~Derived()
{
cout << "~Derived()..." << endl;
}
};
int main()
{
Base* p;
p = new Derived; // 创建派生类对象的时候,会首先创建它的基类对象
p->Func1();
delete p;
return 0;
}
程序运行结果:
Base()...
Derived()...
Derived::Func1()...
~Derived()...
~Base()...
Program ended with exit code: 0
如果上面代码中的基类没有将析构函数设为虚函数,那么delete p只会执行~Base(),而不会执行~Derived()。
总的来说,什么时候需要用到虚析构函数呢?如果一个类要作为多态基类,那么就要将析构函数定义为虚函数。
反之,如果一个类不会被任何派生类继承,则没有必要将它的析构函数定义为虚函数。
虚表指针和虚函数表
当一个类包含一个或一个以上的虚函数时,编译器就会为这个类产生一个4个字节(编译器工作在32位的框架下)的虚表指针。
虚函数是通过虚表来实现的。讨论没有覆盖的虚函数的情况没有太多意义,我们讨论一下有覆盖的虚函数的工作机制。当一个派生类继承基类时,若是派生类中出现了基类中同名的虚函数,即发生了所谓的覆盖,那么在派生类的虚函数表中,覆盖的虚函数被放到了原本父类虚函数的位置,没有被覆盖的函数,位置依旧。
虚表指针存放在类对象内部的头四个字节,三大区域都有可能。
虚函数表存放在全局数据区(静态区),因为对每个类,所有该类的对象共用虚表。
为方便下面部分代码的理解,这里稍稍介绍一下定义一个函数指针的方法:
返回类型 (*指针名) (函数参数列表)
例如: void (*Fun)(void) 意思是定一个指向返回值为void,函数参数也为void的函数的指针。
探讨包含虚函数的基类和派生类的内存模型:
//
// VF3.cpp
// C++
//
// Created by Cheng Sun on 2017/6/21.
// Copyright © 2017年 Cheng Sun. All rights reserved.
//
#include <stdio.h>
#include <iostream>
using namespace std;
class Base // 4 Bytes (pointer to virtual table) + 4 Bytes (int) = 8 Bytes
{
public:
virtual void Func1()
{
cout << "Base::Func1()..." << endl;
}
virtual void Func2()
{
cout << "Base::Func2()..." << endl;
}
virtual void Func3()
{
cout << "Base::Func3()..." << endl;
}
int data1_;
};
class Derived : public Base // 4 Bytes (pointer to virtual table) + 4 Bytes (int, data1_) + 4 Bytes (int, data2_) = 12 Bytes
{
public:
void Func2()
{
cout << "Derived::Func2()..." << endl;
}
void Func3()
{
cout << "Derived::Func3()..." << endl;
}
int data2_;
};
typedef void (*FUNC) (void); //定义一个函数指针,指向一个函数,这函数的类型是 void Fun (void)
int main()
{
cout << "size of Base = " << sizeof(Base) << endl;
cout << "size of Derived = " << sizeof(Derived) << endl;
Base b;
Derived d;
long** p = (long**)&b; //定义一个p, 指向虚表指针的指针
FUNC fun;
cout << "基类的虚函数表如下: " << endl;
fun = (FUNC)p[0][0];
fun(); //函数指针 == 函数地址 == 函数名 ?
fun = (FUNC)p[0][1];
fun();
fun = (FUNC)p[0][2];
fun();
cout << "派生类的虚函数表如下: " << endl;
p = (long**)&d;
fun = (FUNC)p[0][0];
fun();
fun = (FUNC)p[0][1];
fun();
fun = (FUNC)p[0][2];
fun();
cout << "----------------" << endl;
Base* pp = &d;
pp->Func2(); // 基类指针指向派生对象,通过基类指针(或引用)调用派生类虚函数,触发动态绑定
d.Func2(); // 直接调用,不会触发动态绑定,属于静态绑定
return 0;
}
程序运行结果:
size of Base = 8
size of Derived = 12
基类的虚函数表如下:
Base::Func1()...
Base::Func2()...
Base::Func3()...
派生类的虚函数表如下:
Base::Func1()...
Derived::Func2()...
Derived::Func3()...
----------------
Derived::Func2()...
Derived::Func2()...
Program ended with exit code: 0
多重继承中,包含虚函数的基类和派生类的内存模型:
因为是多重继承,不同于单一继承,所以派生类的虚表指针也不止一个,所以代码中可以访问p[1][n],p[2][n],而如果是单一继承,则只有p[0][n]。
验证代码如下:
//
// VF3.cpp
// C++
//
// Created by Cheng Sun on 2017/6/21.
// Copyright © 2017年 Cheng Sun. All rights reserved.
//
#include <stdio.h>
#include <iostream>
using namespace std;
class Base1 // 4 Bytes (pointer to virtual table) + 4 Bytes (int) = 8 Bytes
{
public:
virtual void Func1()
{
cout << "Base1::Func1()..." << endl;
}
virtual void Func2()
{
cout << "Base1::Func2()..." << endl;
}
virtual void Func3()
{
cout << "Base1::Func3()..." << endl;
}
int data1_ = 1;
};
class Base2 // 4 Bytes (pointer to virtual table) + 4 Bytes (int) = 8 Bytes
{
public:
virtual void Func1()
{
cout << "Base2::Func1()..." << endl;
}
virtual void Func2()
{
cout << "Base2::Func2()..." << endl;
}
virtual void Func3()
{
cout << "Base2::Func3()..." << endl;
}
int data2_ = 2;
};
class Base3 // 4 Bytes (pointer to virtual table) + 4 Bytes (int) = 8 Bytes
{
public:
virtual void Func1()
{
cout << "Base3::Func1()..." << endl;
}
virtual void Func2()
{
cout << "Base3::Func2()..." << endl;
}
virtual void Func3()
{
cout << "Base3::Func3()..." << endl;
}
int data3_ = 3;
};
class Derived : public Base1, public Base2, public Base3
// 4 Bytes (pointer to virtual table) * 3 + 4 Bytes (int, data1_) * 3 + 4 Bytes (int, data4_) = 28 Bytes
{
public: // four virtual funcitons in derived class
void Func2()
{
cout << "Derived::Func2()..." << endl;
}
void Func3()
{
cout << "Derived::Func3()..." << endl;
}
virtual void Func4()
{
cout << "Derived::Func4()... 此函数是派生类特有的哦,只存在派生类覆盖第一个基类的虚表里" << endl;
}
int data4_ = 4;
};
typedef void (*FUNC) (void); //定义一个函数指针,指向一个函数,这函数的类型是 void Fun (void)
int main()
{
cout << "size of Base = " << sizeof(Base1) << endl;
cout << "size of Derived = " << sizeof(Derived) << endl;
long** p;
FUNC fun;
int* da;
//既然当一个类包含虚函数时,系统就会为它自动生成一个虚函数表
//那么一个类就一个表?如果对于继承了好几个基类的派生类呢?
//如果不是,那么继承了多个基类的派生类有多少个虚表指针?
cout << "-----------------" << endl;
cout << "第一个基类的虚函数表如下: " << endl;
Base1 b1;
p = (long**)&b1;
fun = (FUNC)p[0][0];
fun();
fun = (FUNC)p[0][1];
fun();
fun = (FUNC)p[0][2];
fun();
cout << "-----------------" << endl;
da = (int*)p[1];
cout << "data1_ = " << da << endl;
cout << "-----------------" << endl;
cout << "第二个基类的虚函数表如下: " << endl;
Base2 b2;
p = (long**)&b2;
fun = (FUNC)p[0][0];
fun();
fun = (FUNC)p[0][1];
fun();
fun = (FUNC)p[0][2];
fun();
cout << "-----------------" << endl;
da = (int*)p[1];
cout << "data2_ = " << da << endl;
cout << "-----------------" << endl;
cout << "第三个基类的虚函数表如下: " << endl;
Base3 b3;
p = (long**)&b3;
fun = (FUNC)p[0][0];
fun();
fun = (FUNC)p[0][1];
fun();
fun = (FUNC)p[0][2];
fun();
cout << "-----------------" << endl;
da = (int*)p[1];
cout << "data3_ = " << da << endl;
cout << "-----------------" << endl;
cout << "派生类的虚函数表如下: " << endl;
Derived d;
p = (long**)&d; //定义一个p, 指向虚表指针的指针
fun = (FUNC)p[0][0];
fun(); //函数指针 == 函数地址 == 函数名 ?
fun = (FUNC)p[0][1];
fun();
fun = (FUNC)p[0][2];
fun();
fun = (FUNC)p[0][3];
fun();
cout << "-----------------" << endl;
// 为什么跳过p[1][n]呢,因为p[1][0]存放的是 data1_,虽然数据成员并没有存在虚表里面,但是派生类因为继承的关系,依然继承了基类里面的数据成员,所以通过指针访问虚表指针进而调用虚函数时,要跳过派生类中继承的基类的数据成员的内存存储空间(位置)
// 验证一下:
da = (int*)p[1];
cout << "data1_ = " << da << " 成员数据并不存放于虚表中" << endl;
cout << "-----------------" << endl;
fun = (FUNC)p[2][0];
fun();
fun = (FUNC)p[2][1];
fun();
fun = (FUNC)p[2][2];
fun();
cout << "-----------------" << endl;
da = (int*)p[3];
cout << "data2_ = " << da << " 成员数据并不存放于虚表中" << endl;
//fun = (FUNC)p[2][3];
//fun(); //报错,说明继承了多个基类的派生类,它里面独有的虚函数,只存在第一个(按声明顺序)继承的基类的虚表中!!!
// 为什么跳过p[1][n]呢,因为p[1][0]存放的是 data2_
cout << "-----------------" << endl;
fun = (FUNC)p[4][0];
fun();
fun = (FUNC)p[4][1];
fun();
fun = (FUNC)p[4][2];
fun();
cout << "-----------------" << endl;
da = (int*)p[5];
cout << "data3_ = " << da << " 成员数据并不存放于虚表中" << endl;
cout << "-----------------" << endl;
da = (int*)p[6];
cout << "data4_ = " << da << " 成员数据并不存放于虚表中" << endl;
//fun = (FUNC)p[4][3];
//fun(); //报错,同理。
return 0;
}
程序运行结果:
size of Base = 8
size of Derived = 28
-----------------
第一个基类的虚函数表如下:
Base1::Func1()...
Base1::Func2()...
Base1::Func3()...
-----------------
data1_ = 0x1
-----------------
第二个基类的虚函数表如下:
Base2::Func1()...
Base2::Func2()...
Base2::Func3()...
-----------------
data2_ = 0x2
-----------------
第三个基类的虚函数表如下:
Base3::Func1()...
Base3::Func2()...
Base3::Func3()...
-----------------
data3_ = 0x3
-----------------
派生类的虚函数表如下:
Base1::Func1()...
Derived::Func2()...
Derived::Func3()...
Derived::Func4()... 此函数是派生类特有的哦,只存在派生类覆盖第一个基类的虚表里
-----------------
data1_ = 0x1 成员数据并不存放于虚表中
-----------------
Base2::Func1()...
Derived::Func2()...
Derived::Func3()...
-----------------
data2_ = 0x2 成员数据并不存放于虚表中
-----------------
Base3::Func1()...
Derived::Func2()...
Derived::Func3()...
-----------------
data3_ = 0x3 成员数据并不存放于虚表中
-----------------
data4_ = 0x4 成员数据并不存放于虚表中
Program ended with exit code: 0