1.多态的概念
通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出
不同的状态
2.多态的定义及实现
1°条件
- 必须通过基类的指针或者引用调用虚函数
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
2°虚函数
即被virtual修饰的类成员函数称为虚函数。
#include <iostream>
using namespace std;
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
private:
int _a;
char _ch;
};
//成员函数加virtual->虚函数
//普通函数不行
int main()
{
cout << sizeof(Person) << endl;//要多算 会有多的指针
return 0;
}
3°虚函数的重写
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚
函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函
数。
//虚函数
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
};
void Func(Person& p)//指针或者引用都可以 如果传值都会调用全价 如果不是virtual也会调用两个全价 两个函数构成隐藏
{
p.BuyTicket();//指针就用箭头
}
//1.满足多态的条件:跟对象有关 指向哪个对象就调用它的虚函数
//2.不满足的多态的条件:对类型有关 调用的类型是谁 调用就是谁的
int main()
{
Person ps;
Student st;
Func(ps);
Func(st);
//调的是不同的函数 因为传的对象不一样
return 0;
}
-
满足多态的条件:跟对象有关 指向哪个对象就调用它的虚函数
-
不满足的多态的条件:跟类型有关 调用的类型是谁 调用就是谁的
-
小结
不同对象去做同一件事的效果不一样
多态的两个条件
1.虚函数的重写 virtual关键字
virtual关键字
(1)可以修饰成原函数 为了完成虚函数的重写 满足多态的条件之一 (2)可以在菱形继承中 去完成虚继承 解决数据冗余和二义性
虚函数重写:返回值类型 函数名字 参数列表完全相同 注意有特殊情况
两个地方使用了同一个关键字 但是他们互相之间没有一点关联
2.父类对象的指针或者引用去调用虚函数
满足多态:跟指向对象有关 指向哪个对象调用就是他的虚函数
不满足多态:跟调用对象的类型有关 类型是什么就调用谁的虚函数
<1>协变
基类与派生类虚函数返回值类型不同
//虚函数重写的两个例外 小心选择题设坑
//1.协变 子类和父类函数返回值类型不同 也要是本类型的返回值类型 也可以形成多态
class Person {
public:
virtual Person* BuyTicket() //Person*
{
cout << "Person::买票-全价" << endl;
return nullptr;
}
};
class Student : public Person {
public:
Student* BuyTicket() //Student* //继承后可以不写virtual 也可以构成多态 子类可以不写 父类必须写
{
cout << "Student::买票-半价" << endl;
return nullptr;
}
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person ps;
Student st;
Func(ps);
Func(st);
return 0;
}
<2>析构函数的重写
//2.析构函数的重写
class Person {
public:
virtual ~Person() { cout << "~Person()" << endl; }
//析构函数的函数名会被处理成destructor
};
class Student : public Person {
public:
virtual ~Student() { cout << "~Student()" << endl; }
//析构函数的函数名会被处理成destructor
};
int main()
{
//Person p;
//Student s;
//先Student析构 再调父类析构 父类再自己析构 没有问题
//Person* p1 = new Person;//没有问题
//delete p1;
Person* p2 = new Student;//如果无virtual 会析构两次父类
delete p2;//如果student析构函数中有资源释放 这里没有被调用到 就会出现内存泄漏
//不加virtual 不构成多态:看指针类型 对应析构函数
//加virtual 构成多态:看对象类型 对应析构函数
return 0;
}
<3>练习
输出?
//练习
class A
{
public:
virtual void func(int val = 1)
{
std::cout << "A->" << val << std::endl;
}
virtual void test()
{
func();
}//A* this
};
class B : public A
{
public:
void func(int val = 0)
{
std::cout << "B->" << val << std::endl;
}//子类可以不写virtual
//继承下来 缺省参数也会被父类替换 只有函数内容不一样 缺省值为1
};
int main(int argc, char* argv[])
{
B* p = new B;//指向子类对象 调子类的
p->test();//p->test(p)
return 0;
}
答案:B->1
继承下来后 派生类虚函数的缺省值也会被基类虚函数的缺省值替换
所以最后会打印1
4°C++关键字
- 加一个final关键字 不能被重写和继承
- 加一个override检查子类的虚函数是否重写了父类的虚函数
5°重载 重写 隐藏的定义
-
重载:
1.两个函数在同一作用域
2.函数名相同 参数不同
-
重写:
1.两个函数分别在父类和子类的作用域
2.函数名/参数/返回值都必须相同(协变例外)
3.两个函数都必须是虚函数
-
重定义(隐藏)
1.两个函数分别在父类和子类的作用域
2.函数名相同
3.两个父类和子类的同名函数不构成重写就是重定义
3.抽象类
不能实例化出对象
//抽象类
//特点:不能实例化出对象
class Car
{
public:
virtual void Drive() = 0;//不需要实现 纯虚函数
};
class Benz :public Car
{
public:
virtual void Drive()
{}
};
int main()
{
//Car car;//不行
Benz bz;//继承的类也不可以实例化出对象 继承了父类的纯虚函数 还是抽象类
//解决:重写一下
//纯虚函数:1.强制子类去完成重写
//2.表示抽象的类型 抽象就是在显示中没有对应的实体的
return 0;
}
子类重写虚函数后
// 包含纯虚函数的类抽象类 抽象类不能实例化出对象
class Car
{
public:
virtual void Drive() = 0;//纯虚函数 不需要实现
};
class Benz :public Car
{
public:
virtual void Drive()
{
cout << "Benz-舒适" << endl;
}
};
int main()
{
Benz benz;//重写后可以实例化出对象
return 0;
}
4.多态的原理
1°虚函数表
- 笔试题:sizeof(Base)是多少?
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;//4个字节
//还会多一个指针
//应该是8 32位下
//或者16 64位下 4+8=12加对齐就是16
//虚函数表指针:简称虚表指针
//虚函数表其实就是一个指针数组(虚函数指针)
};
注意会多一个虚函数表指针、
2°原理
class Person {
public:
virtual void BuyTicket() { cout << "买票-全价" << endl; }
int _id = 1;
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "买票-半价" << endl; }
int _s = 2;
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person Mike;
Func(Mike);
Student Johnson;
Func(Johnson);
return 0;
}
- 调用的时候去找不同的地址(虚指针) 去找对应的位置
从而找到和调用子类和父类的虚函数
p指向的Person类型的对象就调用Person虚函数
p指向的Student类型的对象就调用Student虚函数
- 如何实现的指向谁调谁?
多态是在运行时到指向的对象的虚表中查找要调用的虚函数的地址来进行调用
编译时直接确定通过p的类型确定要调用函数的地址
普通函数和虚函数都会被编译成指令以后 存在代码段
class Base
{
public:
virtual void func1()
{
cout << "Base::func1" << endl;
}
virtual void func2()
{
cout << "Base::func2" << endl;
}
private:
int a;
};
class Derive :public Base
{
public:
virtual void func1()
{
cout << "Derive::func1" << endl;
}
virtual void func3()
{
cout << "Derive::func3" << endl;
}
virtual void func4()
{
cout << "Derive::func4" << endl;
}
private:
int b;
};
void func()
{
//取出对象的前4个字节
Base b1;
printf("vftptr:%p\n", *(int*)&b1);//取对象地址强转int*再解引用拿到地址
int i = 0;
int* p1 = &i;
int* p2 = new int;
const char* p3 = "hello";
printf("栈变量:%p\n", p1);
printf("堆变量:%p\n", p2);
printf("代码段常量:%p\n", p3);
printf("虚函数地址:%p\n", &Base::func2);
printf("普通函数地址:%p\n", func);
}
int main()
{
Base b;
Derive d;
func();//可以发现vftptr 虚函数 普通函数与代码段的地址是比较接近的 三者都在代码段
}
- 虚函数存在哪?虚表存在哪?
虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只
是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。
指针数组中多个指针 以0x00000000结束 栈是错的 代码段(常量区) 因为同类型的对象公
用一个虚表就不会在栈上 公共区域代码段
3°动态绑定与静态绑定
静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多
态,比如:函数重载
动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具
体行为,调用具体的函数,也称为动态多态。
class Base
{
public:
virtual void func1()
{
cout << "Base::func1" << endl;
}
virtual void func2()
{
cout << "Base::func2" << endl;
}
private:
int a;
};
class Derive :public Base
{
public:
virtual void func1() //func1重写 func2没有重写 监视看不到func3和func4
{
cout << "Derive::func1" << endl;
}
virtual void func3()
{
cout << "Derive::func3" << endl;
}
virtual void func4()
{
cout << "Derive::func4" << endl;
}
private:
int b;
};
void f1(double d)
{}
int main()
{
int i = 0;
double d = 1.1;
//静态绑定 静态的多态 (静态:编译时确定函数地址)
cout << i << endl;
cout << d << endl;
f1(i);
f1(d);
//动态绑定 动态的多态 (动态:运行时到虚表中找虚函数地址)
Base* p = new Base;
p->func1();
p = new Derive;
p->func1();
return 0;
}
5.单继承合多继承关系的虚函数表
1°单继承
//单继承 虚表打印
class Base
{
public:
virtual void func1()
{
cout << "Base::func1" << endl;
}
virtual void func2()
{
cout << "Base::func2" << endl;
}
private:
int a;
};
class Derive :public Base
{
public:
virtual void func1() //func1重写 func2没有重写 监视看不到func3和func4
{
cout << "Derive::func1" << endl;
}
virtual void func3()
{
cout << "Derive::func3" << endl;
}
virtual void func4()
{
cout << "Derive::func4" << endl;
}
private:
int b;
};
//void(*p)();//定义一个函数指针 通过typedef简化
typedef void(*VF_PTR)();
void PrintVFtable(VF_PTR* pTable)//打印虚函数表
{
for (size_t i = 0; pTable[i] != 0; ++i)
{
printf("vfTable[%d]:%p\n", i, pTable[i]);
VF_PTR f = pTable[i];
f();
}
}
int main()
{
Base b;
Derive d;
//取对象前4个字节的虚表指针
PrintVFtable((VF_PTR*)(*(int*)&b));
cout << endl;
//除了func2没有重写 调的是父类 其余都是调自己的
PrintVFtable((VF_PTR*)(*(int*)&d));//会有很多个 编译器的BUG 需要重新清理一下 生成里面
return 0;
}
2°多继承
//多继承
class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1;
};
class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2;
};
class Derive : public Base1, public Base2 {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }//自己的func3是往第一个虚表走的
private:
int d1;
};
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
cout << " 虚表地址>" << vTable << endl;
for (int i = 0; vTable[i] != nullptr; ++i)
{
printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
VFPTR f = vTable[i];
f();
}
cout << endl;
}
int main()
{
cout << sizeof(Derive) << endl;//20 为什么不是24?
//Base1 8
//Base2 8
//Derive 4 自己虚表指针不用算 算了也会覆盖父类的
//20
Derive d;
VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
PrintVTable(vTableb1);
VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));//Base1头4个字节 先强转char* 再加sizeof
PrintVTable(vTableb2);
return 0;
}
- 区分虚表和虚继表
虚基表存的是偏移量 解决菱形继承的数据冗余和二义性
虚表是一个函数指针数组,数组里存放的都是函数指针,指向虚函数所在的位置
【C++】13.多态 完