构造函数与析构函数一_技术

  • 构造函数是特殊的成员函数。
  • 创建类类型的新对象,系统自动会调用构造函数。
  • 构造函数是为了保证对象的每个数据成员都被正确初始化。
  • 函数名和类名完全相同。
  • 不能定义构造函数的类型(返回类型),也不能使用void。
  • 通常情况下构造函数应声明为公有函数,否则它不能像其他成员函数那样被显式地调用。
  • 构造函数被声明为私有有特殊的用途。【单例模式的应用】
  • 构造函数可以有任意类型和任意个数的参数,一个类可以有多个构造函数(重载)。

对于有编程经验的人来说上面的很容易理解,下面具体来看下构造函数在C++中的使用:

Test.h:

#ifndef _TEST_H_
#define _TEST_H_

class Test
{
public:
    Test();
private:
    int num_;
};
#endif // _TEST_H_

Test.cpp:

#include "Test.h"
#include <iostream>
using namespace std;

// 不带参数的构造函数称为默认构造函数
Test::Test()
{
    num_ = 0;
    cout<<"Initializing Default"<<endl;
}

下面来调查用测试下:

01.cpp:

#include "Test.h"

int main(void) {
    Test t;
    return 0;
}

编译运行:

构造函数与析构函数一_技术_02

如预其一样,调用了默认构造函数,其中构造函数是自动调用的,

构造函数与析构函数一_构造函数_03

  •  不带参数的构造函数,上面已经说明了。
  • 如果程序中未声明,则系统自动产生出一个默认构造函数。
    这时将我们声明的默认构造函数去掉:
    构造函数与析构函数一_构造函数_04
    构造函数与析构函数一_构造函数_05
    构造函数与析构函数一_默认构造函数_06
    这时编译运行:
    构造函数与析构函数一_#include_07
    这是由于没有对num成员变量进行人为初始化,所以它的值就是不确定的。
    如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的默认的构造函数,而如果声明了带参数的构造函数,系统则不会为我们提供不带参数的默认构造函数了,如下:
    构造函数与析构函数一_技术_08
    构造函数与析构函数一_#include_09
    而这时如果还是调用默认构造函数,肯定会出错:
    构造函数与析构函数一_默认构造函数_10
    构造函数与析构函数一_默认构造函数_11
    这时调用构造时需要传递一个参数才行:
    构造函数与析构函数一_默认构造函数_12
    编译运行:
    构造函数与析构函数一_#include_13

构造函数与析构函数一_#include_14

 跟方法重载类似,很容易理解:

构造函数与析构函数一_#include_15

构造函数与析构函数一_技术_16

构造函数与析构函数一_技术_17

编译运行:

构造函数与析构函数一_#include_18

构造函数与析构函数一_技术_19

 构造函数与析构函数一_技术_20

编译运行:

构造函数与析构函数一_默认构造函数_21

构造函数与析构函数一_构造函数_22

  •  函数名和类名相似(前面多了一个字符“~”)。
  • 没有返回类型。
  • 没有参数。
  • 析构函数不能被重载。
  • 如果没有定义析构函数,编译器会自动生成一个默认析构函数,其格式如下:

      类名::~默认析构函数名( )

      {

      }
  • 默认析构函数是一个空函数。

 构造函数与析构函数一_默认构造函数_23

构造函数与析构函数一_#include_24

构造函数与析构函数一_构造函数_25

编译运行看构造与析构顺序:

构造函数与析构函数一_#include_26

从中可以发现,构构顺序是跟创建顺序相反的。

构造函数与析构函数一_#include_27

 构造函数与析构函数一_#include_28

编译运行:

构造函数与析构函数一_构造函数_29

从运行结果来看,确实全局对象的构造是先于main函数的。 

构造函数与析构函数一_技术_30

 构造函数与析构函数一_#include_31

编译运行:

构造函数与析构函数一_技术_32

从中是初始化了两个对象,调用了两次构造。 

构造函数与析构函数一_默认构造函数_33

需要注意上面的两种形式代表的不同含义,编译运行:

构造函数与析构函数一_#include_34

构造函数与析构函数一_构造函数_35

 当调用delete运算符时,不但释放了内存,还调用了析构函数,具体如上面的输出结果既可以看出来。

构造函数与析构函数一_#include_36【一般很少这样做,只有在特珠的场合才这样做,之后遇到会来使用它】

构造函数与析构函数一_#include_37

编译运行:

构造函数与析构函数一_默认构造函数_38

构造函数与析构函数一_#include_39

上节中实现过一个时钟类,这里新建一个工程,将上次的时钟类导进来,基于这个来进行新语法的说明,先看一下之前编写的代码:

Clock.h:

//#pragma once
#ifndef _CLOCK_H_
#define _CLOCK_H_

