一、构造函数初始化列表
推荐在构造函数初始化列表中进行初始化
构造函数的执行分为两个阶段
初始化段
普通计算段
(一)、对象成员及其初始化
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 =
,
int obj2 =
) : obj2_(obj2), obj1_(obj1)
{
cout <<
"Container ..." << endl;
}
~Container()
{
cout <<
"~Container ..." << endl;
}
private:
Object obj1_;
Object obj2_;
};
int main(
void)
{
Container c(
,
);
return
;
}
|
从输出可以看出几点,一是构造对象之前,必须先构造对象的成员;二是对象成员构造的顺序与定义时的顺序有关,跟初始化列表顺序无关;三是构造的顺序和析构的顺序相反;四是如果对象成员对应的类没有默认构造函数,那对象成员也只能在初始化列表进行初始化。
(二)、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 =
,
TYPE_B =
};
public:
Object(
int num =
) : 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(
);
Object obj2(
);
obj1.DisplayKNum();
obj2.DisplayKNum();
cout << obj1.TYPE_A << endl;
cout << obj2.TYPE_A << endl;
cout << Object::TYPE_A << endl;
return
;
}
|
因为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_
|
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
|
|
#include
"Test.h"
#include <iostream>
using
namespace std;
// 不带参数的构造函数称为默认构造函数 Test::Test() : num_(
)
{
//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;
}
|
C++ Code
|
|
#include
"Test.h"
int main(
void)
{
Test t(
);
//Test t2(t); // 调用拷贝构造函数 Test t2 = t;
// 等价于Test t2(t);
return
;
}
|
即调用了拷贝构造函数,destroy 的两个分别是t 和 t2。
(二)、拷贝构造函数调用的几种情况
当函数的形参是类的对象,调用函数时,进行形参与实参结合时使用。这时要在内存新建立一个局部对象,并把实参拷贝到新的对象中。理所当然也调用拷贝构造函数。
当函数的返回值是类对象,函数执行完成返回调用者时使用。理由也是要建立一个临时对象中,再返回调用者。为什么不直接用要返回的局部对象呢?因为局部对象在离开建立它的函数时就消亡了,不可能在返回调用函数后继续生存,所以在处理这种情况时,编译系统会在调用函数的表达式中创建一个无名临时对象,该临时对象的生存周期只在函数调用处的表达式中。所谓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(
);
TestFun(t);
cout <<
"........" << endl;
return
;
}
|
即在传参的时候调用了拷贝构造函数,函数返回时TestFun 的形参t 1生存期到了,在分割线输出之前销毁t1,最后destroy 的是 t。
将TestFun(t); 换成 TestFun2(t);
参数为引用,即没有调用拷贝构造函数。
将TestFun(t); 换成 t = TestFun3(t);
函数返回时会调用拷贝构造函数,接着调用赋值运算符,释放临时对象,最后释放t。如果没有用t 接收,则临时对象也会马上释放。
将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 是引用故也不会调用拷贝构造函数。