【C++】构造函数、析构函数、拷贝构造函数、赋值函数

当声明一个类A时:

class A{
};
C++编译器会自动产生四个默认的函数,实际上类A与下面的等价:

class A{

public:

    A();                                         //默认的构造函数
    A(const A& a);                      //默认的拷贝构造函数(复制构造函数)
    ~A();                                     //默认的析构函数
    A & operate=(const A &a); //默认的赋值函数
};
接下来便是对这四个函数进行详细整理的笔记和验证程序

===========摘要===========
= 四种函数的基本概念
= 拷贝构造函数和赋值函数的区别
= 拷贝构造函数的浅拷贝和深拷贝
= 构造函数和析构函数的调用顺序
========================

======基本概念======
【构造函数】
(1).构造函数是一种特殊成员函数,用来处理对象的初始化,当创建对象时,系统自动调用构造函数。
(2).构造函数名与类名相同,一个类可以拥有多个构造函数(重载),构造函数可以有任意类型的参数,但不能具有返回类型。
(3).无参构造函数是默认构造函数,一个类只能有一个默认构造函数,如果用户未定义构造函数,则系统会自动提供一个默认构造函数,其函数体是空的,没有初始化作用。
【析构函数】
(1).析构函数名字为符号“~”加类名,析构函数没有参数和返回值。
(2).一个类中只能定义一个析构函数,因此析构函数不能重载。
(3).析构函数的作用不是删除对象,而是在撤销对象占用内存之前完成一些清理工作,也可以用来执行用户希望最后一次使用对象后执行的操作。
(4).如果用户没有定义析构函数,则编译系统会自动生成一个空的析构函数。
(4).在以下情况下,析构函数会被自动调用:
   1.如果一个对象被定义在一个函数体内,则当这个函数结束时,该对象的析构函数被自动调用。
2.静态(static)局部对象和全局对象在函数结束并不释放,在main函数或者调用exit函数时会调用其析构函数。
   4.若使用new运算符动态创建一个对象,当使用delete运算符释放它时,delete将会自动调用析构函数。用new创建的对象,必须用delete销毁。
注:delete释放new产生的空间,而delete [] 释放new []产生的数组空间。
【拷贝构造函数】也叫复制构造函数
1. 只有一个参数且参数为自身类的引用的构造函数,称为拷贝构造函数。拷贝构造函数的功能是用一个已有对象初始化一个正在建立的同类对象。
2. 拷贝构造函数的特点如下:
(1)该函数名与类同名,因为它也是一种构造函数,并且该函数也不被指定返回类型;
(2)该函数只有一个参数,并且是对某个对象的引用,一般加const声明;
(3)每个类都必须有一个拷贝构造函数,可以有多个拷贝构造函数,例如参数为const和非const声明的两个拷贝构造函数;
(4)如果程序员没有显式地定义一个拷贝构造函数,那么,C++编译器会自动生成一个缺省的拷贝构造函数.
(5)拷贝构造函数的目的是建立一个新的对象实体,所以,一定要保有证新创建的对象有着独立的内存空间,而不是与先前的对象共用。在定义一些类时,有时需要(而且强立推荐)显式地定义拷贝构造函数。
3.拷贝构造函数主要在如下三种情况中起初始化作用。
(1)声明语句中用一个对象初始化另一个对象(一定是在”声明并初始化对象”时被调用);例如TPoint P2(P1)表示由对象P1初始化P2时,需要调用拷贝构造函数。
(2)将一个对象作为参数按值调用方式(而不是指针)传递给另一个对象时生成对象副本(即 拷贝构造函数在”对象作为函数参数”时被调用)。当对象作为函数实参传递给函数形参时,如p=f(N),在调用f()函数时,对象N是实参,要用它来初始化被调用函数的形参,这时需要调用拷贝构造函数。
(3)生成一个临时的对象作为函数的返回结果。但对象作为函数返回值时,如return R时,系统将用对象R来初始化一个匿名对象,这时需要调用拷贝构造函数。
4.拷贝构造函数的执行:
(1)用已有对象初始化创建对象,将显式调用拷贝构造函数。 
(2)当对象作函数参数时,因为要用实参初始化形参,将隐式调用拷贝构造函数。
(3)函数返回值为类类型时,也会隐式调用拷贝构造函数。

【赋值函数】
1. 赋值操作符则给对象一个新的值,既然是新的值,说明这个对象原来就有值,这也说明,赋值函数只能被已经存在了的对象调用。
2. 如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”的方式自动生成缺省的函数。倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误(两个对象中的指针变量指向同一片内存,销毁对象时可能会对这个指针变量进行两次delete)。

======拷贝构造函数和赋值函数的区别======
(1).拷贝构造函数是在对象被创建时调用,而赋值函数要被已经存在的对象调用。
(2).赋值函数除了完成拷贝构造函数所完成的复制动态申请的内存数据之外,还可以对其进行重载,释放了原本自己申请的内存空间,避免内存泄漏。

======构造函数的浅拷贝和深拷贝======
所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝。大多情况下“浅拷贝”已经能很好地工作了,但是一旦对象存在了动态成员(例如指针变量),就会留下隐患。在销毁对象时,两个对象的析构函数将对同一个内存空间释放两次,这就是错误出现的原因。我们需要的不是两个p有相同的值,而是两个p指向的空间有相同的值,解决办法就是使用“深拷贝”。
在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间
例如:

class Rect  
{  
public:  
    Rect()      // 构造函数,p指向堆中分配的一空间  
    {  
        p = new int(100);  
    }  
    Rect(const Rect& r)  //拷贝构造函数
    {  
        width = r.width;  
        height = r.height;  
        p = new int;    // 为新对象重新动态分配空间  
        *p = *(r.p);  
    }  
    ~Rect()     // 析构函数,释放动态分配的空间  
    {  
        if(p != NULL)  
        {  
            delete p;  
        }  
    }  
private:  
    int width;  
    int height;  
    int *p;     // 一指针成员  
};

======构造函数和析构函数的调用顺序======
创建一个对象时,如果该对象有父类,则依次调用上一层父类的构造函数,否则直接调用自身的构造函数。析构函数与构造函数调用顺序相反,最先构造的后析构,后构造的先析构。
下面是测试例子:

#include<iostream>
using namespace std;
class A{
public:
    A()
    {
        cout << "调用A的构造函数" << endl;
    }
    ~A()
    {
        cout << "调用A的析构函数" << endl;
    }
 };
class B :public A
{
public:
    B()
    {
        cout << "调用A的子类B的构造函数" << endl;
    }
    ~B()
    {
        cout << "调用A的子类B的析构函数" << endl;
    }
};
class C :public B{
public:
    C()
    {
        cout << "调用B的子类C的构造函数" << endl;
    }
    ~C()
    {
        cout << "调用B的子类C的析构函数" << endl;
    }
};
int main()
{
    C *p=new C();
    delete p;
    system("pause");
    return 0;
}

输出结果为:
调用A的构造函数
调用A的子类B的构造函数
调用B的子类C的构造函数
调用B的子类C的析构函数
调用A的子类B的析构函数

调用A的析构函数

 

 

参考资料:
《C++Primer(第四版)》
http://blog.csdn.net/liranke/article/details/4710991

http://blog.csdn.net/lwbeyond/article/details/6202256

 

【2016-04-04 12:42:32】

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注