class Clock
{
public:
    void Display();
    void Init(int hour, int minute, int second);
    void Update();

    int GetHour();
    int GetMinute();
    int GetSecond();

    void SetHour(int hour);
    void SetMinute(int minute);
    void SetSecond(int second);
private:
    int hour_;
    int minute_;
    int second_;
};
#endif // _CLOCK_H_

Clock.cpp:

#include "Clock.h"
#include <iostream>
using namespace std;

void Clock::Display()
{
    cout<<hour_<<":"<<minute_<<":"<<second_<<endl;
}

void Clock::Init(int hour, int minute, int second)
{
    hour_ = hour;
    minute_ = minute;
    second_ = second;
}

void Clock::Update()
{
    second_++;
    if (second_ == 60)
    {
        minute_++;
        second_ = 0;
    }
    if (minute_ == 60)
    {
        hour_++;
        minute_ = 0;
    }
    if (hour_ == 24)
    {
        hour_ = 0;
    }
}

int Clock::GetHour()
{
    return hour_;
}

int Clock::GetMinute()
{
    return minute_;
}

int Clock::GetSecond()
{
    return second_;
}

void Clock::SetHour(int hour)
{
    hour_ = hour;
}

void Clock::SetMinute(int minute)
{
    minute_ = minute;
}

void Clock::SetSecond(int second)
{
    second_ = second;
}

其测试代码如下:

#include "Clock.h"

int main(void)
{
    Clock c;
    c.Init(10, 10, 10);
    c.Display();
    //c.second_ += 1;
    c.Update();
    c.Display();

    //c.hour_ = 11;
    c.SetHour(11);
    c.Display();

    return 0;
}

编译运行其结果是:

构造函数与析构函数一_#include_40

首先第一步改造一下原有的代码,将数据成员的初始化放到构造函数中:

构造函数与析构函数一_#include_41

构造函数与析构函数一_构造函数_42

构造函数与析构函数一_默认构造函数_43

构造函数与析构函数一_默认构造函数_44

接下来正式开始学习转换构造函数,先简单认识一下它:

  • 单个参数的构造函数
  • 将其它类型转换为类类型
  • 类的构造函数只有一个参数是非常危险的,因为编译器可以使用这种构造函数把参数的类型隐式转换为类类型【有办法限止这种转换,下面explicit会说明】

也就是说,带一个参数的构造函数有以下两个功能:

1、普通构造函数(只能进行初始化)

2、转换构造函数(除了初始化之后,还能进行类型转化)

下面用代码进行说明,还是延用上节中的Test类,如下:

Test.h:

#ifndef _TEST_H_
#define _TEST_H_

class Test
{
public:
    // 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的
    // 默认的构造函数
    Test();
    Test(int num);
    void Display();

    ~Test();
private:
    int num_;
};
#endif // _TEST_H_

Test.c:

#include "Test.h"
#include <iostream>
using namespace std;

// 不带参数的构造函数称为默认构造函数
Test::Test()
{
    num_ = 0;
    cout<<"Initializing Default"<<endl;
}

Test::Test(int num)
{
    num_ = num;
    cout<<"Initializing "<<num_<<endl;
}

Test::~Test()
{
    cout<<"Destroy "<<num_<<endl;
}

void Test::Display()
{
    cout<<"num="<<num_<<endl;
}

构造函数与析构函数一_#include_45

构造函数与析构函数一_#include_46

编译运行看是否这时创建有两个对象了:

构造函数与析构函数一_构造函数_47

为了更清楚的看到临时对象的生成,下面这样做:

构造函数与析构函数一_技术_48

构造函数与析构函数一_默认构造函数_49

 

构造函数与析构函数一_#include_50

  •  在初始化语句中的等号不是运算符。编译器对这种表示方法有特殊的解释
    构造函数与析构函数一_技术_51
  • 赋值
    构造函数与析构函数一_默认构造函数_52
  • Test& Test::operator=(const Test& other);
    关于运算符的重载,之后会仔细学习,这里先为了说明问题简单使用一下,上面是运算符重载的形式,下面重载=号运算符:
    构造函数与析构函数一_#include_53
    构造函数与析构函数一_#include_54
    编译运行:
    构造函数与析构函数一_构造函数_55
    如果再赋值一个对象:
    构造函数与析构函数一_技术_56
    运行结果如下:
    构造函数与析构函数一_技术_57
    另外对于自己重写的等号运算符可以写得更加好一些,修改如下:
    构造函数与析构函数一_默认构造函数_58

构造函数与析构函数一_#include_59

  •  只提供给类的构造函数使用的关键字。
  • 编译器不会把声明为explicit的构造函数用于隐式转换,它只能在程序代码中显示创建对象

构造函数与析构函数一_技术_60

构造函数与析构函数一_#include_61

构造函数与析构函数一_构造函数_62