• C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数:缺省、单一、拷贝、移动、赋值)


    构造函数的概念

    在这里插入图片描述

    构造函数

    构造对象时调用的函数,名称与类名相同,没有返回值,可以包含多个版本(重载)

    #include <iostream>
    #include <vector>
    using namespace std;
    
    class Str
    {
    public:
        // 构造函数
        Str()
        {
            cout << "Constructor is called." << endl;
        }
    
        // 构造函数的重载
        Str(int input)
        {
            x = input;
            cout << x << endl;
        }
    private:
        int x;
    };
    
    int main()
    {
        Str m; // 调用构造函数
    
        Str m1(3);
    }
    
    • 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

    代理构造函数:
    代理构造函数先执行,然后继续执行原始构造函数

    #include <iostream>
    #include <vector>
    using namespace std;
    
    
    class Str
    {
    public:
        // 代理构造函数,让下面的构造函数代理
        Str() : Str(3)
        {
            cout << "here1" << endl; // 后执行
        }
    
        // 构造函数的重载
        Str(int input)
        {
            cout << "here2" << endl; // 先执行
            x = input;
        }
    
        void fun()
        {
            cout << x << endl;
        }
    private:
        int x;
    };
    
    
    int main()
    {
        Str m; // 调用构造函数
        m.fun();
    }
    
    • 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

    初始化列表

    没有使用初始化列表:

    #include <iostream>
    #include <vector>
    using namespace std;
    
    
    class Str
    {
    public:
        Str(const std::string& val)
        {
            cout << "Pre-assignment: " << x << endl; // 空字符, 缺省初始化了
            x = val; // 这里是赋值,不是定义
            cout << "Post-assignment: " << x << endl; // "abc"
        }
    
    private:
        std::string x;
    };
    
    
    int main()
    {
        Str m("abc"); 
    
    }
    
    • 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

    使用了初始化列表:

    #include <iostream>
    #include <vector>
    using namespace std;
    
    
    class Str
    {
    public:
        Str(const std::string& val) : x(val), y(1) // 初始化列表
        {
            cout << "Pre-assignment: " << x << endl; // 也是“abc”
            cout << "Post-assignment: " << x << endl; // "abc"
            cout << "Pre-assignment: " << y << endl; // 1
            cout << "Post-assignment: " << y << endl; // 1
        }
    
    private:
        std::string x;
        int y;
    };
    
    
    int main()
    {
        Str m("abc"); 
    
    }
    
    • 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

    通过引用进行初始化:

    #include <iostream>
    #include <vector>
    using namespace std;
    
    
    class Str
    {
    public:
        Str(const std::string& val, int& p_i) 
        : x(val)
        , y(1)
        , ref(p_i)
        {
            cout << "Pre-assignment: " << x << endl; // 也是“abc”
            cout << "Post-assignment: " << x << endl; // "abc"
            cout << "Pre-assignment: " << y << endl; // 1
            cout << "Post-assignment: " << y << endl; // 1
            ref = 100;
        }
    
    private:
        std::string x;
        int y;
        int& ref;
    };
    
    
    int main()
    {
        int val;
        Str m("abc", val); 
        cout << val << endl; // 100 通过引用进行初始化
    }
    
    • 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

    注意,元素的初始化顺序与其声明相关,与初始化列表顺序无关
    c++构造和销毁对象是按照一定顺序的,比如先构造了A,再构造了B。那么销毁的时候是先销毁B,再销毁A。
    在这里插入图片描述

    #include <iostream>
    #include <vector>
    using namespace std;
    
    
    class Str
    {
    public:
    	// 不管初始化列表顺序,而是按照声明顺序,c++不需要关注对象调用了哪个构造函数,而是关注声明顺序再决定销毁顺序。
        Str()
            : x("")
            , y(1)
        {
        }
    
    
        Str(const std::string& val) 
        : y(x.size()) // 与初始化列表顺序无关,与声明顺序有关
        , x(val)
        {
            cout << x << endl; 
            cout << y << endl; 
        }
    
    private:
        // 声明的顺序是x在y前面,在内存先开x的内存,再开y
        std::string x;
        size_t y;
    };
    
    
    int main()
    {
        Str m("abc"); 
    }
    
    • 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

    通常要求我们写代码的时候,初始化列表的顺序和声明顺序相同。
    不然让读代码的人,有疑惑。

    使用初始化列表覆盖类内成员初始化

    #include <iostream>
    #include <vector>
    using namespace std;
    
    
    class Str
    {
    public:
    
        Str() 
            : x(50)
            , y(100)
        {
            cout << x << endl;  // 50
            cout << y << endl;  // 100 会覆盖类内成员初始化
        }
    
    private:
        size_t x = 3;
        size_t y = 4;
    };
    
    int main()
    {
        Str m; 
    }
    
    • 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

    特殊的构造函数

    缺省构造函数

    不需要提供实际参数就可以调用的构造函数
    在这里插入图片描述
    在这里插入图片描述
    如果类中没有任何构造函数,在允许的条件下,编译器会合成一个缺省的构造函数。
    在这里插入图片描述
    如果提供了构造函数,编译器就不会自动合成默认构造函数。
    在这里插入图片描述
    调用缺省构造函数时,避免most vexing parse
    在这里插入图片描述
    用default来定义缺省构造函数
    在这里插入图片描述

    单一参数构造函数

    在这里插入图片描述
    缺省构造函数是包含0个参数的构造函数。
    单一参数的构造函数,就是包含1个参数的构造函数。

    #include <iostream>
    #include <vector>
    using namespace std;
    
    
    struct Str
    {   // explicit必须显式的初始化,不能隐式初始化
        explicit Str(int x)
        // Str(int x)
            : val(x)
        {}
    
    private:
        int val;
    };
    
    void fun(Str m)
    {
    
    }
    
    int main()
    {
        Str m(3);
        // 下面显式初始化式不行的
        // Str m1 = 3; // 也可以这么调用构造函数,把3通过单一构造函数转化成Str m1类型
        // fun(3); // 3转化成Str类型
    }
    
    • 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

    拷贝构造函数

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    如果未显示提供,那么编译器会合成一个,合成的版本会依次对每个数据成员调用拷贝构造。

    移动构造函数

    在这里插入图片描述
    接收一个当前类右值引用对象的构造函数。进一步提升性能。
    在这里插入图片描述

    int main()
    {
        std::string ori("abc");
        std::string newStr = ori; // string 的拷贝构造
        cout << newStr << endl;
        cout << ori << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    int main()
    {
        std::string ori("abc");
        std::string newStr = std::move(ori); // 构造了一个右值,将亡值
        cout << newStr << endl; // abc
        cout << ori << endl; // 空
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    
    
    struct Str
    {  
        Str() = default;
    
        Str(Str&& x)
            : val(x.val)
            , a(std::move(x.a))
        {
        }
    
        void fun()
        {
            cout << val << ' ' << a << endl;
        }
        
        int val = 3;
        std::string a = "abc";
    };
    
    
    int main()
    {
        Str m;
        m.fun(); // 3 abc
        Str m1 = std::move(m);
        m.fun(); // 3 "" , 字符串move,移动构造函数
        m1.fun(); // 3 abc
    }
    
    • 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 <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    
    struct Str2
    {
        Str2() = default;
        // Str2拷贝构造
        Str2(const Str2&)
        {
            cout << "Str2 copy constuctor is called" << endl;
        }
    
    /*
        // Str2移动构造(有移动调移动,没有移动调拷贝)
        Str2(Str2&&)
        {
            cout << "Str2 move constuctor is called" << endl;
        }
    */
    
    };
    
    struct Str
    {  
        Str() = default;
        Str(const Str&) = default;
    
        Str(Str&&) = default; // 缺省移动构造函数
    
    
        // 对每一个类型调用默认的移动构造
        int val = 3; // 内建类型去赋值
        std::string a = "abc"; // strig类型去移动
        Str2 m_str2; // 这个数据成员只有拷贝构造函数,没有移动构造函数,那么Str的移动构造函数
        // 在处理Str2时,会调用Str2的拷贝构造函数。
        // 换句话说,Str2有移动,调移动,没移动构造,调拷贝构造
    };
    
    
    int main()
    {
        Str m;
        Str m2 = std::move(m);
    }
    
    • 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

    右值引用对象用作表示式时是左值。
    在这里插入图片描述

    拷贝赋值与移动赋值函数(operator =)

    在这里插入图片描述
    operator = ,运算符重载。

    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    
    struct Str
    {  
        Str() = default;
        Str(const Str&) = default; // 拷贝构造函数
        Str(Str&& x) noexcept = default; // 移动构造函数
    
        // 拷贝赋值函数,赋值函数不能使用初始化列表,m2已经完成初始化过了,这里是赋值
        // 返回当前类型的引用,return *this
        Str& operator= (const Str& x)
        {
            cout << "copy assignment is called" << endl;
            val = x.val;
            a = x.a;
            return *this;
        }
    
        // 移动赋值函数
        Str& operator= (Str&& x)
        {
            if (&x == this)
            {
                cout << "dummy assignment is called" << endl;
                return *this; // 处理给自身赋值的情况
            } 
            cout << "move assignment is called" << endl;
            val = std::move(x.val);
            a = std::move(x.a);
            return *this;
        }
    
        int val = 3; 
        std::string a = "abc"; 
    };
    
    
    int main()
    {
        Str m;
        Str m2;
        // m2 = m; // 这不再是构造,而是一个赋值的过程
        // m2 = std::move(m); // 调用移动赋值函数
        m = std::move(m);
    }
    
    • 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

    在一些情况下,编译器会自动合成。

    #include <iostream>
    #include <string>
    #include <vector>
    using namespace std;
    
    struct Str
    {  
       
        int val = 3; 
        std::string a = "abc"; 
        int* ptr;
    
    };
    
    
    int main()
    {
        Str m;
        Str m2;
        Str m3;
        m = std::move(m2); // 编译器自动合成移动赋值函数
        m = m3; // // 编译器自动合成拷贝赋值函数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
  • 相关阅读:
    初学unity开发学习笔记----第一天
    PHP代码审计16—ThinkPHP代码审计入门
    Jetsonnano B01 笔记5:IIC通信
    ECMA Script Module(ES module)知识点
    【图像分类】2022-MaxViT ECCV
    java计算机毕业设计在线教育资源管理系统源程序+mysql+系统+lw文档+远程调试
    ArcGIS要点和难点以及具体应用和优缺点介绍
    废弃的kotlin-android-extensions,是时候接受ViewBinding了
    Flutter - APP主界面Tabbar保持页面状态
    k8s创建并发布WordPress
  • 原文地址:https://blog.csdn.net/weixin_43716712/article/details/125533014