欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 新闻 > 焦点 > 《C++Primer 第五版 中文版 》—— 第13章 拷贝控制

《C++Primer 第五版 中文版 》—— 第13章 拷贝控制

2024/10/23 15:19:08 来源:https://blog.csdn.net/qq_43648751/article/details/143076881  浏览:    关键词:《C++Primer 第五版 中文版 》—— 第13章 拷贝控制

《C++Primer 第五版 中文版 》—— 第13章 拷贝控制

      • 构造函数什么时候调用?
      • 拷贝构造函数,移动构造函数什么时候调用?
      • 拷贝赋值运算符、移动赋值运算符什么时候调用?
      • 析构函数什么时候调用?
      • 构造函数,拷贝构造函数,移动构造函数,拷贝赋值运算符,移动赋值运算符和析构函数的自定义和编译器合成关系是什么?
    • 13.1 拷贝、赋值与销毁
      • 如何定义拷贝构造函数?
      • 拷贝构造函数在哪些情况下会被隐式使用?
      • 如何定义合成拷贝构造函数?
      • 合成拷贝构造函数的作用是什么?
      • 拷贝初始化和直接初始化的区别?
      • 什么是聚合类?
      • 拷贝初始化的发生时机?
      • 为什么拷贝构造函数的第一个参数必须是引用类型?
      • 容器的初始化,insert元素,push元素, emplace元素,调用类的构造函数、移动构造函数还是拷贝构造函数?
      • 如何理解:在拷贝初始化过程中,编译器可以(但不是必须)跳过拷贝/移动构造函数,直接创建对象。
      • 什么是重载运算符?
      • 如何定义拷贝赋值运算符?
      • 如何定义合成拷贝赋值运算符?
      • 合成拷贝赋值运算符的作用?

构造函数什么时候调用?

创建对象时。

拷贝构造函数,移动构造函数什么时候调用?

同类型对象初始化本对象时。

拷贝赋值运算符、移动赋值运算符什么时候调用?

同类型对象赋值给本对象时。

析构函数什么时候调用?

销毁对象时。

构造函数,拷贝构造函数,移动构造函数,拷贝赋值运算符,移动赋值运算符和析构函数的自定义和编译器合成关系是什么?

构造函数:
未自定义任何构造函数(自实现的默认构造函数,带参数的构造函数,拷贝构造函数,移动构造函数),编译器合成默认构造函数。
自定义任何一个构造函数(自实现的默认构造函数,带参数的构造函数,拷贝构造函数,移动构造函数),编译器不会合成默认构造函数。

拷贝构造函数:
用户定义了拷贝构造函数或移动构造函数,编译器不会合成拷贝构造函数。
用户未定义拷贝构造函数,编译器合成拷贝构造函数。

拷贝赋值运算符:
用户定义了拷贝赋值运算符或移动赋值运算符,编译器不会合成拷贝赋值运算符。
用户未定义拷贝赋值运算符,编译器合成拷贝赋值运算符。

移动构造函数:
用户定义了拷贝构造函数、移动构造函数、拷贝赋值运算符、移动赋值运算符或析构函数其中的一个,编译器不会合成默认移动构造函数。

用户未定义拷贝构造函数、移动构造函数、拷贝赋值运算符、移动赋值运算符和析构函数,编译器合成默认移动构造函数。

移动赋值运算符:
用户定义了拷贝构造函数、移动构造函数、拷贝赋值运算符、移动赋值运算符或析构函数其中的一个,编译器不会合成默认移动赋值运算符。

用户未定义拷贝构造函数、移动构造函数、拷贝赋值运算符、移动赋值运算符和析构函数,编译器合成默认移动赋值运算符。

析构函数:
用户定义了析构函数,编译器不会合成默认析构函数。
用户未定义析构函数,编译器合成默认析构函数。

13.1 拷贝、赋值与销毁

如何定义拷贝构造函数?

  1. 构造函数。
  2. 第一个参数是自身类类型 const 的引用,任何额外参数都有默认值。
#include <iostream>
using namespace std;class Foo
{
public:Foo();			//默认构造函数Foo(const Foo&);//拷贝构造函数
};int main()
{cout << "test run success" << endl;return 0;
}

拷贝构造函数在哪些情况下会被隐式使用?

TODO

如何定义合成拷贝构造函数?

没有为类自定义拷贝构造函数时,编译器会定义合成拷贝构造函数。

合成拷贝构造函数的作用是什么?

将参数对象的每个 非static 成员逐个拷贝到正在创建的对象中,每个成员类型决定了如何拷贝(类类型对象调用拷贝构造函数来拷贝;内置类型直接拷贝;数组逐元素拷贝,如果数组元素是类类型,使用元素的拷贝构造函数来拷贝。)。

拷贝初始化和直接初始化的区别?

string data(10, '.');	//直接初始化,调用构造函数。
string nines = string(100, '9');//拷贝初始化,调用拷贝构造函数或移动构造函数。

什么是聚合类?

  1. 没有用户提供的构造函数(没有定义任何构造函数,或者所有构造函数都有默认值)
  2. 没有 private 或 protected 的非静态数据成员,
  3. 没有虚函数。
  4. 没有基类。
