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

发布时间:2024-06-22 13:01

文章目录

  • 构造函数的概念
    • 构造函数
    • 初始化列表
  • 特殊的构造函数
    • 缺省构造函数
    • 单一参数构造函数
    • 拷贝构造函数
    • 移动构造函数
    • 拷贝赋值与移动赋值函数(operator =)

构造函数的概念

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

构造函数

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

#include 
#include 
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);
}

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

#include 
#include 
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();
}

初始化列表

没有使用初始化列表:

#include 
#include 
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"); 

}

使用了初始化列表:

#include 
#include 
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"); 

}

通过引用进行初始化:

#include 
#include 
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 通过引用进行初始化
}

注意,元素的初始化顺序与其声明相关,与初始化列表顺序无关
c++构造和销毁对象是按照一定顺序的,比如先构造了A,再构造了B。那么销毁的时候是先销毁B,再销毁A。
C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数:缺省、单一、拷贝、移动、赋值)_第2张图片

#include 
#include 
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"); 
}

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

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

#include 
#include 
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; 
}

特殊的构造函数

缺省构造函数

不需要提供实际参数就可以调用的构造函数
C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数:缺省、单一、拷贝、移动、赋值)_第3张图片
C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数:缺省、单一、拷贝、移动、赋值)_第4张图片
如果类中没有任何构造函数,在允许的条件下,编译器会合成一个缺省的构造函数。
C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数:缺省、单一、拷贝、移动、赋值)_第5张图片
如果提供了构造函数,编译器就不会自动合成默认构造函数。
C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数:缺省、单一、拷贝、移动、赋值)_第6张图片
调用缺省构造函数时,避免most vexing parse
C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数:缺省、单一、拷贝、移动、赋值)_第7张图片
用default来定义缺省构造函数
C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数:缺省、单一、拷贝、移动、赋值)_第8张图片

单一参数构造函数

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

#include 
#include 
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类型
}

拷贝构造函数

在这里插入图片描述
C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数:缺省、单一、拷贝、移动、赋值)_第9张图片
C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数:缺省、单一、拷贝、移动、赋值)_第10张图片
如果未显示提供,那么编译器会合成一个,合成的版本会依次对每个数据成员调用拷贝构造。

移动构造函数

C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数:缺省、单一、拷贝、移动、赋值)_第11张图片
接收一个当前类右值引用对象的构造函数。进一步提升性能。
C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数:缺省、单一、拷贝、移动、赋值)_第12张图片

int main()
{
    std::string ori("abc");
    std::string newStr = ori; // string 的拷贝构造
    cout << newStr << endl;
    cout << ori << endl;
}
int main()
{
    std::string ori("abc");
    std::string newStr = std::move(ori); // 构造了一个右值,将亡值
    cout << newStr << endl; // abc
    cout << ori << endl; // 空
}
#include 
#include 
#include 
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
}

移动构造函数通常不会抛出异常,因为异常一般出现在内存的分配时,但是移动构造不存在内存的分配,只是偷资源,拷贝可能会出现异常。

#include 
#include 
#include 
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);
}

右值引用对象用作表示式时是左值。
C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数:缺省、单一、拷贝、移动、赋值)_第13张图片

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

C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数:缺省、单一、拷贝、移动、赋值)_第14张图片
operator = ,运算符重载。

#include 
#include 
#include 
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);
}

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

#include 
#include 
#include 
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; // // 编译器自动合成拷贝赋值函数
}

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号