C++——构造函数、析构函数以及复制构造函数

一、构造函数

在程序执行的过程中,当遇到与对声明语句时,程序会向操作系统申请一定的内存空间用于存放新建的对象。但是与普通变量相比,类的对象特别复杂,编译器不知如何产生代码去初始化对象,这便引出了构造函数。

1.1 构造函数是什么?

C++中,构造函数是一种特殊的成员函数,在每次创建一个类的时候编译器都会默认调用构造函数进行初始化。

1.2 为什么要有构造函数?

构造函数的作用就是在对象被创建的时候,利用特定的值构造对象,将对象初始化为一个特定的状态。

1.3 如何使用构造函数?

要学会如何使用构造函数,首先需要了解构造函数的一些特殊性质:构造函数的函数名与类名相同,而且没有返回值;构造函数通常被声明为公有函数。

Notes:只要类中有了构造函数,编译器就会在创建新对象的地方自动插入对构造函数调用的代码。所以构造函数在对象被创建的时候将会被自动调用。

下面分别介绍常见的几种构造函数:

  1. 默认构造函数:
class Clock
{
    
    
public:
    Clock()   //编译器自动生成的隐含的默认构造函数;
    {
    
    
        
    }
    ....
};

默认构造函数调用时无须提供参数。如果类中没有写构造函数,那么编译器会自动生成一个隐含的默认构造参数,该构造函数的参数列表和函数体都为空,这个构造函数不做任何事情。

Notes: 无参数的构造函数与全缺省的构造函数都称为默认构造函数。

Q:既然默认构造函数不做任何事情,那么为什么还要生成这个构造函数?

答:因为在建立对象时自动调用构造函数时C++必然要做的事情。上述例子中,默认构造函数什么都没有做,但是有些函数体为空的构造函数并非什么都不做,因为它还要负责基类的构造和成员对象的构造。

  1. 有参数的构造函数和无参数的构造函数
class Clock
{
    
    
public:
    Clock(int NewH,int NewM,int NewS);  //有参数的构造函数
    Clock()   //无参数的构造函数
    {
    
    
        hour = 0;
        minute = 0;
        second = 0;
    }
    void SetTime(int NewH,int NewM,int NewS);
    void ShowTime();

private:
    int hour,minute,second;
};

int main()
{
    
    
    Clock(0,0,0); //调用有参数的构造函数
    Clock my_clock;  //调用无参数的构造函数
    return 0;
}

上述例子中出现了两种重载的构造函数的形式:有参数的和无参数的(即默认构造函数)

1.4 构造函数的实现


Clock::Clock(int NewH, int NewM, int NewS) 
{
    
    
    hour = NewH;
    minute = NewM;
    second = NewS;
}

二、复制构造函数

生成一个对象的副本有两种途径,第一种途径是建立一个新的对象,然后将原始对象的数据成员取出来,赋值给新对象。这样做显然太繁琐了,所以为了使得一个类具有自行复制本类对象的能力,复制构造函数被引出。

2.1 什么是复制构造函数?

复制构造函数是一种特殊的构造函数,其具有一般构造函数的所有特性,其形参是本类对象的引用。

2.2 为什么要有复制构造函数?

复制构造函数的作用是使得一个已经存在的对象(由复制构造函数的参数指定),去初始化一个同类的一个新对象。如果程序没有定义类的复制构造函数,系统就会在必要时自动生成一个隐藏的复制构造函数。这个隐藏的复制构造函数的功能是,把初始值对象的每个数据成员复制到新建立的对象中去。这样得到的对象和原本的对象具有相同的数据成员和属性。

2.3 复制构造函数的功能

在说明复制构造函数的功能之前,我们先看一下声明和实现复制构造函数的一般方法:

class 类名
{
    
    
public:
    类名(形参); //构造函数
    类名(类名& 对象名); //复制构造函数
    ...
};