struct MyAggregate {int a;double b;std::string c;
};

拷贝初始化的发生时机?

#include <iostream>
#include <string>class MyString
{
public:MyString(const char* s) : str(s){std::cout << "Constructing MyString from const char*: " << str << std::endl;}MyString(const MyString& other) : str(other.str){std::cout << "Copy constructing MyString: " << str << std::endl;}MyString(MyString&& other) noexcept : str(std::move(other.str)){std::cout << "Move constructing MyString: " << str << std::endl;}// Just for demonstrationconst std::string& get() const { return str; }private:std::string str;
};struct MyAggregate
{int a;double b;MyString c;
};void func1(MyString s1) {}MyString func2()
{MyString s2("test");return s2; //编译器优化,s2直接在函数调用的返回值(result)存储位置进行构造,不会调用拷贝构造函数和移动构造函数。
}int main()
{MyString arr[] = { "apple", "banana", "cherry" };	//花括号列表初始化数组中的元素,调用元素构造函数。func1(arr[0]);	//实参传递给非引用类型形参,调用类拷贝构造函数。auto result = func2();MyAggregate obj = { 42, 3.14, "hello" };	//花括号列表初始化聚合类中的成员,s调用构造函数。return 0;
}

运行结果:
运行结果

为什么拷贝构造函数的第一个参数必须是引用类型?

拷贝构造函数被用来初始化非引用类型对象。

如果拷贝构造函数不使用引用类型,为了调用拷贝构造函数,必须拷贝实参,拷贝实参又需要调用拷贝构造函数,如此会异常无限循环。

拷贝构造函数的第一个参数是引用类型,调用拷贝构造函数时不需要拷贝。

容器的初始化,insert元素,push元素, emplace元素,调用类的构造函数、移动构造函数还是拷贝构造函数?

#include <iostream>
#include <string>
#include <vector>
#include <list>
class MyString
{
public:MyString(const char* s = "") : str(s){std::cout << "Constructing MyString from const char*: " << str << std::endl;}MyString(const MyString& other) : str(other.str){std::cout << "Copy constructing MyString: " << str << std::endl;}MyString(MyString&& other) noexcept : str(std::move(other.str)){std::cout << "Move constructing MyString: " << str << std::endl;}// Just for demonstrationconst std::string& get() const { return str; }private:std::string str;
};int main()
{std::vector<MyString> vs1 = { "data1" };	//先使用 "data1" 构造 MyString 对象,然后容器初始化调用拷贝构造函数std::cout << "============================================================1" << std::endl;MyString data2("data2"); //使用 "data2" 构造 MyString 对象std::cout << "============================================================2" << std::endl;std::vector<MyString> vs2;std::cout << "============================================================3" << std::endl;vs2.push_back(data2);	//push_back 调用拷贝构造函数std::cout << "============================================================4" << std::endl;std::vector<MyString> vs3;vs3.push_back("data3");	//先使用 "data3" 构造 MyString 对象,再push_back调用移动构造函数std::cout << "============================================================5" << std::endl;MyString data4("data4");std::cout << "============================================================6" << std::endl;std::list<MyString> ls;ls.insert(ls.begin(), data4);//insert 调用拷贝构造函数std::cout << "============================================================7" << std::endl;ls.emplace_back("data5"); //emplace_back 调用构造函数std::cout << "============================================================8" << std::endl;ls.insert(ls.begin(), "data6");//先使用 "data6" 构造 MyString 对象,再insert调用移动构造函数std::cout << "============================================================9" << std::endl;return 0;
}

运行结果:
运行结果

如何理解:在拷贝初始化过程中,编译器可以(但不是必须)跳过拷贝/移动构造函数,直接创建对象。

#include <iostream>
#include <string>
#include <vector>
#include <list>class MyString 
{
public:MyString(const char* s = "") : str(s) {std::cout << "Constructing MyString from const char*: " << str << std::endl;}// Just for demonstrationconst std::string& get() const { return str; }private:std::string str;MyString(const MyString& other) : str(other.str){std::cout << "Copy constructing MyString: " << str << std::endl;}MyString(MyString&& other) noexcept : str(std::move(other.str)){std::cout << "Move constructing MyString: " << str << std::endl;}};int main() 
{MyString data2 = "data2";//调用构造函数//说明:拷贝构造和移动构造 是private 无影响return 0;
}

运行结果:
运行结果

什么是重载运算符?

本质是函数。
函数名:operator[要重载的运算符符号]。
参数列表:运算对象。
返回类型:和内置类型一致。

如何定义拷贝赋值运算符?

class Foo 
{
public:Foo& operator=(const Foo&);	//赋值运算符//返回指向左侧运算对象的引用,和内置类型的赋值保持一致。
};

如何定义合成拷贝赋值运算符?

如果一个类未自定义拷贝赋值运算符,编译器生成一个合成拷贝赋值运算符。

合成拷贝赋值运算符的作用?

将右侧运算对象每个非static成员赋予左侧运算对象的对应成员。
类对象成员调用拷贝赋值运算符,基础类型直接赋值。数组类型的成员逐个赋值数组元素。
返回一个指向左侧运算对象的引用。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com