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

C++的构造函数 和 析构函数的问题

2005-08-11 14:55 393 查看
在C++里一个类即便是在不写构造函数的情况下系统也会自动产生一个默认的构造函数。对一个没有继承和被继承关系的单独类来说,构造函数的问题不算是大问题。但是当一个类继承其他类时,构造函数的调用多少让新手郁闷……我们来看例子,这个例子在win2000 Sp4+Vc6 sp6 下调试的。
#include <iostream.h>
class A
{  
 public:
  void Func(void){ cout << "Func of class A" << endl; }
  A();                              //无参的构造函数
  A(const A &_Copy);  //拷贝构造函数
  A(int Length, int Weight);  //有参构造函数
  A(int Length, int Weight,int Age);  //有参构造函数
  ~A();  //析构函数
  void operator =(const A srcObject);  //赋值函数
 private:
  int   mLength;
  int   mWeight;
  const int  mAge;  //常量成员
};
A::A():mAge(0)
{
 cout<<"Now Class A's Non-param Construct Funcaiton Called."<<endl;
 A::mLength=0;
 A::mWeight=0;
 
}
A::A(int Length, int Weight, int Age):mAge(Age)
{
 cout<<"Now Class A's param Construct Funcaiton Called."<<endl;
 A::mLength = Length;
 A::mWeight = Weight;
}

A::A(const A &_Copy):mAge(_Copy.mAge)
{
 cout<<"Now Class A's Copy Construct Funcation Called."<<endl;
 A::mLength=_Copy.mLength;
 A::mWeight=_Copy.mWeight;
}
A::~A()
{
 cout<<"Now Class A's Destruct Funcaiton Called."<<endl;
}

void A::operator =(const A srcObject)
{
 cout<<"Now Class A's Equ Funcaiton Called."<<endl;
 A::mLength = srcObject.mLength;
 A::mWeight = srcObject.mWeight;
}

class B:public A
{
 public:
  B();
  ~B();
 private:
  A  mMan;
};
B::B()://mMan(180,180,180)
{
 cout<<"Class B's Non-param Construct Funcation Called"<<endl;
 mMan = A(180,180,180);
}
B::~B()
{
 cout<<"Class B's Destruct Funcation Called"<<endl;
}

int main()
{
A Object1;  
A Object2(183,120,1);
 Object1 = Object2;
 B ObjectB;
 return 1;
}

先来说说什么是拷贝构造函数,这个函数其实是用来在系统需要为现有的对象变量创建一个临时副本时使用的,就算你不写,系统也会自动生成一个这样的函数。那么这个函数到底需要不需要我们自己来写呢?对于成员变量不含有指针的类来说,这个函数你不写或许没什么问题,但是一旦成员变量里有指针而且你又利用这个指针进行了内存分配的操作,那么如果不写拷贝构造函数,将会很危险。因为系统默认的拷贝构造函数使用了位拷贝的方式,这样的话,将使临时生成的对象变量的副本和蓝本变量公用分配到的动态内存空间,对副本的操作会直接影响到对蓝本的操作。并且由于指向同一块空间,使得该空间有可能被释放2次,导致出错。
拷贝构造函数最常被调用的就是对象作为函数参数时的情况。我们知道正常情况下函数的参数会被push,这个时候系统就会调用拷贝构造函数在堆栈里创建变量的副本以供函数作为参数使用。 那么即使是我们自己来写拷贝构造函数,那么面对被分配了动态空间的指针怎么办呢? 可以考虑也为副本变量开辟同样大的空间并且把蓝本文件里对应的空间中的内容Copy过来,这个过程可能会比较占资源,因为如果蓝本变量里分配了很大的空间,那么这时再分配一次就会比较浪费,而且Copy过来也是要花时间的。这种方式被称为“深拷贝” 而不对蓝本变量拥有的资源进行再分配和同步的拷贝方式称为“浅拷贝”。其实蓝本和副本公用动态空间有时可能会很有用,但是相对的不稳定因素也要多,怎么用看具体要求吧。
现在看上面的例子,看不懂的就先去看C++基础。
int main()
{
A Object1;  
A Object2(183,120,1);
 Object1 = Object2;
 B ObjectB;
 return 1;
}

首先生成A类对象 Object1 这时调用的是无参构造函数。随后的Object1调用了有参构造函数,并且这个构造函数使用初始化列表来初始化类A的常量成员。这点看前面的代码。
随后对Object1 进行了赋值操作,赋值运算符=已经在类里被重载过,我们来看看对应这个语句,编译器都干了什么,首先,运算符重载其实是函数的机制,因此这条语句可以理解为是函数的一个调用,那么既然是函数调用,参数就应该被push,也就是说要生成临时变量做参数,这个时候拷贝构造函数会先被调用,在堆栈里生成一个Object2的副本,随后调用赋值运算,再赋值运算=的实现里,最后系统会自动加上~A()的调用,以释放为了调用而生成的临时变量。
随后生成类B的对象 Object B ,这个类的成员里包含了类A的对象。那么,怎么初始化这个类A的对象呢?有2种办法,一种是在类B的构造函数里使用初始化列表,另种着是在类B的构造函数里使用类A的赋值运算,上面的代码里用了后者,大家注意,再类B的mMan = A(180,180,180);一句并不会调用A的拷贝构造函数,为什么呢?这不是和上面说的冲突了吗,其实不。A(180,180,180),这其实是个无名变量,再这里直接开辟在堆栈区成为临时变量,这个无名变量的开辟就是为了完成赋值运算,想当于就是生成了函数调用的参数,在赋值运算完成后会被系统调用~A()以销毁。因此这里并不调用拷贝函数去多此一举的再生成副本。
如果是采用初始化列表来初始化B类里的A对象,则在B的构造函数里就会直接调用A的对应构造函数完成初始化,可以看到,这比使用A的赋植运算符的效率要高。
其实无论你是否在B类里申明包含A类的对象,只要B继承了A,那么B里就会包含一个以A类的类名为名字的A类对象,这里就是变量A ,我们可以直接在初始化列表里对A进行初始化,写成这样就可以:
B::B():A(180,180,180)
但是如果这时你想利用A的赋值运算符或者其他的类A的函数进行初始化,则编译器会给你语法错误,这是当然的,因为你的类B里并没有任何关于类A对象的声明。这个对象的存在只是因为继承关系的缘故,否则类B怎么体现继承自类A的属性呢?
有兴趣的编译下这段程序看看构造函数和析购函数的执行顺序吧。 :)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++ object class 编译器