一、构造函数初始化列表

推荐在构造函数初始化列表中进行初始化
构造函数的执行分为两个阶段

初始化段

普通计算段

(一)、对象成员及其初始化

 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 成员和引用成员必须在初始化列表中初始化。另外,可以使用定义枚举类型来得到类作用域共有的常量。

二、拷贝构造函数

(一)、拷贝构造函数

功能:使用一个已经存在的对象来初始化一个新的同一类型的对象
声明:只有一个参数并且参数为该类对象的引用 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_(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;
}

 C++ Code 
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++编程规范

初始化列表(const和引用成员)、拷贝构造函数的更多相关文章

  1. C++中构造函数的初始化列表(const、引用&变量初始化)

    1. 构造函数执行分为两个阶段: a.初始化阶段(初始化) 初始化阶段具体指的是用构造函数初始化列表方式来初始化类中的数据成员. ClassXX:val(a),key(b){}; b.普通计算阶段(赋 ...

  2. 从零开始学C++之构造函数与析构函数(二):初始化列表(const和引用成员)、拷贝构造函数

    一.构造函数初始化列表 推荐在构造函数初始化列表中进行初始化 构造函数的执行分为两个阶段 初始化段 普通计算段 (一).对象成员及其初始化  C++ Code  1 2 3 4 5 6 7 8 9 1 ...

  3. 个人学习记录-Cpp基础-成员初始化列表

    Translator     Translator     参考链接: https://blog.csdn.net/XIONGXING_xx/article/details/115553291http ...

  4. C++定义构造函数必须使用初始化列表的场合

    明其理,而知其然也. 先给理论.1. 初始化 != 赋值. a.初始化代表为变量分配内存. 变量在其定义处被编译器初始化(编译时). 在函数中, 函数参数初始化发生在函数调用时(运行时). b.赋值代 ...

  5. C++的成员初始化列表和构造函数体(以前未知)

    成员的初始化列表和构造函数在对成员指定初值方面是不一样的.成员初始化列表是对成员初始化,而构造函数,是对成员赋值 成员初始化列表使用初始化的方式来为数据成员指定初值, 而构造函数的函数体是通过赋值的方 ...

  6. C++ 类 & 对象-类成员函数-类访问修饰符-C++ 友元函数-构造函数 & 析构函数-C++ 拷贝构造函数

    C++ 类成员函数 成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义. 需要强调一点,在 :: 运算符之前必须使用类名.调用成员函数是在对象上使用点运算符(.),这样它就能操作与 ...

  7. 拷贝构造函数第一个参数最好使用const

    拷贝构造函数的第一个参数要求是自身类型的引用,但是没有一定要求具有底层const属性即对常量的引用,但是使用时最好加上const,原因是我们可能在某些"不知道"的情况下对常量对象调 ...

  8. C++对象模型(四):class成员初始化列表(Member Initialization List)

    本文是Inside C++ Object Model Chapter 2 部分的读书笔记. 编译器如何处理初始化成员列表的. 下列情况中,必须要使用member initialization list ...

  9. 【c++】必须在类初始化列表中初始化的几种情况

    转自:http://www.cnblogs.com/kaituorensheng/p/3477630.html 1. 类成员为const类型 2. 类成员为引用类型 #include <iost ...

随机推荐

  1. MONO,原来你是水中月

    什么是MONO? MONO项目是由Ximian发起的,由Miguel de lcaza领导的,一个致力于开创.NET在Linux上使用的开源工程.它包含了一个C#语言的编译器,一个CLR的运行时,和一 ...

  2. SpringMVC 理论与有用技术(一) 简单、有用、易懂的几个实例

    SpringMVC先来看一下百度百科中的定义; Spring MVC属于SpringFrameWork的兴许产品,已经融合在Spring Web Flow里面.Spring 框架提供了构建 Web 应 ...

  3. SQL中关于where后面不能放聚合函数(如sum等)的解决办法

    我们在编写较为复杂的SQL语句的时候,常常会遇到需要将sum()放到where后面作为条件查询,事实证明这样是无法执行的,执行会报[此处不允许使用分组函数]异常. 那么如何解决呢,使用HAVING关键 ...

  4. 仿LOL项目开发第六天

    仿LOL项目开发第六天 by草帽 OK,因为更新模块已经处理好了,接着开始登陆的编写.那么我们就需要状态机的管理. 所谓状态机就是在哪个状态执行那个状态的代码逻辑: 那么我们开始编写GameState ...

  5. 数学图形(1.34) peut aussi曲线

    这是一种左右对称的类圆形曲线 #http://www.mathcurve.com/courbes2d/lissajous/lissajous2.shtml vertices = t = to (*PI ...

  6. Android系统信息获取

    在Android中可以通过android.os.Build这个类和System.getProperty(“xxx”);来获取设备信息,下面列举的常见设备信息摘自Android群英传 Build.BOA ...

  7. 附1 consul常用命令+常用选项

    之后每用到一个command或options,都会记录在这里. 常用命令command: agent 作用:运行一个consul agent join 作用:将agent加入到consul clust ...

  8. 修改Nginx与Apache配置参数解决http状态码:413上传文件大小限制问题

    一.修改Nginx上传文件大小限制 我们使用ngnix做web server的时候,nginx对上传文件的大小有限制,默认是1M. 当超过大小的时候会报413(too large)错误.这个时候我们要 ...

  9. Java GC、新生代、老年代

    堆内存 Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象.在 Java 中,堆被划分成两个不同的区域:新生代 ( Young ).老年代 ( Old ).新生代 ( ...

  10. Deep Learning论文笔记之(一)K-means特征学习

    Deep Learning论文笔记之(一)K-means特征学习 zouxy09@qq.com http://blog.csdn.net/zouxy09          自己平时看了一些论文,但老感 ...