类名::类名(类名 &对象名) //复制构造函数的实现
{
    
    
    //函数体
}

前面我们知道,普通的构造函数在对象被创建的时候调用,而复制构造函数在下面3种情况下均会被调用:

例:

class Point
{
    
    
public:
    Point(int x = 0,int y = 0)
    {
    
    
        _x = x;
        _y = y;
    }
    Point(Point& p); //复制构造函数
    int GetX()
    {
    
    
        return _x;
    }
    int GetY()
    {
    
    
        return _y;
    }

private:
    int _x,_y;
};
  1. 当用类的一个对象去初始化另一个对象时:
int main()
{
    
    
    Point a(1,2);
    Point b(a); //用对象a初始化对象b时,复制构造函数被调用
    Point c = a; //用对象a初始化对象c时,复制构造函数被调用
    return 0;
}

Notes:上面对b和c的初始化都能够调用复制构造函数,两种写法是等价的,执行的操作完全相同。

  1. 如果函数的形参是类的对象,调用函数时,进行形参和实参结合时:
void Fun(Point p)
{
    
    
    //函数体
}

int main()
{
    
    
    Point a(1,2);
    Fun(a); //函数的形参为类的对象,当调用对象时,复制构造函数被调用.
    return  0;
}

Notes:只有把对象用值传递的时候,才会调用复制构造函数。如果传递的是引用,则不会调用复制构造函数。所以这也是传递引用会比传值的效率高的原因。

  1. 如果函数的返回值是类的对象,函数执行完成返回调用者时:
Point Fun()
{
    
    
    Point a(1,2);
    return a;  //函数Fun的返回值是类的对象,返回函数值的时候,调用复制构造函数
}

int main()
{
    
    
    Point b;
    b = Fun();
    return 0;
}

Q:为什么在这种情况下,返回函数值的时候会调用复制构造函数?

答:函数Fun()将a返回给了主函数,但是我们都知道a是Fun()的局部变量,当Fun()函数的生命周期结束的时候,a也就消亡了,不可能在返回主函数后继续生存。所以在这种情况下编译器会在主函数中创建一个无名的临时对象,该临时对象的生命周期仅仅在b = Fun()中。当执行语句return a时,实际上是调用复制构造函数将a的值复制到无名临时对象中。当函数Fun()运行结束时,对象a消失,但是临时对象会存在于b = Fun()中。当计算这个表达式后,临时对象也就完成了它的工作。

三、析构函数

我们刚讨论了当一个局部变量随着它的函数生命周期结束的时候,函数中的对象也会消失,那么在对象,要消失的时候(比如构造对象的时候,在函数中用malloc动态申请了空间)谁来做这些所谓的“善后”工作呢?这样我们就引出了析构函数。

什么是析构函数?

与构造函数一样,析构函数通常也是类的一个公有成员函数,它的名称是由类名前面加一个"~"构成,没有返回值。析构函数与构造函数不同的是,析构函数不接受任何参数!(参数可以是虚函数),如果我们不自行定义析构函数,则编译器同样会自动生成一个隐藏的析构函数,它的函数体为空。

下面我们看一下析构函数的声明:

class Clock
{
    
    
public:
    Clock(int NewH,int NewM,int NewS);  //有参数的构造函数
    Clock()   //无参数的构造函数
    {
    
    
        hour = 0;
        minute = 0;
        second = 0;
    }
    void SetTime(int NewH,int NewM,int NewS);
    void ShowTime();
    ~Clock(); //析构函数

private:
    int hour,minute,second;
};

Notes:函数体为空的析构函数并不是什么都不做。

Tips:
类的构造顺序是按照语句的顺序进行构造
类的析构函数调用完全按照构造函数调用的相反顺序进行调用
1.全局对象先于局部对象进行构造
2.静态对象先于普通对象进行构造

猜你喜欢

转载自blog.csdn.net/xhuyang111/article/details/114587797