您的位置:首页 > 编程语言 > C语言/C++

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

2012-07-22 18:53 549 查看
C++中class默认属性为private, struct默认属性为public

构造函数

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指向的数据区域.异常发生!

PS:

当类对象中有指针成员时必须用深拷贝,浅拷贝会导致指针成员变成野指针的风险;

传值的时候,对象的副本产生,但是该副本和原始对象都包含了指针成员,同时该指针成员指向的地址是相同的,那么在完成了传值的作用域之后,该副本对象会自动销毁,因此必然导致该副本的析构函数被调用[通常会在析构函数中释放指针成员的资源],那么当真正的对象被习够的时候,就会再次处理该指针成员.

析构函数:

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++基类中的虚函数定义为纯虚函数. 那么该类就称为不能创建任何对象的抽象类,在任何派生类中,都必须定义所有纯虚函数.如果不是,则该派生类也将称为抽象类.

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

虚函数表,

下面部分内容参考陈皓专栏
http://blog.csdn.net/haoel/article/details/1948051
cppVirtualFunctionTable.cpp

#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;}
};

class deriveClass: public baseClass
{
public:
void a(){ cout<<"deriveClass function a()"<<endl;}
void b1(){ cout<<"deriveClass function b1()"<<endl;}
void c1(){ cout<<"deriveClass function c1()"<<endl;}
};


main.cpp

#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

输出结果:

虚函数表地址:0031fce4

虚函数表第一个函数地址:011e7868

baseClass function a()

请按任意键继续. . .

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



图1

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

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

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



图2

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

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

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

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

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

“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无 virtual 关键字,基类的函数将被隐藏(注意别与重载混淆) 。

(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆) 。

内联函数:

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

静态成员函数:

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

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

实例:Singleton模式,保证一个类只有一个实例对象,同时使该实例只有一个全局访问点.

C++对象属性:public, protected, private

共有继承,基类的公共成员和保护成员可以作为其派生类的公共成员和保护成员.派生类无法访问基类的私有成员.

私有继承,基类的共有成员和私有成员都作为其派生类的私有成员.基类的成员只能由直接派生类来访问,不能再往下继承。

保护继承,基类的共有成员和保护成员都作为其派生类的保护成员,基类的共有成员和保护成员只能被其派生类成员和友元函数访问.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: