-
-
[旧帖] [原创]C++ virtual 深刻理解 0.00雪花
-
发表于: 2011-3-23 09:49 2480
-
高手路过,小弟第一次在看雪上发表帖子,请大家勿轻拍砖。
Virtual 是C++中一个重要的关键字,只要是学过C++的人都知道在类Base中加了Virtual关键字的函数就是虚拟函数(例如函数print),于是在Base的派生类Derived中就可以通过重写虚拟函数来实现对基类虚拟函数的覆盖。当基类Base的指针point指向派生类Derived的对象时,对point的print函数的调用实际上是调用了Derived的print函数而不是Base的print函数。这是面向对象中的多态性的体现(改部分引用自http://blog.csdn.net/ring0hx/archive/2007/05/11/1605254.aspx)
好那我们来看一段代码
#include <iostream>
using namespace std;
//class a
class A
{
public:
A();
void ShowMe();
private:
int m_a;
};
//class b
class B :public A
{
public:
B();
void ShowMe();
private:
int m_b;
};
A::A()
{
m_a =1;
}
void A::ShowMe()
{
cout<<"This is A"<<endl;
}
B::B()
{
m_b=2;
}
void B::ShowMe()
{
cout<<"This is B"<<endl;
}
int main()
{
B *pb=new B();
pb->ShowMe();
dynamic_cast<A*>(pb)->ShowMe();
delete pb;
return 0;
}
输出结果是什么呢?
现在我们来分析一下以下4中情况,针对ShowMe函数
(1)A没有virual, B有。
(2)A 没有virtual,B也没有。
(3)A有virtual, B没有
(4)A 有virtual ,B 也有。
输出结果又是什么样的呢?
经过实际测试我们发现,如果A类中的showme函数前面没有virtual时,不论B类中的showme函数有没有vittual关键字,输出结果都是 B A。A类中的showme函数前面有virtual关键字,不论B类中的showme函数有没有virtual关键字,输出结果都是 B B。
也就是说,在类的函数前面加virtual关键字时就是允许子类覆盖它。即使把子类的指针转换为基类指针时调用的也是子类的函数。
现在我们来看另外的一个应用,virtual如果要加在析构函数上,会是什么目的呢?
#include <iostream>
using namespace std;
//定义抽象类
class CAnimal
{
protected:
int m_weight; //重量
char *m_pname;
public:
virtual void Cry() = 0;
//virtual ~CAnimal();
~CAnimal();
CAnimal();
};
CAnimal::CAnimal()
{
m_pname =NULL;
m_pname= new char[20];
if (NULL !=m_pname)
{
strncpy(m_pname,"Animal",20);
}
}
CAnimal::~CAnimal()
{
if (NULL != m_pname)
{
cout<<"delete "<<m_pname<<endl;
delete m_pname;
}
}
//定义具体类
class CFish : public CAnimal
{
public:
virtual void Cry()
{
cout<<"I am Fish!"<<endl;
}
CFish();
~CFish();
protected:
char *m_pFishName;
};
CFish::CFish()
{
m_pFishName=NULL;
m_pFishName= new char[20];
if(NULL != m_pFishName)
{
strncpy(m_pFishName,"Fish",20);
}
}
CFish::~CFish()
{
if (NULL != m_pFishName)
{
cout<<"delete "<<m_pFishName<<endl;
delete m_pFishName;
}
}
class CBird : public CAnimal
{
public:
virtual void Cry()
{
cout<<"I am Bird!"<<endl;
}
CBird();
~CBird();
protected:
char *m_pBirdName;
};
CBird::CBird()
{
m_pBirdName=NULL;
m_pBirdName= new char[20];
if(NULL != m_pBirdName)
{
strncpy(m_pBirdName,"Bird",20);
}
}
CBird::~CBird()
{
if (NULL != m_pBirdName)
{
cout<<"delete "<<m_pBirdName<<endl;
delete m_pBirdName;
}
}
//定义工厂类
class CFactory
{
public:
CAnimal* CreateObj(int flag)
{
switch(flag)
{
case 0:
return new CBird();
break;
case 1:
return new CFish();
break;
default:
break;
}
}
};
//测试类
int main(int argc, char* argv[])
{
//定义工厂对象
CFactory *factory=new CFactory();
CAnimal* pAnimal;
pAnimal = factory->CreateObj(0);
pAnimal->Cry();
delete pAnimal;
pAnimal = factory->CreateObj(1);
pAnimal->Cry();
delete pAnimal;
return 0;
}
代码写的比较烂,大家凑合着看吧。输出结果是什么呢?
我们的子类中的析构函数没有被调用,导致我们在子类中申请的空间没有被释放,造成了内存的泄漏。
现在我们在抽象类的析构函数前加上virtual那结果会怎么样呢?
现在子类的析构函数也被调用了。
这是在工厂模式中 ,实例化一个工厂类的时候只会调用抽象类的析构函数如果不在抽象类的析构函数前加virtual,一旦加了virtual就会调用子类的析构函数。具体书籍请参考effective c++ 3td。
首先,每个派生类对象都有一个指向虚函数表的指针,访问任何虚函数都是间接通过这个指针进行的,之所以要用虚函数表,是因为调用一个虚函数的哪个版本是在运行过程(调用时指针所指的对象)才能确定的(动态绑定)。 相对于虚函数,实函数的调用机制就简单的多:由于每个实函数只有一个版本,因此调用地址在编译时即可确定(静态绑定) 析构函数也可以通过virtual修饰而声名为虚函数,虚析构函数与一般虚函数的不同之处在于: 1》它的重定义函数就是派生类的析构函数,但不要求同名。 2》一个虚析构函数的版本被调用后,接着就要调用执行基类版本,依次类推,直到调用执行了派生序列的最开始的那个虚析构函数版本为止。////////////////////////////////////////////////////////////////////////////////// 以上引自《全国计算机等级考试二级教程——C++语言程序设计》
请牛人指教,大家相互探讨一下。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
赞赏
- X86/X64 HOOK库 MinHook 来自codeproject 16428
- [原创]c++中调用64位汇编函数 13172
- [原创]反编译黑莓手机COD格式文件(首发看雪和maxpda)) 12842
- [原创]C++ virtual 深刻理解 2481