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


文章目录

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

构造函数的概念 C++|C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数(缺省、单一、拷贝、移动、赋值))
文章图片

构造函数 构造对象时调用的函数,名称与类名相同,没有返回值,可以包含多个版本(重载)
#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++|C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数(缺省、单一、拷贝、移动、赋值))
文章图片

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

通常要求我们写代码的时候,初始化列表的顺序和声明顺序相同。
不然让读代码的人,有疑惑。
【C++|C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数(缺省、单一、拷贝、移动、赋值))】使用初始化列表覆盖类内成员初始化
#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++|C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数(缺省、单一、拷贝、移动、赋值))
文章图片

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

如果类中没有任何构造函数,在允许的条件下,编译器会合成一个缺省的构造函数。
C++|C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数(缺省、单一、拷贝、移动、赋值))
文章图片

如果提供了构造函数,编译器就不会自动合成默认构造函数。
C++|C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数(缺省、单一、拷贝、移动、赋值))
文章图片

调用缺省构造函数时,避免most vexing parse
C++|C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数(缺省、单一、拷贝、移动、赋值))
文章图片

用default来定义缺省构造函数
C++|C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数(缺省、单一、拷贝、移动、赋值))
文章图片

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

缺省构造函数是包含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++|C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数(缺省、单一、拷贝、移动、赋值))
文章图片

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

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

如果未显示提供,那么编译器会合成一个,合成的版本会依次对每个数据成员调用拷贝构造。
移动构造函数 C++|C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数(缺省、单一、拷贝、移动、赋值))
文章图片

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

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++|C++ 基础与深度分析 Chapter11 类与面向对象编程(构造函数(缺省、单一、拷贝、移动、赋值))
文章图片

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

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

    推荐阅读