虚函数
虚函数主要用来解决基类指针指向派生类时实际指向基类的问题 问题示例
虚析构
虚析构是为了解决基类指针指向派生类时调用指针进行析构不能析构派生类可能导致内存泄漏问题 问题示例
纯虚函数示例
纯虚函数是基类为派生类专门声明的接口,因此派生类如果需要实例化则必须对该纯虚函数进行定义,否则定义不完整不能实例化。
类似纯虚类也是,专门用来提供派生类覆盖模板,派生类必须完全实现才能实例化。
overwrite
-
用在基类函数后表示该函数必须被派生类进行重写
-
用在派生类明确表示是对基类的函数覆盖,防止写错函数名或参数表
此处实验实例化含未实现虚函数的对象实例化会报链接错误,即实例化的类中不能包含未被定义的函数
虚函数原理和vtb
- 首先区分栈区,代码区,比如new class ,new出的只是类的成员变量部分,而函数在代码区
- 如果一个类包含虚函数会在new出的对象开始部分增加一个指针大小的空间,用来存储vtab的地址
- 如果一个类包含虚函数类的大小会增加(64位上前8后4)
- 含虚函数的类首地址指向虚函数表,而同一个虚函数类同继承的不同实例指向同一个vtab,同一个虚函数的不同继承的实例指向不同vtab
- 测试代码
虚函数示例代码
#include <iostream>
using namespace std;
class A{
public:
void Pt(){
std::cout << "Pt A" << std::endl;
}
};
class B:public A{
public:
void Pt(){
std::cout << "Pt B" << std::endl;
}
};
int main(int argc,char** argv){
A* oa = dynamic_cast<A*>(new B);
oa->Pt();//Pt A
return 0;
}
虚析构示例代码
#include <iostream>
using namespace std;
class A{
public:
A(){std::cout << "construct A" << std::endl;}
~A(){std::cout << "disconstruct A" << std::endl;}
};
class B:public A{
public:
B(){std::cout << "construct B" << std::endl;buf = new char[50];}
~B(){std::cout << "disconstruct B" << std::endl;delete buf;}
private:
char* buf;
};
int main(int argc,char** argv){
A* oa = dynamic_cast<A*>(new B);
delete oa;
/*construct A
construct B
disconstruct A
这里看出对象B析构函数未被执行,因此可能会造成内存泄漏*/
return 0;
}
/*
当两个析构函数都加上virtual前缀后B析构,因此一般在写析构时都会写成虚析构,防止内存泄漏
construct A
construct B
disconstruct B
disconstruct A
*/
vtab示例代码
#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>
std::string to_hex(unsigned char* data, int len) {
std::stringstream ss;
ss << std::uppercase << std::hex << std::setfill('0');
for (int i = 0; i < len; i++) {
ss << std::setw(2) << static_cast<unsigned>(data[i]);
}
return ss.str();
}
class A{
private:
int a;
public:
A():a(11){};
void p();
};
class B{
protected:
int a;
public:
B():a(11){};
virtual void p(){ std::cout << "B Class"<< a << std::endl;};
};
class C:public B{
public:
void p(){std::cout << "C Class"<< a << std::endl;};
};
int main()
{
A a1;
unsigned char* p1 = reinterpret_cast<unsigned char*>(&a1);
std::cout << to_hex(p1,sizeof(a1)) << std::endl;//0B000000
std::cout <<sizeof(a1) << std::endl;//4
B b1;
unsigned char* pb1 = reinterpret_cast<unsigned char*>(&b1);
std::cout << to_hex(pb1,sizeof(b1) ) << std::endl;//40204000000000000B0000009D7F0000
std::cout <<sizeof(b1) << std::endl;
C c1;
unsigned char* pc1 = reinterpret_cast<unsigned char*>(&c1);
std::cout << to_hex(pc1,sizeof(c1) ) << std::endl;//28204000000000000B0000009D7F0000
std::cout <<sizeof(b1) << std::endl;
C c2;
unsigned char* pc2 = reinterpret_cast<unsigned char*>(&c2);
std::cout << to_hex(pc2,sizeof(c2) ) << std::endl;//28204000000000000B0000009D7F0000
std::cout <<sizeof(c2) << std::endl;
long* vptr_head = (long*)&c2;//取虚函数表首地址
long rela_func_ptr = *((long*)*vptr_head);
void (*pf)();
pf = (void(*)())(rela_func_ptr);
pf();//未能正确执行,待学习函数指针后再实验
return 0;
}