上面的初始化赋值变量a还正确吗?当然错误,否则在语法上这就算一个 漏洞了(外界可以借此修改不能修改的成员)。但有些时候的确又需要进行初始化以保证一些逻辑关系,为此C++提出了构造和析构的概念,分别对应于初始化和扫尾工作。在了解这个之前,让我们先看下什么叫实例(Instance)。
实例是个抽象概念,表示一个客观存在,其和下篇将介绍的“世界”这个概念联系紧密。比如:“这是桌子”和“这个桌子”,前者的“桌子”是种类,后者的“桌子”是实例。这里有10只羊,则称这里有10个羊的实例,而羊只是一种类型。可以简单地将实例认为是客观世界的物体,人类出于方便而给各种物体分了类,因此给出电视机的说明并没有给出电视机的实例,而拿出一台电视机就是给出了一个电视机的实例。同样,程序的代码写出来了意义不大,只有当它被执行时,我们称那个程序的一个实例正在运行。如果在它还未执行完时又要求操作 系统执行了它,则对于多任务操作系统,就可以称那个程序的两个实例正在被执行,如同时点开两个Word文件查看,则有两个Word程序的实例在运行。
在C++中,能被操作的只有数字,一个数字就是一个实例(这在下篇的说明中就可以看出),更一般的,称标识记录数字的内存的地址为一个实例,也就是称变量为一个实例,而对应的类型就是上面说的物体的种类。比如:long a, *pA = &a, &ra = a;,这里就生成了两个实例,一个是long的实例,一个是long*的实例(注意由于ra是long&所以并未生成实例,但ra仍然是一个实例)。同样,对于一个自定义类型,如:Radiogram ab, c[3];,则称生成了四个Radiogram的实例。
对于自定义类型的实例,当其被生成时,将调用相应的构造函数;当其被销毁时,将调用相应的析构函数。谁来调用?编译器负责帮我们编写必要的代码以实现相应构造和析构的调用。构造函数的原型(即函数名对应的类型,如float AB( double, char );的原型是float( double, char ))的格式为:直接将自定义类型的类型名作为函数名,没有返回值类型,参数则随便。对于析构函数,名字为相应类型名的前面加符号“~”,没有返回值类型,必须没有参数。如下:
struct ABC { ABC(); ABC( long, long ); ~ABC(); bool Do( long ); long a, count; float *pF; };
ABC::ABC() { a = 1; count = 0; pF = 0; }
ABC::ABC( long tem1, long tem2 ) { a = tem1; count = tem2; pF = new float[ count ]; }
ABC::~ABC() { delete[] pF; }
bool ABC::Do( long cou )
{
float *p = new float[ cou ];
return false;
delete[] pF;
pF = p;
count = cou;
return true;
}
extern ABC g_ABC;
void main(){ ABC a, &r = a; a.Do( 10 ); { ABC b( 10, 30 ); } ABC *p = new ABC[10]; delete[] p; }
ABC g_a( 10, 34 ), g_p = new ABC[5];
上面的结构ABC就定义了两个构造函数(注意是两个重载函数),名字都为ABC::ABC(实际将由编译器转成不同的符号以供连接之用)。也定义了一个析构函数(注意只能定义一个,因为其必须没有参数,也就无法进行重载了),名字为ABC::~ABC。
再看main函数,先通过ABC a;定义了一个变量,因为要在栈上分配一块内存,即创建了一个数字(创建装数字的内存也就导致创建了数字,因为内存