实现同一方法的调用,完成不同的效果,方便程序的接口实现
一、动态联编和静态联编
1.什么是联编
1.联编指的是:计算机自身彼此关联的过程,在这个过程中确定程序中的操作调用与该操作的代码之间的映射关系,比如:函数重载时,编译器会根据函数名和函数的参数来区别要执行的函数(那一段代码)。这就是建立的一种映射关系,明确函数调用时会执行哪一个代码段
2.静态联编:指的是联编工作出现在写代码的阶段,又称之为早期联编,或静态约束。在编译时就确定了函数实现及函数调用的关联。比如C语言的函数,只需要函数名和参数就能确定能不能调用函数,因为C语言是没有函数重载,参数正确才能调用。
函数重载和模板就是静态联编,在编译期间确定
3.动态联编:指的是编译过程不能确定知道将要调用的函数,只能在程序运行的时候才能确定将要调用的函数,也就是说在程序运行的时候才将函数实现和函数调用关联,也叫晚期联编,或动态约束
虚函数和继承关系实现动态联编,在运行期间确定
2.实现动态联编的条件
1.必须把动态联编的行为定义为类的虚函数
2.必须有类,而且类之间满足父子关系,这个行为通常是重写的父类的虚函数
3.必须是先是用基类指针或引用指向派生类对象,然后使用基类指针调用虚函数成员
注:其实动态联编就是今天学习的多态
二、多态
1.概念
1.多态:字面意思就是具有多种形式或者状态,理解为,同一个接口,通过不同的对象调用就有不同的效果
2.定义
1.类中声明函数成员的时候,在函数的前面加上virtual关键字,则该成员为虚函数。
virtual 函数返回值类型 函数名(参数列表){函数体;}
2.虚函数的作用:通过类的继承及函数来实现的多态
3.内存分布
1.对象内,首先存储的是“虚函数表指针”,又称“虚表指针”。
2.然后再存储非静态数据成员。
3.静态数据成员,存放在数据段里,没有放在对象空间内
4.对象的非虚函数,保存在类的代码中!
5.对象的内存,只存储虚函数表和数据成员
(类的静态数据成员,保存在数据区中,和对象是分开存储的)
6.添加虚函数后,对象的内存空间不变!仅在虚函数表中添加
7.如果有多个类对象,共享同一个虚函数表!
8.虚函数表单独存放,也不会存放在对象内存中
单个类的虚函数表
#include <iostream>
using namespace std;
class Father {
public:
virtual void func1() { cout << "Father::func1" << endl; }
virtual void func2() { cout << "Father::func2" << endl; }
virtual void func3() { cout << "Father::func3" << endl; }
void func4() { cout << "非虚函数:Father::func4" << endl; }
public: //为了便于测试,特别该用public
int x = 100;
int y = 200;
static int z;
};
//函数指针func_t
typedef void(*func_t)(void);
int Father::z = 1;
int main(void) {
Father father;
// 含有虚函数的对象的内存中,最先存储的就是“虚函数表”
cout << "对象地址:" << (int*)&father << endl;
/*
&father:是father对象的首地址
*(int*)(&father):将首地址转成一个指针,然后取出这个指针指向的内容,也就是一个地址,这个地址就是虚函数表指针的地址。
*(&father)是不能直接取出虚函数表指针的
(int*)*(int*)(&father):再将这个地址转换为指针
*/
int* vptr = (int*)*(int*)(&father);
cout << "虚函数表指针vptr:" << vptr << endl;
cout << "调用第1个虚函数: ";
((func_t) *(vptr + 0))();//使用函数指针去调用虚函数的
cout << "调用第2个虚函数:";
((func_t) * (vptr + 1))();
cout << "调用第3个虚函数: ";
((func_t) * (vptr + 2))();
cout << "--------------------"<<endl;
cout << "第1个数据成员的地址: " << endl;
cout << &father.x << endl;
cout << std::hex << (int)&father<< endl;
cout << std::hex << (int)&father + 4 << endl;
cout << "第1个数据成员的值:" << endl;
cout << std::dec << father.x << endl;
cout << *(int*)((int)&father + 4) << endl;
cout << "第2个数据成员的地址: " << endl;
cout << &father.y << endl;
cout << std::hex << (int)&father + 8 << endl;
cout << "第2个数据成员的值:" << endl;
cout << std::dec << father.y << endl;
cout << *(int*)((int)&father + 8) << endl;
cout << "sizeof(father)==" << sizeof(father) << endl;
Father father2;
cout << "father的虚函数表:";
cout << (int*)(*(int*)&father) << endl;
cout << "father2的虚函数表:";
cout << (int*)(*(int*)&father2) << endl;
system("pause");
return 0;
}
单继承后的虚函数表
#include <iostream>
using namespace std;
class Father {
public:
virtual void func1() { cout << "Father::func1" << endl; }
virtual void func2() { cout << "Father::func2" << endl; }
virtual void func3() { cout << "Father::func3" << endl; }
void func4() { cout << "非虚函数:Father::func4" << endl; }
public: //为了便于测试,特别该用public
int x = 100;
int y = 200;
};
class Son : public Father {
public:
void func1() { cout << "Son::func1" << endl; }
virtual void func5() { cout << "Son::func5" << endl; }
};
typedef void(*func_t)(void);
int main(void) {
Father father;
Son son;
// 含有虚函数的对象的内存中,最先存储的就是“虚函数表”
cout << "son对象地址:" << (int*)&son << endl;
int* vptr = (int *)*(int*)&son;
cout << "虚函数表指针vptr:" << vptr << endl;
for (int i = 0; i < 4; i++) {
cout << "调用第" << i + 1 << "个虚函数:";
((func_t) * (vptr + i))();
}
for (int i = 0; i < 2; i++) {
// +4 是因为先存储了虚表指针
cout << *(int*)((int)&son + 4 + i * 4) << endl;
}
system("pause");
return 0;
}
子类虚函数表的构建过程:
1.子类的虚函数表直接复制父类的虚函数表
2.如果子类重写了父类的某个虚函数,那么就在虚函数表中将相应的函数进行替换
3.如果子类增加了新的虚函数,就把这个虚函数添加到虚函数表中(在尾部添加)
多继承后的虚函数表
#include <iostream>
using namespace std;
class Father {
public:
virtual void func1() { cout << "Father::func1" << endl; }
virtual void func2() { cout << "Father::func2" << endl; }
virtual void func3() { cout << "Father::func3" << endl; }
void func4() { cout << "非虚函数:Father::func4" << endl; }
public:
int x = 200;
int y = 300;
static int z;
};
class Mother {
public:
virtual void handle1() { cout << "Mother::handle1" << endl; }
virtual void handle2() { cout << "Mother::handle2" << endl; }
virtual void handle3() { cout << "Mother::handle3" << endl; }
public: //为了便于测试,使用public权限
int m = 400;
int n = 500;
};
class Son : public Father, public Mother {
public:
virtual void func1() { cout << "Son::func1" << endl; }
virtual void handle1() { cout << "Son::handle1" << endl; }
virtual void func5() { cout << "Son::func5" << endl; }
};
int Father::z = 0;
typedef void(*func_t)(void);
int main(void) {
Son son;
int* vptr = (int*) * (int*)&son;
cout << "第一个虚函数表指针:" << vptr << endl;
for (int i = 0; i < 4; i++) {
cout << "调用第" << i + 1 << "个虚函数:";
((func_t) * (vptr + i))();
}
for (int i = 0; i < 2; i++) {
cout << *(int*)((int)&son + 4 + i * 4) << endl;
}
int* vptr2 = (int*) * ((int*)&son + 3);
for (int i = 0; i < 3; i++) {
cout << "调用第" << i + 1 << "个虚函数:";
((func_t) * (vptr2 + i))();
}
for (int i = 0; i < 2; i++) {
cout << *(int*)((int)&son + 16 + i * 4) << endl;
}
system("pause");
return 0;
}
从上面可以看出只要类中有虚函数,系统就会创建一个虚函数表指针和一个虚函数表
上述:class Son : public Father, public Mother
那么子类son的对象内存空间:
1.虚函数表指针-Father
2.从Father继承来的非静态数据成员
3.虚函数表指针-Mother
4.从Mother继承来的非静态数据成员
5.自己的非静态数据成员
4.final
用来修饰类,让该类不能被继承
理解:使得该类终结!
class XiaoMi {
public:
XiaoMi(){}
};
class XiaoMi2 final : public XiaoMi {//类XiaoMi2不能被继承
XiaoMi2(){}
};
class XiaoMi3 : public XiaoMi2 { //错误! 不能把XiaoMi2作为基类
};
用来修饰类的虚函数,使得该虚函数在子类中,不能被重写
理解:使得该功能终结!
class XiaoMi {
public:
virtual void func() final;
};
void XiaoMi::func() { //不需要再写final
cout << "XiaoMi::func" << endl;
}
class XiaoMi2 : public XiaoMi {
public:
void func() {}; // 错误!不能重写func函数
};
5.override
override仅能用于修饰虚函数。
override只需在函数声明中使用,不需要在函数的实现中使用。
作用:
1.提示程序的阅读者,这个函数是重写父类的功能。
2.防止程序员在重写父类的函数时,把函数名写错。
#include <iostream>
using namespace std;
class XiaoMi {
public:
virtual void func() { cout << "XiaoMi::func" << endl; };
};
class XiaoMi2 : public XiaoMi {
public:
void func() override {}
//void func() override; 告诉程序员func是重写父类的虚函数
//void func1() override{} 错误!因为父类没有func1这个虚函数
};
int main(void) {
XiaoMi2 xiaomi;
return 0;
}
6.子类析构
1.基类中有虚函数时,且是通过基类指针去分配派生类对象的时候,在释放的时候只能通过基类指针去进行释放,在delete基类指针的时候会调用基类的析构函数,而不会调用派生类的析构函数
2.需要把基类的析构函数定义为虚析构,那么在释放基类指针的时候就会先调用子类的析构函数了,再调用基类的析构函数(否者可能会造成内存泄漏)
3.把Father类的析构函数定义为virtual函数时,如果对 Father类的指针使用delete操作时就会对该指针使用动态析构,如果这个指针,指向的是子类对象,那么会先调用该子类的析构函数,再调用自己类的析构函数
4.一般来说父类的析构函数,都应该写成虚函数
注意:
为了防止内存泄露,最好是在基类析构函数上添加virtual关键字,使基类析构函数为虚函数,目的在于:
1.当使用delete释放基类指针时,会实现动态的析构:
2.如果基类指针指向的是基类对象,那么只调用基类的析构函数
3.如果基类指针指向的是子类对象,那么先调用子类的析构函数,再调用父类的析构函数
注意:一般构造函数不使用虚函数,因为创建子类对象,必须先调用父类的构造函数来初始化子类从父类继承到的数据
虚函数的调用依赖于虚函数表,而指向虚函数表的指针vptr需要在构造函数中进行初始化,所以无法调用定义为虚函数的构造函数。
构造析构顺序
class son:public father
子类指针指向子类对象
使用:son* son1=new son();
1.父类构造函数
2.子类构造函数
3.son1死亡时,调用子类析构函数
4.再调用父类析构函数
父类指针指向子类对象
使用:father* f=new son();
1.父类构造函数
2.子类构造函数
3.f死亡时,调用子类析构函数
4.再调用父类析构函数
7.实现多态
1.有两个类,他们之间是父子关系
2.子类和父类有同名的虚函数,但是功能不同
3.父类的指针或引用指向父类或子类对象,指向那个对象就可以调用那个对象的虚函数成员(必须使用父类的指针或者引用,去指向父类或子类对象)
注意事项:
1.指针指向一个子类对象的地址(对指针解引用得到对象)
2.引用指向一个子类对象
3.通过基类指针指向子类对象在调用这个虚函数成员的时候,指向哪个对象就调用那个对象里的成员
4.通常在子类也会带上这个virtual关键字,带上关键字后可以知道哪些函数是虚函数(也可以不带)
5.如果在派生类中没有重写基类的虚函数,那么就算满足虚函数的要求,调用的还是基类的虚函数成员(所以子类中不重写虚函数的功能,那定义虚函数就没有意义了)
6.通过基类指针指向派生类对象所能调用的只是虚函数成员,其他的都不能调用
#include<iostream>
using namespace std;
class animal
{
public:
virtual void eat()//如果去掉virtual,不是虚函数,那么fun函数里指向的是animal的eat函数那就一定是指向的是animal的eat函数,而不是cat的eat函数
{
cout << "动物在吃东西" << endl;
}
};
class cat :public animal
{
public:
void eat()
{
cout << "猫在吃东西" << endl;
}
};
void fun(animal* animal)//基类指针
{
animal->eat();
}
void fun1(animal&p)//基类引用
{
p.eat();
}
int main()
{
animal p4;
cat p3;
fun1(p4);//动物在吃东西
fun1(p3);//猫在吃东西
//基类指针指向基类对象
animal* p2 = new animal;
fun(p2);//动物在吃东西
delete p2;
p2 = NULL;
//派生类指针指向派生类对象
cat* p1 = new cat;
fun(p1);//猫在吃东西
delete p1;
p1 = NULL;
//基类指针指向派生类对象
animal* p = new cat;
p->animal::eat();//调用的应该是猫对象里的eat函数
delete p;
p = NULL;
system("pause");
return 0;
}
虚函数的析构:
#include<iostream>
using namespace std;
class animal
{
public:
animal()
{
cout << "基类构造" << endl;
}
~animal()
{
cout << "基类析构" << endl;
}
virtual void eat()
{
cout << "动物在吃东西" << endl;
}
};
class dog :public animal
{
public:
dog()
{
cout << "派生类构造" << endl;
}
~dog()
{
cout << "派生类析构" << endl;
}
void eat()
{
cout << "狗在吃东西" << endl;
}
};
int main()
{
animal* p1 = new dog;
p1->eat();
delete p1;
p1 = NULL;//只会调用基类animal的析构函数
//基类构造
//派生类构造
//基类析构
//将基类的析构函数变为虚析构,那么在释放的时候就会先调用派生类的析构函数,再去调用基类的析构函数
system("pause");
return 0;
}
三、纯虚函数
1.概念
1.纯虚函数是一种特殊的虚函数。在基类中不能给虚函数有意义的实现,就可以把它声明为纯虚函数,然后把它的实现留给派生类完成
2.析构函数也可以是纯虚函数,但是需要在类外实现
virtual ~animal()=0;//类中声明
2.定义
virtual void fun()=0;
3.抽象类
1.在一个类中具有一个及以上的纯虚函数,那么这个类被称之为抽象类
2.抽象类不能实例化对象,但是可以定义指针,只能作为基类为派生类服务
3.如果派生类中没有完全实现基类中所有的纯虚函数,那么该派生类也会变成抽象类,同样不能实例化对象
#include<iostream>
using namespace std;
class animal
{
public:
virtual void eat() = 0;//定义纯虚函数
};
class cat :public animal
{
public:
virtual void eat()
{
cout << "猫在吃东西" << endl;
}
};
int main()
{
animal* a = new cat;//不能new出animal
a->eat();
delete a;
a = NULL;
system("pause");
return 0;
}
抽象类:描述具有共同属性的类,继承,只需要一个指针,一个接口
#include<iostream>
using namespace std;
class worker
{
public:
int a=10;
//公有的数据可以直接拿来用
void fun() { cout << "为了薪水" << endl; }
//这个fun函数就是他们公有的行为,而且这个行为还是一致的
virtual void speak() = 0;
virtual void work() = 0;
//用来给子类实现的,告诉你这些功能是你有的,而且需要做
};
//抽象类,把那些共有的数据和行为集合在一起,通过子类对象来继承使用
class yunfei :public worker
{
public:
virtual void speak()
{
cout << "通过麦给大家说话" << endl;
}
virtual void work()
{
cout << "坐在位子上工作" << endl;
}
};
class boss :public worker
{
virtual void speak()
{
cout << "面带微笑说话" << endl;
}
virtual void work()
{
cout << "老板自己才知道" << endl;
}
};
void fun(worker* worker)//传指针
{
worker->speak();
worker->work();
cout << worker->a << endl;
worker->fun();
}
void fun1(worker&worker)//直接传对象
{
worker.speak();
}
int main()
{
worker* p1 = new yunfei;
fun(p1);
delete p1;
p1=NULL;
system("pause");
return 0;
}