C++的安全性高也体现在提供了构造与析构函数区初始化变量、清除变量。
1) C语言的初始化
l 全局变量和静态变量
对于基本类型如int、float、char、int*,默认初始值分别为0、0、'/0'、0。结构体或联合体也会被初始化。
l 除了全局变量和静态变量以外, 其它变量如果没有赋初值, 则默认初始值为内存中的垃圾内容,对于垃圾内容不能有任何假设。
l 用malloc()和realloc() 动态分配的内存也存在垃圾内容,因此在使用内存前最好都赋初值。
注意:如果初始值是随机值,有可能会导致程序崩溃,比如指针是随机值。
2) 数组/结构体初始化
int age[10] = {10};
第一个元素被初始化为10,其他为0。如果没有初始化,将是随机值。
struct data
{
int a;
float b;
};
data c[10] = { {1,1.4}};
其他为0
3) 默认构造函数
如果没有定义任何的构造函数,则编译器使用默认构造函数。默认构造函数不能为你将所有数据都清零。
一般来说,尽量不用默认构造函数,自己手动定义一个。
4) 拷贝构造函数
l 默认拷贝构造函数
系统默认的拷贝构造函数是浅拷贝,即按位拷贝。
l 什么时候需要定义自己的拷贝构造函数
当需要按值传递,并且需要深拷贝时(拷贝指针指向的内容);否则应该禁止拷贝构造。
提示:深层拷贝时,为提高效率,可不用每次都拷贝内存,用写时拷贝(引用计数)的原理,可在改变对象时才创建一个新对象,拷贝内存。
l 如何禁止默认的拷贝构造
1) 只声明拷贝构造,不实现
2) 定义为私有的拷贝构造
l 子类的拷贝构造需要调用基类的拷贝构造
class A
{
};
class B:public A
{
public:
B(const B& b):A(b)
{
}
};
5) 为什么构造和析构没有返回值
l 构造函数
从基本语义上看,构造函数应该返回的是构建的对象本身。编译器知道它该返回什么,对于用户来说它是没有返回值的。
如果构造函数允许用户指定类型,假如是int,那么会出现如下的尴尬:
class A
{
public:
int A(){}
};
1) A a = A(); //类型不匹配
2) void Func( A& a)
void Func( int a )
Func( A() ); //调用的是哪一个呢,这样就会出现歧义。
l 析构函数
析构函数和构造函数不一样,其不需要用户明确的调用它,超出生命周期后其自然被调用,如果在堆上构造的,可能需要delete来间接的调用。
从基本语义上来看,一个对象析构后,是不需要知道它的返回值了。
6) 什么时候需要手动调用析构函数
l 当用new placement将对象构建在特定内存时,由于它没有分配内存,所以一般不delete,这时需要手动调用对象的析构函数。
关于new placement请参考
http://blog.youkuaiyun.com/yeming81/archive/2010/06/15/5673019.aspx
7) 什么时候析构不会被调用
l 对于局部变量,当程序调用exit(n) 或abort()时,析构函数不会被调用
l 对于全局或局部static对象、全局对象,当程序调用exit(n),析构函数会被调用;当调用abort()时,析构函数不会调用。
关于abort/exit,请参考
http://blog.youkuaiyun.com/yeming81/archive/2010/06/16/5673052.aspx
8) 析构的时机
当超出对象的生命周期时,析构函数被调用;
即使是goto或者nonlocal goto(setjmp/longjmp) 其也会调用(Microsoft C++编译器会,其他可能不会,所以不具备移植性)。
例子如下
class A
{
public:
A(){}
~A(){}
};
local goto(在一个函数内跳转)
int main()
{
{
A a;
goto Label1;
}
Label1:
cout<<”end”<<endl;
return 0;
}
nonlocal goto(在不同函数内跳转)
jmp_buffer jmpbuf;
void Func()
{
cout << “ get in “ <<endl;
A a;
longjmp(jmpbuf, 1);
cout << “ never get here “<<endl;
}
int main()
{
cout << “ get in main “ <<endl
if( setjmp (jmpbuf ) == 0 )
{
cout<<”set lable”<<endl;
Func();
}
else
{
cout<<” jmp to here”<<endl;
}
}
关于C跳转,请参考
http://blog.youkuaiyun.com/yeming81/archive/2010/05/31/5637734.aspx
9) 异常
l 全局或局部静态对象的构造和析构函数抛出异常时,是无法被捕获的(因为不知道在哪里捕获),默认将调用terminate函数。
l 当抛出一个异常时,是先要析构对象,才能处理异常的。这时候,如果析构函数再抛出一个异常,异常处理过程被中断,默认将调用terminate函数。
l 当构造函数抛出异常时,即构造函数没有成功完成,对象的析构函数肯定也不会执行。
l 所以,一般构造函数和析构函数不要抛出异常。
关于异常的详细介绍,请参考
http://blog.youkuaiyun.com/yeming81/archive/2010/06/16/5673070.aspx