构造函数与析构函数一
- 构造函数是特殊的成员函数。
- 创建类类型的新对象,系统自动会调用构造函数。
- 构造函数是为了保证对象的每个数据成员都被正确初始化。
- 函数名和类名完全相同。
- 不能定义构造函数的类型(返回类型),也不能使用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; }
编译运行:
如预其一样,调用了默认构造函数,其中构造函数是自动调用的,
- 不带参数的构造函数,上面已经说明了。
- 如果程序中未声明,则系统自动产生出一个默认构造函数。
这时将我们声明的默认构造函数去掉:
这时编译运行:
这是由于没有对num成员变量进行人为初始化,所以它的值就是不确定的。
如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的默认的构造函数,而如果声明了带参数的构造函数,系统则不会为我们提供不带参数的默认构造函数了,如下:
而这时如果还是调用默认构造函数,肯定会出错:
这时调用构造时需要传递一个参数才行:
编译运行:
跟方法重载类似,很容易理解:
编译运行:
编译运行:
- 函数名和类名相似(前面多了一个字符“~”)。
- 没有返回类型。
- 没有参数。
- 析构函数不能被重载。
-
如果没有定义析构函数,编译器会自动生成一个默认析构函数,其格式如下:
类名::~默认析构函数名( )
{
} - 默认析构函数是一个空函数。
编译运行看构造与析构顺序:
从中可以发现,构构顺序是跟创建顺序相反的。
编译运行:
从运行结果来看,确实全局对象的构造是先于main函数的。
编译运行:
从中是初始化了两个对象,调用了两次构造。
需要注意上面的两种形式代表的不同含义,编译运行:
当调用delete运算符时,不但释放了内存,还调用了析构函数,具体如上面的输出结果既可以看出来。
【一般很少这样做,只有在特珠的场合才这样做,之后遇到会来使用它】
编译运行:
上节中实现过一个时钟类,这里新建一个工程,将上次的时钟类导进来,基于这个来进行新语法的说明,先看一下之前编写的代码:
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; }
编译运行其结果是:
首先第一步改造一下原有的代码,将数据成员的初始化放到构造函数中:
接下来正式开始学习转换构造函数,先简单认识一下它:
- 单个参数的构造函数
- 将其它类型转换为类类型
- 类的构造函数只有一个参数是非常危险的,因为编译器可以使用这种构造函数把参数的类型隐式转换为类类型【有办法限止这种转换,下面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; }
编译运行看是否这时创建有两个对象了:
为了更清楚的看到临时对象的生成,下面这样做:
- 在初始化语句中的等号不是运算符。编译器对这种表示方法有特殊的解释
- 赋值
-
Test& Test::operator=(const Test& other);
关于运算符的重载,之后会仔细学习,这里先为了说明问题简单使用一下,上面是运算符重载的形式,下面重载=号运算符:
编译运行:
如果再赋值一个对象:
运行结果如下:
另外对于自己重写的等号运算符可以写得更加好一些,修改如下:
- 只提供给类的构造函数使用的关键字。
- 编译器不会把声明为explicit的构造函数用于隐式转换,它只能在程序代码中显示创建对象