回顾C++
目录
1. 多态
例子:
#include <iostream>
using namespace std;
class Person
{
public:
virtual void speak()
{
cout<<"speak"<<endl;
}
};
class Chinese : public Person
{
public:
void speak()
{
cout<<"speak Chinese"<<endl;
}
};
class English : public Person
{
public:
void speak()
{
cout<<"speak English"<<endl;
}
};
int main(int argc, char const *argv[])
{
/* code */
Person* p[3];
Chinese c1;
Chinese c2;
English e1;
p[0] = &c1;
p[1] = &c2;
p[2] = &e1;
cout<<sizeof(Person)<<endl;
cout<<sizeof(Chinese)<<endl;
cout<<sizeof(English)<<endl;
for(int i = 0; i<3; i++)
{
p[i]->speak();
}
return 0;
}
如果不写virtual,则每个size都是1,相当于空类,且都输入speak;如果加上virtual关键字,则每个size都是8(32位则是4),是从基类继承的一个虚函数表指针,且根据派生类的不同输出speak Chinese和speak English。
1.1 父类指针指向子类对象
我们创建p是一个Person指针数组,里面的Person*指向每一个它的子类Chinese或English。
Person* p[0] = &Chinese;
Person* p[1] = &English;
由于函数地址早绑定,如果不是虚函数,每个输出都会是Person类的speak。而动态多态可以实现地址晚绑定。
1.2 虚基类表指针和虚基类表
vbptr指的是虚基类表指针(virtual base table pointer),该指针指向了一个虚基类表(virtual table),虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员。
带有虚函数的类称为虚基类,子类继承虚基类。在C++中虚基类有一个虚函数表指针保存虚函数表地址,而虚函数表保存函数地址。虚函数表并不在虚基类里,但是虚函数表指针在虚基类里,子类继承虚基类,子类也就有了虚函数表指针。
#include <iostream>
class Base {
public:
Base() {
std::cout << "Base::constructor run" << std::endl;
}
virtual void fun1() {
std::cout << "Base::fun1 run" << std::endl;
}
virtual void fun2() {
std::cout << "Base::fun2 run" << std::endl;
}
virtual ~Base() {
std::cout << "Base::desconstructor run" << std::endl;
}
};
class Derive : public Base {
public:
Derive() {
std::cout << "Derive::constructor run" << std::endl;
}
void fun1() {
std::cout << "Derive::fun1 run" << std::endl;
}
void fun3() {
std::cout << "Derive::fun3 run" << std::endl;
}
~Derive() {
std::cout << "Derive::desconstructor run" << std::endl;
}
};
void test()
{
Derive* d = new Derive();
d->fun1(); //Derive::fun1 run
d->fun2(); //Base::fun2 run
d->fun3(); //Derive::fun3 run
delete d;
}
void test2()
{
Base* b = new Derive();
b->fun1(); //Derive::fun1 run
b->fun2(); //Base::fun2 run
delete b;
}
int main()
{
test2();
}
从输出可以看出派生类从基类继承了虚函数表指针vfptr,且占用字节数大小是4字节,刚好就是一个指针占用字节数。
虚函数表vftable里保存了派生类成员函数fun1,基类成员函数fun2的地址,由于派生类成员函数fun3不是属于基类的函数,因此虚函数表里没有fun3的地址。
基类指针b调用fun1的过程:通过虚函数表指针vfptr找到虚函数表vftable,再通过虚函数表找到派生类成员函数fun1的地址,调用派生类成员函数fun1。
基类指针b调用fun2的过程:通过虚函数表指针vfptr找到虚函数表vftable,再通过虚函数表找到基类成员函数fun2的地址,调用基类成员函数fun2。
1.3 动态多态满足条件*
继承,子类重写父类虚函数,父类指针/引用指向子类对象
1.3.1 重写和重载区别:
重载:同一个作用域下,函数名相同,参数顺序/个数/类型不同,返回值无所谓。
重写:函数返回值、名称、参数完全相同
1.4 虚基类表指针和虚函数表指针
虚基类表指针:虚继承时(解决菱形继承问题),派生类中会有一个指向虚基类表的指针。
虚函数表指针:多态时,表中记录虚函数的地址
【C++】虚表与虚表指针,虚基类表与虚基类表指针_虚基表指针-CSDN博客
黑马的截图。如果子类中没有重写speak函数,是一个空类的话,那么大小依然为4(继承父类的虚函数表指针)
1.5 纯虚函数和抽象类
1.5.1 纯虚函数
由于父类中定义的虚函数在多态中通常没有意义,所以可以把它改成纯虚函数。
语法:
virtual 返回值 函数名(参数列表) = 0;
1.5.2 抽象类
当一个类中有纯虚函数,这个类就是抽象类。
特点:
- 无法实例化对象(无论堆区栈区)
- 子类中必须重写这个纯虚函数,否则也为抽象类
#include <iostream>
using namespace std;
class Base
{
public:
virtual void get() = 0;
};
class Child : public Base
{
public:
void get()
{
cout<<"haha"<<endl;
}
};
int main(int argc, char const *argv[])
{
// Base b; //报错
Child c;
c.get();
return 0;
}
1.6 虚析构和纯虚析构
问题:使用多态时,如果父类是虚基类,那么子类开辟到堆区的内容,父类指针释放时无法调用到子类的析构代码。
例子:由于子类不会调用析构函数导致子类在堆区开辟的内存无法释放干净。
#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
cout<<"Base gouzao"<<endl;
}
virtual void get() = 0;
~Base()
{
cout<<"Base xigou"<<endl;
}
};
class Child : public Base
{
public:
Child(int a)
{
cout<<"Child gouzao"<<endl;
m_B = new int(a);
}
void get()
{
cout<<"haha"<<endl;
}
~Child()
{
cout<<"Child xigou"<<endl;
if(m_B != NULL)
{
delete m_B;
m_B = NULL;
}
}
int* m_B;
};
int main(int argc, char const *argv[])
{
//Child c(10);
//Base *b = &c;
Base *b = new Child(10);
b->get();
delete b;
return 0;
}
输出:
Base gouzao
Child gouzao
haha
Base xigou
改正:父类析构函数改为虚析构/纯虚析构
注意,纯虚析构也需要在类外实现。
#include <iostream>
using namespace std;
class Base
{
public:
Base()
{
cout<<"Base gouzao"<<endl;
}
virtual void get() = 0;
// virtual ~Base()
// {
// cout<<"Base xigou"<<endl;
// }
virtual ~Base() = 0;
};
Base::~Base()
{
cout<<"Base xigou"<<endl;
}
1.7 整体例子1
注意,这里的Computer类不需要虚析构。
#include <iostream>
using namespace std;
class CPU
{
public:
virtual void calculate() = 0;
};
class VideoCard
{
public:
virtual void display() = 0;
};
class Memory
{
public:
virtual void storage() = 0;
};
class Computer
{
public:
Computer(CPU *cpu, VideoCard *card, Memory *memory)
{
this->cpu = cpu;
this->card = card;
this->memory = memory;
}
void work()
{
this->card->display();
this->cpu->calculate();
this->memory->storage();
}
~Computer()
{
if(this->cpu != NULL)
{
delete this->cpu;
this->cpu = NULL;
}
if(this->card != NULL)
{
delete this->card;
this->card = NULL;
}
if(this->memory != NULL)
{
delete this->memory;
this->memory = NULL;
}
}
private:
CPU *cpu;
VideoCard *card;
Memory *memory;
};
class IntelCPU : public CPU
{
void calculate()
{
cout<<"Intel CPU"<<endl;
}
};
class LenovoCPU : public CPU
{
void calculate()
{
cout<<"Lenovo CPU"<<endl;
}
};
class IntelVideoCard : public VideoCard
{
void display()
{
cout<<"Intel Video Card"<<endl;
}
};
class LenovoVideoCard : public VideoCard
{
void display()
{
cout<<"Lenovo Video Card"<<endl;
}
};
class IntelMemory : public Memory
{
void storage()
{
cout<<"Intel Memory"<<endl;
}
};
class LenovoMemory : public Memory
{
void storage()
{
cout<<"Lenovo Memory"<<endl;
}
};
void test()
{
CPU *my_cpu = new IntelCPU;
VideoCard *my_card = new LenovoVideoCard;
Memory *my_memory = new IntelMemory;
Computer *computer1 = new Computer(my_cpu, my_card, my_memory);
computer1->work();
delete computer1;
cout<<"---------"<<endl;
Computer *computer2 = new Computer(new LenovoCPU, new LenovoVideoCard, new LenovoMemory);
computer2->work();
delete computer2;
}
int main(int argc, char const *argv[])
{
/* code */
test();
return 0;
}