您的位置:首页 > 其它

构造函数,析构函数,虚函数,内联函数,静态成员函数,重载,覆盖,隐藏

2012-08-20 18:32 666 查看


C++语言特性:构造函数,析构函数,虚函数,内联函数,静态成员函数,重载,覆盖,隐藏

来自:CSDN博客推荐文章 | 时间:2012-07-22 14:19:01

原文链接: http://blog.csdn.net/iamcxl369/article/details/7760188

相关主题: C++

构造函数

1.初始化对象成员;

2.创建类对象;

由于虚函数是建立在对象的基础上的,因此构造函数不能声明为虚函数;虚函数是在执行的时候才识别,根据具体对象进行动态绑定.

每个类对象都有一个默认构造函数.当一个对象被在堆上创建的时候,第一步先执行new操作,第二步才会执行构造函数体,因此尽可能不要在构造函数内部动态申请太多的资源,以免引起内存泄露.

详情如下:

cppBaseClass *base = new cppBaseClass();
012A1FDD  push        0Ch
012A1FDF  call        operator new (12A11E5h)
012A1FE4  add         esp,4
012A1FE7  mov         dword ptr [ebp-11Ch],eax
012A1FED  mov         dword ptr [ebp-4],0
012A1FF4  cmp         dword ptr [ebp-11Ch],0
012A1FFB  je          main+70h (12A2010h)
012A1FFD  mov         ecx,dword ptr [ebp-11Ch]
012A2003  call        cppBaseClass::cppBaseClass (12A1023h)
012A2008  mov         dword ptr [ebp-130h],eax
012A200E  jmp         main+7Ah (12A201Ah)
012A2010  mov         dword ptr [ebp-130h],0
012A201A  mov         eax,dword ptr [ebp-130h]
012A2020  mov         dword ptr [ebp-128h],eax
012A2026  mov         dword ptr [ebp-4],0FFFFFFFFh
012A202D  mov         ecx,dword ptr [ebp-128h]
012A2033  mov         dword ptr [ebp-14h],ecx


拷贝构造函数[复制构造函数]:

如果要动态地为本地C++类的成员分配空间,则必须实现复制构造函数.

下面是拷贝构造函数的实现模版:

ConstructorFunctionClassName(const ConstructorFunctionClassName& ObjectType)
{
}


拷贝构造函数的调用条件:

1.当一个对象以值传递的方式传入函数体;

2.当一个对象以值传递的方式作为函数的返回值;

3.当一个对象通过另外一个对象进行初始化;

为什么需要实现拷贝构造函数?

首先这里需要描述一下C++中默认拷贝构造函数,参考下面代码:

className Object0("Test the constructor function");
className Object1 = Object0; //默认构造函数被调用


默认构造函数会把类对象Object0的指针成员存储地址复制到Object1中,这个时候Object1和Object0同时指向同一块内存区域, 那么如果Oject1被销毁了,那么Object0中的内容就会发生改变.

上面的现象也被OOP中称为 浅拷贝 .

那对应的肯定有 深拷贝 , Object0拥有资源,当上面的Object1在复制的过程中重新分配了资源,那么这个过程就是深拷贝.

className cName;

className::className(cName);

在传递对象cName之前,编译器需要安排创建该对象的副本.因此,编译器为了处理复制构造函数的这条语句,需要调用复制构造函数来创建实参的副本。由于是按值传递,第二次调用同样需要创建实参的副本,因此还得调用复制构造函数,产生无穷调用.

看下面的一段代码:

className  cName("Test default constructor");
testDefaultConstructor(cName);
void testDefaultConstructor( className Object0)
{
Object0.PrintString();
}


实参cName是作为传值的方式传递的,那么Object0将导致默认构造函数被调用:

1.创建cName对象;

2.由于cName是传值方式,因此导致Object0将要创建一个副本,而且该副本同时指向原来对象所指向的数据成员.

3.当退出testDefaultConstructor函数的时候, Object0超出了其作用域,那么Object0的析构函数将要被调用,Object0指向的数据成员将要被释放.

4.当函数从testDefaultConstructor返回的时候, cName对象依然指向之前Object0指向的数据区域.异常发生!

析构函数

1.给对象提供释放资源的机会;

2.销毁对象,销毁不再需要或超出其作用域的对象.当对象超出其作用域时,程序将自动调用类的析构函数.

如果想要防止对像被创建在堆上,可以私有化析构函数,不过这样以来该类不能被继承.

如果需要调用私有析构函数,则需要实现一个成员函数,在该成员函数内部调用
delete this;

虚拟析构函数:

如果当前类作为接口基类,那么需要声明该类的析构函数为虚拟析构函数.

因为如果,当我们使用基类的指针去删除派生类的对象的时候,如果基类的析构函数不是虚拟析构函数,那么派生类的析构函数将会不被执行.

但是当生命了虚函数之后,在对象中就会生成一个虚函数表,这样会增加类对象的存储空间开销.

