从零开始学C++之构造函数与析构函数(二):初始化列表(const和引用成员)、拷贝构造函数
2013-06-25 21:50
627 查看
一、构造函数初始化列表
推荐在构造函数初始化列表中进行初始化
构造函数的执行分为两个阶段
初始化段
普通计算段
(一)、对象成员及其初始化
C++ Code
从输出可以看出几点,一是构造对象之前,必须先构造对象的成员;二是对象成员构造的顺序与定义时的顺序有关,跟初始化列表顺序无关;三是构造的顺序和析构的顺序相反;四是如果对象成员对应的类没有默认构造函数,那对象成员也只能在初始化列表进行初始化。再提一点,如果类是继承而来,基类没有默认构造函数的时候,基类的构造函数要在派生类构造函数初始化列表中调用。
(二)、const成员、引用成员的初始化
C++ Code
因为const 变量或者引用都得在定义的时候初始化,所以const 成员和引用成员必须在初始化列表中初始化。另外,可以使用定义枚举类型来得到类作用域共有的常量。
二、拷贝构造函数
(一)、拷贝构造函数
功能:使用一个已经存在的对象来初始化一个新的同一类型的对象
声明:只有一个参数并且参数为该类对象的引用 Test::Test(const Test &other)
;
如果类中没有定义拷贝构造函数,则系统自动生成一个缺省复制构造函数,作为该类的公有成员,所做的事情也是简单的成员复制
C++ Code
C++ Code
C++ Code
即调用了拷贝构造函数,destroy 的两个分别是t 和 t2。
(二)、拷贝构造函数调用的几种情况
当函数的形参是类的对象,调用函数时,进行形参与实参结合时使用。这时要在内存新建立一个局部对象,并把实参拷贝到新的对象中。理所当然也调
用拷贝构造函数。还有一点,为什么拷贝构造函数的参数需要是引用? 这是因为如果拷贝构造函数中的参数不是一个引用,即形如CClass(const
CClass c_class),那么就相当于采用了传值的方式(pass-by-value),而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函
数。
当函数的返回值是类对象,函数执行完成返回调用者时使用。也是要建立一个临时对象,再返回调用者。为什么不直接用要返回的局部对象呢?
因为局部对象在离开建立它的函数时就消亡了,不可能在返回调用函数后继续生存,所以在处理这种情况时,编译系统会在调用函数的表达式中创建一
个无名临时对象,该临时对象的生存周期只在函数调用处的表达式中。所谓return 对象,实际上是调用拷贝构造函数把该对象的值拷入临时对象。如果
返回的是变量,处理过程类似,只是不调用构造函数。
C++ Code
即在传参的时候调用了拷贝构造函数,函数返回时TestFun 的形参t 1生存期到了,在分割线输出之前销毁t1,最后destroy 的是 t。
将TestFun(t); 换成 TestFun2(t);
参数为引用,即没有调用拷贝构造函数。
将TestFun(t); 换成 t = TestFun3(t);
函数返回时会调用拷贝构造函数,接着调用赋值运算符,释放临时对象,最后释放t。如果没有用t 接收,不会调用operator= 而且临时对象也会马上释放。
将TestFun(t); 换成 Test t2 = TestFun3(t);
函数返回调用拷贝构造函数,但没有再次调用拷贝构造函数,而且没有释放临时对象,可以理解成临时对象改名为t2 了。
将TestFun(t); 换成 Test& t2 = TestFun3(t);
函数返回时调用拷贝构造函数,因为t2 引用着临时对象,故没有马上释放。
将TestFun(t); 换成 Test t2 = TestFun4(t);
函数传参和返回都没有调用拷贝构造函数,初始化t2 时会调用拷贝构造函数。
将TestFun(t); 换成 const Test& t2 = TestFun4(t);
函数传参和返回都没有调用构造函数,t2 是引用故也不会调用拷贝构造函数。
参考:
C++ primer 第四版
Effective C++ 3rd
C++编程规范
推荐在构造函数初始化列表中进行初始化
构造函数的执行分为两个阶段
初始化段
普通计算段
(一)、对象成员及其初始化
C++ Code
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 37 38 39 40 | #include <iostream> using namespace std; class Object { public: Object(int num) : num_(num) { cout << "Object " << num_ << " ..." << endl; } ~Object() { cout << "~Object " << num_ << " ..." << endl; } private: int num_; }; class Container { public: Container(int obj1 = 0, int obj2 = 0) : obj2_(obj2), obj1_(obj1) { cout << "Container ..." << endl; } ~Container() { cout << "~Container ..." << endl; } private: Object obj1_; Object obj2_; }; int main(void) { Container c(10, 20); return 0; } |
从输出可以看出几点,一是构造对象之前,必须先构造对象的成员;二是对象成员构造的顺序与定义时的顺序有关,跟初始化列表顺序无关;三是构造的顺序和析构的顺序相反;四是如果对象成员对应的类没有默认构造函数,那对象成员也只能在初始化列表进行初始化。再提一点,如果类是继承而来,基类没有默认构造函数的时候,基类的构造函数要在派生类构造函数初始化列表中调用。
(二)、const成员、引用成员的初始化
C++ Code
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 37 38 39 40 41 42 43 44 45 46 47 48 49 | #include <iostream> using namespace std; // const成员的初始化只能在构造函数初始化列表中进行 // 引用成员的初始化也只能在构造函数初始化列表中进行 // 对象成员(对象成员所对应的类没有默认构造函数)的初始化,也只能在构造函数初始化列表中进行 class Object { public: enum E_TYPE { TYPE_A = 100, TYPE_B = 200 }; public: Object(int num = 0) : num_(num), kNum_(num), refNum_(num_) { //kNum_ = 100; //refNum_ = num_; cout << "Object " << num_ << " ..." << endl; } ~Object() { cout << "~Object " << num_ << " ..." << endl; } void DisplayKNum() { cout << "kNum=" << kNum_ << endl; } private: int num_; const int kNum_; int &refNum_; }; int main(void) { Object obj1(10); Object obj2(20); obj1.DisplayKNum(); obj2.DisplayKNum(); cout << obj1.TYPE_A << endl; cout << obj2.TYPE_A << endl; cout << Object::TYPE_A << endl; return 0; } |
因为const 变量或者引用都得在定义的时候初始化,所以const 成员和引用成员必须在初始化列表中初始化。另外,可以使用定义枚举类型来得到类作用域共有的常量。
二、拷贝构造函数
(一)、拷贝构造函数
功能:使用一个已经存在的对象来初始化一个新的同一类型的对象
声明:只有一个参数并且参数为该类对象的引用 Test::Test(const Test &other)
;
如果类中没有定义拷贝构造函数,则系统自动生成一个缺省复制构造函数,作为该类的公有成员,所做的事情也是简单的成员复制
C++ Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #ifndef _TEST_H_ #define _TEST_H_ class Test { public: // 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的 // 默认的构造函数 Test(); explicit Test(int num); Test(const Test &other); 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 37 38 39 40 41 42 | #include "Test.h" #include <iostream> using namespace std; // 不带参数的构造函数称为默认构造函数 Test::Test() : num_(0) { //num_ = 0; cout << "Initializing Default" << endl; } Test::Test(int num) : num_(num) { //num_ = num; cout << "Initializing " << num_ << endl; } Test::Test(const Test &other) : num_(other.num_) { //num_ = other.num_; cout << "Initializing with other " << 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 | #include "Test.h" int main(void) { Test t(10); //Test t2(t); // 调用拷贝构造函数 Test t2 = t; // 等价于Test t2(t); return 0; } |
即调用了拷贝构造函数,destroy 的两个分别是t 和 t2。
(二)、拷贝构造函数调用的几种情况
当函数的形参是类的对象,调用函数时,进行形参与实参结合时使用。这时要在内存新建立一个局部对象,并把实参拷贝到新的对象中。理所当然也调
用拷贝构造函数。还有一点,为什么拷贝构造函数的参数需要是引用? 这是因为如果拷贝构造函数中的参数不是一个引用,即形如CClass(const
CClass c_class),那么就相当于采用了传值的方式(pass-by-value),而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函
数。
当函数的返回值是类对象,函数执行完成返回调用者时使用。也是要建立一个临时对象,再返回调用者。为什么不直接用要返回的局部对象呢?
因为局部对象在离开建立它的函数时就消亡了,不可能在返回调用函数后继续生存,所以在处理这种情况时,编译系统会在调用函数的表达式中创建一
个无名临时对象,该临时对象的生存周期只在函数调用处的表达式中。所谓return 对象,实际上是调用拷贝构造函数把该对象的值拷入临时对象。如果
返回的是变量,处理过程类似,只是不调用构造函数。
C++ Code
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 | #include "Test.h" #include <iostream> using namespace std; void TestFun(const Test t1) { } void TestFun2(const Test &t1) { } Test TestFun3(const Test &t1) { return t1; } const Test &TestFun4(const Test &t1) { //return const_cast<Test&>(t1); return t1; } int main(void) { Test t(10); TestFun(t); cout << "........" << endl; return 0; } |
即在传参的时候调用了拷贝构造函数,函数返回时TestFun 的形参t 1生存期到了,在分割线输出之前销毁t1,最后destroy 的是 t。
将TestFun(t); 换成 TestFun2(t);
参数为引用,即没有调用拷贝构造函数。
将TestFun(t); 换成 t = TestFun3(t);
函数返回时会调用拷贝构造函数,接着调用赋值运算符,释放临时对象,最后释放t。如果没有用t 接收,不会调用operator= 而且临时对象也会马上释放。
将TestFun(t); 换成 Test t2 = TestFun3(t);
函数返回调用拷贝构造函数,但没有再次调用拷贝构造函数,而且没有释放临时对象,可以理解成临时对象改名为t2 了。
将TestFun(t); 换成 Test& t2 = TestFun3(t);
函数返回时调用拷贝构造函数,因为t2 引用着临时对象,故没有马上释放。
将TestFun(t); 换成 Test t2 = TestFun4(t);
函数传参和返回都没有调用拷贝构造函数,初始化t2 时会调用拷贝构造函数。
将TestFun(t); 换成 const Test& t2 = TestFun4(t);
函数传参和返回都没有调用构造函数,t2 是引用故也不会调用拷贝构造函数。
参考:
C++ primer 第四版
Effective C++ 3rd
C++编程规范
相关文章推荐
- 从零开始学C++之构造函数与析构函数(二):初始化列表(const和引用成员)、拷贝构造函数
- 从零开始学C++之构造函数与析构函数(二):初始化列表(const和引用成员)、拷贝构造函数
- C++的6大成员函数,构造函数(初始化列表),析构函数,拷贝构造函数,运算符重载,const成员函数
- 从零开始学C++之构造函数与析构函数(二):初始化列表(const和引用成员)、拷贝构造函数
- 九、构造函数和析构函数(三) 初始化列表、对象成员初始化、const,引用成员初始化
- C++中的初始化列表、const修饰的成员、友元类和友元函数、内联函数、static成员、构造函数的优化
- const成员或者引用成员必须使用构造函数初始化列表的方式
- C++的const和引用只能在初始化列表里初始化而不能在构造函数体内赋值初始化
- C/C++ 通过初始化列表和构造函数内赋值初始化成员变量的区别
- c++ 捕获构造函数成员初始化列表产生的异常
- C/C++ 通过初始化列表和构造函数内赋值初始化成员变量的区别
- 从零开始学C++之构造函数与析构函数(一):构造函数、析构函数、赋值与初始化、explicit关键字
- C++中类的构造函数与析构函数(成员初始化列表)
- C++的静态成员函数,成员变量,构造函数,析构函数,拷贝构造函数
- C/C++基础问答(1):通过初始化列表和构造函数内赋值初始化成员变量的区别
- c++-成员初始化问题(static, const, 引用)
- C++中的拷贝构造函数和拷贝赋值操作符+const成员变量初始化(5)---《Effective C++》
- 【c++】构造函数初始化列表中成员初始化的次序性
- 从零开始学C++之构造函数与析构函数(一):构造函数、析构函数、赋值与初始化、explicit关键字
- 关于C++中构造函数初始化成员列表的总结