从零开始学C++之构造函数与析构函数(一):构造函数、析构函数、赋值与初始化、explicit关键字
2013-06-25 20:28
441 查看
一、构造函数、默认构造函数
(1)、构造函数
构造函数是特殊的成员函数
创建类类型的新对象,系统自动会调用构造函数
构造函数是为了保证对象的每个数据成员都被正确初始化
函数名和类名完全相同
不能定义构造函数的类型(返回类型),也不能使用void
通常情况下构造函数应声明为公有函数,一般被隐式地调用。
构造函数被声明为私有有特殊的用途,比如单例模式,以后详谈。
构造函数可以有任意类型和任意个数的参数,一个类可以有多个构造函数(重载)
(2)、默认构造函数
不带参数的构造函数
如果程序中未声明,则系统自动产生出一个默认构造函数,是空函数
C++ Code
C++ Code
C++ Code
用上面的程序测试,输出为:
可以看到构造函数是被自动调用的,且构造函数可以被重载调用;栈上的对象生存期到了会自动调用析构函数;而new operator 做了两件事,一个是创建了对象内存,一个是调用构造函数;堆上的内存需要delete 释放,做了两件事,一是调用析构函数,二是释放内存。
还有一个注意点,全局对象的构造先于main函数执行,如下:
C++ Code
在return 0 时全局变量的生存期也到了,故也会自动调用析构函数。
二、析构函数
函数名和类名相似(前面多了一个字符“~”)
没有返回类型
没有参数
析构函数不能被重载
如果没有定义析构函数,编译器会自动生成一个默认析构函数,其格式如下:
类名::~默认析构函数名( )
{
}
默认析构函数是一个空函数
C++ Code
注意 Test t[2] = {10, 20}; 中10,20是当作参数传递给每个对象的构造函数的,如果没有对应的构造函数,比如只有2个参数的构造函数,那么编译是失败的。
实际上,构造函数和析构函数都是可以被显式调用的,只是很少这样做,可以参考这里。
三、转换构造函数
单个参数的构造函数不一定是转换构造函数
将其它类型转换为类类型
类的构造函数只有一个参数是非常危险的,因为编译器可以使用这种构造函数把参数的类型隐式转换为类类型
C++ Code
可以看到初始化了一个临时对象,传递参数20,然后调用赋值运算符operator=,接着释放临时对象,最后释放的对象是已经被更改过的t 。赋值运算符的格式为:Test& Test::operator=(const Test& other);事实上如果没有自己实现,编译器也会实现一个默认的赋值运算符,做的事情跟我们现在实现的函数一样。
四、赋值与初始化的区别
在初始化语句中的等号不是运算符。
C++ Code
第一条语句是初始化,后面是赋值操作,参照上面临时对象的创建销毁,赋值运算符的调用可以理解输出。
五、explicit 关键字
只提供给类的构造函数使用的关键字。
编译器不会把声明为explicit的构造函数用于隐式转换,它只能在程序代码中显示创建对象
假设在Test 类的构造函数Test(int num); 前面加上explicit 关键字,那么Test t = 10; 或者 t = 20; 这种语句都是编译不通过的,因为不允许隐式转换。
(1)、构造函数
构造函数是特殊的成员函数
创建类类型的新对象,系统自动会调用构造函数
构造函数是为了保证对象的每个数据成员都被正确初始化
函数名和类名完全相同
不能定义构造函数的类型(返回类型),也不能使用void
通常情况下构造函数应声明为公有函数,一般被隐式地调用。
构造函数被声明为私有有特殊的用途,比如单例模式,以后详谈。
构造函数可以有任意类型和任意个数的参数,一个类可以有多个构造函数(重载)
(2)、默认构造函数
不带参数的构造函数
如果程序中未声明,则系统自动产生出一个默认构造函数,是空函数
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #ifndef _TEST_H_ #define _TEST_H_ class Test { public: // 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的 // 默认的构造函数 Test(); Test( int num); void Display(); Test & operator=( const Test &other); ~Test(); private: int num_; }; #endif // _TEST_H_ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | #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) { cout << "Test::operator=" << endl; if ( this == &other) return * this; num_ = other.num_; return * this; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include "Test.h" int main( void) { Test t; t.Display(); Test t2( 10); t2.Display(); Test *t3 = new Test( 20); // new operator t3->Display(); delete t3; return 0; } |
可以看到构造函数是被自动调用的,且构造函数可以被重载调用;栈上的对象生存期到了会自动调用析构函数;而new operator 做了两件事,一个是创建了对象内存,一个是调用构造函数;堆上的内存需要delete 释放,做了两件事,一是调用析构函数,二是释放内存。
还有一个注意点,全局对象的构造先于main函数执行,如下:
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 | #include "Test.h" #include <iostream> using namespace std; Test t( 10); int main( void) { cout << "Entering main ..." << endl; cout << "Exiting main ..." << endl; return 0; } |
在return 0 时全局变量的生存期也到了,故也会自动调用析构函数。
二、析构函数
函数名和类名相似(前面多了一个字符“~”)
没有返回类型
没有参数
析构函数不能被重载
如果没有定义析构函数,编译器会自动生成一个默认析构函数,其格式如下:
类名::~默认析构函数名( )
{
}
默认析构函数是一个空函数
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include "Test.h" int main( void) { Test t[ 2] = { 10, 20}; Test *t2 = new Test( 2); delete t2; Test *t3 = new Test[ 2]; delete[] t3; return 0; } |
注意 Test t[2] = {10, 20}; 中10,20是当作参数传递给每个对象的构造函数的,如果没有对应的构造函数,比如只有2个参数的构造函数,那么编译是失败的。
实际上,构造函数和析构函数都是可以被显式调用的,只是很少这样做,可以参考这里。
三、转换构造函数
单个参数的构造函数不一定是转换构造函数
将其它类型转换为类类型
类的构造函数只有一个参数是非常危险的,因为编译器可以使用这种构造函数把参数的类型隐式转换为类类型
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include "Test.h" int main( void) { Test t( 10); // 带一个参数的构造函数,充当的是普通构造函数的功能 t = 20; // 将20这个整数赋值给t对象 // 1、调用转换构造函数将20这个整数转换成类类型 (生成一个临时对象) // 2、将临时对象赋值给t对象(调用的是=运算符) Test t2; return 0; } |
可以看到初始化了一个临时对象,传递参数20,然后调用赋值运算符operator=,接着释放临时对象,最后释放的对象是已经被更改过的t 。赋值运算符的格式为:Test& Test::operator=(const Test& other);事实上如果没有自己实现,编译器也会实现一个默认的赋值运算符,做的事情跟我们现在实现的函数一样。
四、赋值与初始化的区别
在初始化语句中的等号不是运算符。
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include "Test.h" int main( void) { Test t = 10; // 等价于Test t(10); 这里的=不是运算符,表示初始化。 t = 20; // 赋值操作 Test t2; t = t2; // 赋值操作 t.operator=(t2); return 0; } |
第一条语句是初始化,后面是赋值操作,参照上面临时对象的创建销毁,赋值运算符的调用可以理解输出。
五、explicit 关键字
只提供给类的构造函数使用的关键字。
编译器不会把声明为explicit的构造函数用于隐式转换,它只能在程序代码中显示创建对象
假设在Test 类的构造函数Test(int num); 前面加上explicit 关键字,那么Test t = 10; 或者 t = 20; 这种语句都是编译不通过的,因为不允许隐式转换。
相关文章推荐
- 从零开始学C++之构造函数与析构函数(一):构造函数、析构函数、赋值与初始化、explicit关键字
- 从零开始学C++之构造函数与析构函数(一):构造函数、析构函数、赋值与初始化、explicit关键字
- 从零开始学C++之构造函数与析构函数(一):构造函数、析构函数、赋值与初始化、explicit关键字
- 构造函数、析构函数、赋值与初始化、explicit关键字
- 八、构造函数和析构函数(二) 转换构造函数、赋值和初始化的区别、explicit
- 从零开始学C++之构造函数与析构函数(二):初始化列表(const和引用成员)、拷贝构造函数
- 从零开始学C++之构造函数与析构函数(二):初始化列表(const和引用成员)、拷贝构造函数
- 从零开始学C++之构造函数与析构函数(二):初始化列表(const和引用成员)、拷贝构造函数
- 第三章:构造函数,析构函数和赋值操作(Effective C++ Second Edition 读书笔记)
- C++构造函数初始化列表与赋值
- C++的const和引用只能在初始化列表里初始化而不能在构造函数体内赋值初始化
- C++三种构造函数以及explicit和friend关键字
- (总结)C++的构造函数(初始化列表,缺省/复制和带默认参数的构造函数)和析构函数详解
- C++在单继承、多继承、虚继承时,构造函数、复制构造函数、赋值操作符、析构函数的执行顺序和执行内容 【转】 参考度4.6星
- C++在单继承、多继承、虚继承时,构造函数、复制构造函数、赋值操作符、析构函数的执行顺序和执行内容
- C/C++ 通过初始化列表和构造函数内赋值初始化成员变量的区别
- C++中函数调用中构造函数、析构函数、赋值过程
- C/C++ 通过初始化列表和构造函数内赋值初始化成员变量的区别
- 深入C++中构造函数、拷贝构造函数、赋值操作符、析构函数的调用过程总结
- C/C++ 通过初始化列表和构造函数内赋值初始化成员变量的区别