下面部分是基于基类虚拟析构函数的条件下反汇编出来的:
cppBaseClass *base = new cppDeriveClass();
002D235D  push        10h
002D235F  call        operator new (2D121Ch)
002D2364  add         esp,4
002D2367  mov         dword ptr [ebp-104h],eax
002D236D  mov         dword ptr [ebp-4],0
002D2374  cmp         dword ptr [ebp-104h],0
002D237B  je          main+70h (2D2390h)
002D237D  mov         ecx,dword ptr [ebp-104h]
002D2383  call        cppDeriveClass::cppDeriveClass (2D1069h)
002D2388  mov         dword ptr [ebp-118h],eax
002D238E  jmp         main+7Ah (2D239Ah)
002D2390  mov         dword ptr [ebp-118h],0
002D239A  mov         eax,dword ptr [ebp-118h]
002D23A0  mov         dword ptr [ebp-110h],eax
002D23A6  mov         dword ptr [ebp-4],0FFFFFFFFh
002D23AD  mov         ecx,dword ptr [ebp-110h]
002D23B3  mov         dword ptr [ebp-14h],ecx
delete base;
002D23B6  mov         eax,dword ptr [ebp-14h]
002D23B9  mov         dword ptr [ebp-0ECh],eax
002D23BF  mov         ecx,dword ptr [ebp-0ECh]
002D23C5  mov         dword ptr [ebp-0F8h],ecx
002D23CB  cmp         dword ptr [ebp-0F8h],0
002D23D2  je          main+0D9h (2D23F9h)
002D23D4  mov         esi,esp
002D23D6  push        1
002D23D8  mov         edx,dword ptr [ebp-0F8h]
002D23DE  mov         eax,dword ptr [edx]
002D23E0  mov         ecx,dword ptr [ebp-0F8h]
002D23E6  mov         edx,dword ptr [eax]
002D23E8  call        edx
002D23EA  cmp         esi,esp
002D23EC  call        @ILT+445(__RTC_CheckEsp) (2D11C2h)
002D23F1  mov         dword ptr [ebp-118h],eax
002D23F7  jmp         main+0E3h (2D2403h)
002D23F9  mov         dword ptr [ebp-118h],0
return 0;
002D2403  xor         eax,eax
}


虚函数

虚函数主要是实现了面向对象中的多态的作用.通俗的讲就是通过基类的指针指向派生类的对象,用基类的指针来调用派生类的成员函数. 这种技术可以让派生类拥有“多种形态”,这是一种泛型技术, 通过使用不变的代码来实现可变的算法.比如:模版技术, RTTI[Run-Time Type Identification], 虚函数技术.

虚函数是以virtual关键字声明的基类函数.如果在基类中将某个函数指定为virtual,并且派生类中有该函数的另外一个定义,则编译器将知道我们不想静态链接该函数.

当基类中声明了虚函数,那么根据调用该函数的当前对象的类型,选择派生类中出现的该函数的其他定义.

纯虚函数 , 通过在函数声明最后添加=0,可以将本地C++基类中的虚函数定义为纯虚函数. 那么该类就称为不能创建任何对象的抽象类,在任何派生类中,都必须定义所有纯虚函数.如果不是,则该派生类也将称为抽象类.

根据虚函数定义后的虚函数表,基类指针既可以指向派生类的成员,也可以指向基类的成员.

虚函数表 ,

下面部分内容参考陈皓专栏

/article/2551883.html

#include <iostream>
using std::cout;
using std::endl;

class baseClass
{
public:
virtual void a(){ cout<<"baseClass function a()"<<endl;}
virtual void b(){ cout<<"baseClass function b()"<<endl;}
virtual void c(){ cout<<"baseClass function c()"<<endl;}
};


#include "cppVirtualFunctionTable.cpp"
int main(int argc, char** argv)
{
// define the Func is an alias for the function pointer.
typedef void(*Func)(void);

baseClass cBase;
Func pFunc = NULL;
cout<<"虚函数表地址:"<<(int*)(&cBase)<<endl;
cout<<"虚函数表第一个函数地址:"<<(int*)*(int*)(&cBase)<<endl;

// Invoke first virtual function
pFunc = (Func)*((int*)*(int*)(&cBase));
pFunc();
return 0;
}


环境:Microsoft Visual Studio 2010, Window 7
输出结果 :

虚函数表地址:002FF8D8

虚函数表第一个函数地址:00EF7868

baseClass function a()

请按任意键继续. . .

下面是虚函数表的详细情况:





(int*)(&cBase) //强制转换cBase对象在内存中的内容为int*[0x0031fce4],它指向cBase对象的第一个成员的地址,即虚函数表的地址[0x011e7868]

(int*)*(int*)(&cBase) //对虚函数表的地址进行解引用,*(int*)(&cBase)指向的地址就是虚函数表的地址,然后转换为int*,它指向虚函数表的第一个成员,即_vfptr[0] ,地址为[0x011e121c]

下面是关于基类地址,以及虚函数表、基类成员、派生类成员的详细分布:



从上面的图中我们可以看到,派生类的成员a()覆盖了基类的成员a()

C++中的重载、覆盖、隐藏

1.重载从最简单的角度来讲只发生在对象内部,对象内部同名的函数,但是参数个数或参数类型不同;

2.覆盖就是上面图中标示的那种情况;

c.当派生类和基类的函数同名,而且基类同名函数前virtual修饰符,基类的同名函数被隐藏;

内联函数

关键字inline,功能类似宏替换,具有函数的结构,在编译时刻根据函数名来替换成对应的代码,在代码量小的重复次数多的情况下,比较高效.不是适合复杂代码,同时是否能够实现内联的功能,具体要依赖编译器,有可能编译器根据实际情况当成普通函数来处理.

静态成员函数

静态成员在同类对象中只有一个实例,因此可以用来统计同类对象的计数;

静态成员函数独立于类对象,因此即使类对象不存在,静态成员函数依然可以被调用,静态成员函数只能调用静态成员变量在这样的情况下.

实例:Singleton模式,保证一个类只有一个实例对象,同时使该实例只有一个全局访问点.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: