文章目录
一、虚函数
1.重载,重写(覆盖),重定义(隐藏)的对比
重载: 两个函数在一个作用域,函数名相同,参数不同
.
重写(覆盖): 两个函数分别在基类和派生类的作用域
函数名,返回类型,参数都必须相同(协变例外),两个函数必须是虚函数
.
重定义(隐藏): 两个函数分别在基类和派生类的作用域
函数名相同,两个基类和派生类的同名函数不构成重写(覆盖)就是重定义
2.虚函数的重写
class person
{
public:
void BuyTicket() //输出全为all
//virtual void BuyTicket() //正确的输出
{
cout << "all" << endl;
}
};
class student : public person
{
public:
void BuyTicket()
//virtual void BuyTicket()
{
cout << "half" << endl;
}
};
void Fun1(person& p) //注意引用 有子类赋值给父类(切割,切片)
{
p.BuyTicket();
}
void Fun2(person* p) //指针也可以
{
p->BuyTicket();
}
int main()
{
student s;
person p;
Fun1(s);
Fun1(p);
Fun2(&s);
Fun2(&p);
return 0;
}
//virtual 关键字: 可以修饰成员函数,为了完成虚函数的重写,满足多态的条件之一
// : 可以在零菱形继承中,去完成虚继承,解决数据冗余和二义性
//虽然关键字一样,但他们之间没有一点关联
//多态的两个条件:
//1.虚函数的重写
//2.父类对象的指针或引用去调用虚函数
//满足多态:跟指向对象有关,指向哪个对象调用的就是他的虚函数
//不满足多态:跟对象调用的类型有关,类型是什么调用的就是谁
①协变
基类与派生类虚函数的返回值类型不同
却仍然可以构成重写
//基类虚函数返回类型为基类对象的指针或引用,派生类虚拟函数返回类型为派生类对象的指针或引用,称为协变
class A{};
class B : public A{};
class person
{
public:
virtual person* BuyTicket()
//virtual A* BuyTicket() //而且不限于本父子类
//virtual B* BuyTicket() //但必须对应父子类,否则会出错
{
cout << "all" << endl;
return nullptr;
}
};
class student : public person
{
public:
virtual student* BuyTicket() //如果虚函数返回类型是基类和派生类的指针或引用时,就会构成协变,此时无错误
//virtual B* BuyTicket()
//virtual A* BuyTicket()
{
cout << "half" << endl;
return nullptr;
}
};
void Fun1(person& p)
{
p.BuyTicket();
}
int main()
{
student s;
person p;
Fun1(s);
Fun1(p);
return 0;
}
②析构函数的重写
基类与派生类的函数名不同
但仍然可以构成重写
class person
{
public:
//virtual ~person()
~person() //析构函数名被编译器处理为同一名称destruct
{
cout << "~person" << endl;
}
};
class student : public person
{
public:
//virtual ~student()
~student()
{
cout << "~student" << endl;
}
};
int main()
{
person* p1 = new person;
person* p2 = new student; //不加virtual会使得只调用person的析构
//调用不了student的析构函数,会内存泄漏
//virtual: `person `student `person , 感觉到了继承,根据情况调用不同的"同名"函数
//加上virtual,构成多态,调用的指针指向谁就调用谁的析构函数
//没有virtual: `person `person , 函数只调一个,知道有继承,但就是只能调一个
//不加上virtual,不构成多态,调用的指针类型是谁,调用的就是谁的析构函数
delete p1;
delete p2;
return 0;
}
③虚函数的一些点
虚函数有一个指针的大小
虚函数重写生效的只是内容,而不是参数
class A
{
public:
virtual void func(int val = 1)
{
cout << "A->" << val << endl;
}
virtual void test() //传入的是对象B指针,所以调用B中的func
{
func();
}
};
class B : public A
{
public:
void func(int val = 0) //重写生效的只是内容,而不是参数,所以缺省参数中 int val = 0 无效,所以 val == 1
{
cout << "B->" << val << endl;
}
};
int main()
{
A* p = new B;
p->test();
return 0;
}
④抽象类(纯虚函数)
//在虚函数后面加上 =0 ,则这个函数就是纯虚函数
class car //有了纯虚函数,这个类就被成为抽象类
{
public:
virtual void drive() = 0;//纯虚函数,不需要实现,实现了不报错,但也没用
//virtual void drive() = 0
//{
// cout << "aa" << endl;
//}
};
class benz : public car
{
public:
virtual void drive()
{
cout << "benz" << endl;
}
};
int main()
{
//car autocar; //抽象类实例化不出对象
benz autoccar; //抽象类的子类,也实例化不出对象,继承了纯虚函数(没有重写)
//重写后即可以实例化出对象
return 0;
}
纯虚函数的作用是
强迫重写
表示抽象的类型(现实中没有对应实体的)
二、两个关键字
1.final
修饰虚函数:可以阻止函数被重写,final无法对类中非虚函数进行修饰
修饰类: 可以阻止类被继承
class person
{
public:
virtual void BuyTicket() final //此后,该函数不能再被重写
{
cout << "all" << endl;
}
};
class student : public person
{
public:
virtual void BuyTicket() //此时会报错,无法继承
{
cout << "half" << endl;
}
};
class A final{}; //final后A不能被继承
class B : public A{};
2.override
可以检查出虚函数是否被成功重写
class A
{
public:
void out()
//virtual void out()
{
cout << "111" << endl;
}
};
class B : public A
{
public:
virtual void out() override //如果检查出错则无法编译通过
{
cout << "222" << endl;
}
};
三、多态的原理
1.原理
class base
{
public:
virtual void fun1()
{
cout << "base:fun1()" << endl;
}
virtual void fun2()
{
cout << "base:fun2()" << endl;
}
private:
int _b = 1;
};
class derive : public base
{
public:
virtual void fun1()
{
cout << "derive:fun1()" << endl;
}
private:
int _d = 1;
};
void function1(base& b) //运行时,到指向对象的虚表中,查找对应的虚函数的地址
{
b.fun1();
}
//多态如何实现的指向谁就调用谁的虚函数?
//多态是在 运行时 到指向对象的虚表中,查找要调用虚函数的地址来调用
void function2(base b) //编译时,直接确定通过p的类型确定要调用函数的地址
{
b.fun1();
}
int main()
{
base b1;
cout << sizeof(base) << endl; //16 8+4+对齐 == 16
//监视窗口中的_vfptr就是 虚函数表指针:简称虚表指针
//其本质是 指针数组(虚函数指针)
derive d1;
function1(b1);
function1(d1);
return 0;
}
类里面有一个虚表,虚表里面存的指针指向虚函数
重写了,子指针改变指向
没重写,父指哪,子指哪
监视窗口中的_vfptr就是 虚函数表指针:简称虚表指针
其本质是 指针数组(虚函数指针)
一般来说对象前八个字节存的虚表指针
虚表指针数组最后一个指针为0x0000000000000000 表示这个虚函数数组结束了
2.验证虚函数和虚表的位置都是在代码段
class base
{
public:
virtual void fun1()
{
cout << "base::fun1()" << endl;
}
virtual void fun2()
{
cout << "base::fun2()" << endl;
}
virtual void fun3()
{
cout << "base::fun3()" << endl;
}
private:
int _b = 1;
};
class derive : public base
{
public:
virtual void fun1()
{
cout << "derive::fun1()" << endl;
}
private:
int _d = 2;
};
void test()
{
base b1;
printf("_vftptr:%p\n", *(double*)&b1);
printf("_vftptr:%p\n", *(long long*)&b1);
//printf("_vftptr:%p\n", (long long)b1); 直接强制转换语法不接受 取对象前八个字节
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::fun3);
printf("代码段函数地址:%p\n", test);
}
int main()
{
base b1;
derive d1;
test();
return 0;
}
//vs2022没有显示不出fun3()的情况
//有虚函数的对象,前八个字节是虚表指针的地址
四、动静态绑定
静态的绑定:编译时确定函数地址
动态的绑定:运行时到虚表中找虚函数的地址
编译时 是指: 高级语言转换为汇编语言时(初次检查错误)
运行时 是指: 程序开始运行,开始占用cpu和内存
class base
{
public:
virtual void fun1()
{
cout << "base::fun1()" << endl;
}
private:
int _b = 1;
};
class derive : public base
{
public:
virtual void fun1()
{
cout << "derive::fun1()" << endl;
}
private:
int _d = 2;
};
void f1(int i) {}
void f2(double d) {}
int main()
{
//静态的绑定 静态的多态 (静态:编译时确定函数地址)
int i = 0;
double d = 1.1;
f1(i);
f2(d);
//动态绑定 动态的多态 (动态:运行时到虚表中找虚函数的地址)
base* p = new base;
p->fun1();
p = new derive;
p->fun1();
return 0;
}
五、设计函数展现_vftptr所有函数
1. 单继承展现虚表
class base
{
public:
virtual void fun1()
{
cout << "base::fun1()" << endl;
}
virtual void fun2()
{
cout << "base::fun2()" << endl;
}
private:
int b;
};
class derive : public base
{
public:
virtual void fun1()
{
cout << "derive::fun1()" << endl;
}
virtual void fun3()
{
cout << "derive::fun3()" << endl;
}
virtual void fun4()
{
cout << "derive::fun4()" << endl;
}
private:
int d;
};
//void(*p)() 定义一个函数指针变量
typedef void(*VF_PTR)(); //函数指针类型
//打印虚表
//void PrintVFTable(VF_PTR* pTable)
void PrintVFTable(VF_PTR pTable[])
{
for (size_t i = 0; pTable[i] != 0; ++i) //!=nullptr也可以
{
printf("vftabel[%d]:%p->", i, pTable[i]);
VF_PTR f = pTable[i];
f();
}
cout << endl;
}
int main()
{
base b;
derive d;
//base的虚表
PrintVFTable((VF_PTR*)(*(long long*)&b));
PrintVFTable((VF_PTR*)(*(void**)&b));
//derive的虚表
//而监视窗口只有继承下来的fun1和fun2
PrintVFTable((VF_PTR*)(*(long long*)&d));
PrintVFTable((VF_PTR*)(*(void**)&d));
return 0;
}
2.多继承展现虚表
//多继承
class base1
{
public:
virtual void fun1()
{
cout << "base::fun1()" << endl;
}
virtual void fun2()
{
cout << "base::fun2()" << endl;
}
private:
int b1;
};
class base2
{
public:
virtual void fun1()
{
cout << "base::fun1()" << endl;
}
virtual void fun2()
{
cout << "base::fun2()" << endl;
}
private:
int b2;
};
class derive : public base1,public base2
{
public:
virtual void fun1()
{
cout << "derive::fun1()" << endl;
}
virtual void fun3()
{
cout << "derive::fun3()" << endl;
}
private:
int d;
};
//typedef void (*VF_PTR)();
using VF_PTR = void(*)();
void PrintVFTable(VF_PTR pTable[])
{
for (size_t i = 0; pTable[i] != nullptr; ++i)
{
printf("vftabel[%d]:%p->", i, pTable[i]);
VF_PTR f = pTable[i];
f();
}
cout << endl;
}
int main()
{
derive d;
//base1 //监视窗口中只有fun1和fun2
PrintVFTable(( VF_PTR*)(*(void**)&d));
//我们发现derive的fun3存在于base1的虚表中
//base2 //监视窗口也有fun1和fun2
PrintVFTable((VF_PTR*)(*(void**)((char*)&d + sizeof(base1))));
return 0;
}
子类独有的函数,存在第一个虚表中
关于函数指针数组指针的用法
第一种 typedef void(PFUNC)();
第二种 using VF_PTR = void()();
留下了一个问题
在64位平台下base1 和 base2的函数内部只要操作不同,就会导致 base2无法依次访问时遇见nullptr停止,但是从虚表开头开始遍历却没有问题
32位平台下并没有这种问题
六、继承和多态
1.几个关于继承和多态的点
1.inline函数不可以是虚函数,inline函数没有地址,无法把地址放到虚函数表中
2.静态成员不可以是虚函数,静态成员函数没有this指针,使用 类型::成员函数 的调用方式无法访问虚表,所以静态成员函数无法放进虚函数表
3.构造函数不可以是虚函数,对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的
4.对象访问普通函数快还是虚函数快?
如果是普通对象一样快; 如果是指针对象或者是引用对象,普通函数快,构成多态,运行时调用虚函数需要需要到虚表中去查找;
2.继承多态的大小
class A
{
public:
A(int x = 1)
{
cout << "aa" << endl;
}
virtual void Aa(){}
};
class B :virtual public A
{
public:
B(int x = 0)
{
cout << "bb" << endl;
}
virtual void Aa(){}
};
int main()
{
B b;
A a;
cout << sizeof(B) << endl;
printf("%p\n", &A::Aa);
printf("%p\n", &B::Aa); //两个地址不一样,触发了重写
return 0;
}
//虚继承算出来大小是24 : virtual两个指针 8+8, 虚表+8 = 24
//不用虚继承大小是 8 : virtual算指针8字节
3.一道题
计算输出结果
class A
{
public:
A(const char* s)
{
cout << s << endl;
}
};
class B :virtual public A
{
public:
B(const char* s1, const char* s2)
:A(s1)
{
cout << s2 << endl;
}
};
class C :virtual public A
{
public:
C(const char* s1, const char* s2)
:A(s1)
{
cout << s2 << endl;
}
};
class D :public B, public C
{
public:
D(const char* s1,const char* s2,const char* s3,const char* s4)
:B(s1, s2), C(s1, s3), A(s1)
{
cout << s4 << endl;
}
};
int main() {
D* p = new D("class A", "class B", "class C", "class D");
delete p;
return 0;
}
1.A只有一个,所以A只构造一次,优先A构造
2.初始化列表优先于函数体,D最后
3.构造按继承顺序,即使先C(s1,s3),B(s1,s2) 也是B先输出,C再输出
所以结果为ABCD