13.1拷贝、赋值与销毁

13.1.1拷贝构造函数

当定义一个类时,我们显式或者隐式地指定在此类型的对象的拷贝、赋值、移动、销毁时做什么。一个类通常通过5种特殊的构造函数来控制这些操作,包括:拷贝构造函数(copy constructor)、拷贝赋值运算符(copy assignment operator),移动构造函数(move constructor)、移动赋值运算符(move assignment operator)、析构函数(destructor)。

拷贝构造函数|移动构造函数:定义了当用同类型的一个对象初始化另一个对象时的操作。

拷贝赋值运算符|移动赋值运算符:定义了当一个同类型的对象赋予另一个对象所定义的操作。

析构函数:定义了当此类型对象销毁时的操作。

#include <string>
#include <iostream>
#include <vector>
using namespace std;
class Sales_data{public:Sales_data() = default;Sales_data(const Sales_data &);public:Sales_data(int u,double d);Sales_data(const string &);private:string bookno;int units_sold = 0.0;double revenue = 0.0;
};Sales_data::Sales_data(const string &s):bookno(s){
cout << "execute Sales_data(const string &)"<< endl;
}Sales_data::Sales_data(const Sales_data &s):bookno(s.bookno),units_sold(s.units_sold),revenue(s.revenue)
{
cout << "execute copy constructor." << endl;
}Sales_data::Sales_data(int u,double d):units_sold(u),revenue(d)
{
cout << "execute constructor." << endl;
}const Sales_data &test(const Sales_data &s)
{
return s;
}int main()
{
string str = "you are good";Sales_data s;
Sales_data s4 = s;
Sales_data s1(1,2);  //initialize execute constructor
Sales_data s2(s1);   //initialize execute copy constructor
Sales_data s3 = s2;  //initialize execute copy constructor
cout << "s5 is flowing:" << endl;
Sales_data s5 = str;cout << "test0:" << endl;
test(s5);cout << "test1:" << endl;
Sales_data s7[4]{s,s1,s2,s4};cout << "test2" << endl;
vector<Sales_data> v1;
//push_back
cout << "push_back():" << endl;
v1.push_back(s);
cout << "insert :" << endl;
v1.emplace(v1.begin(),s1);
cout << "emplace:"<< endl;
v1.emplace(v1.begin(),string("abc"));
cout << "last one:" << endl'
Sales_data s9 = Sales_data();
Sales_data s10(s9);
return 0;
}

运行结果:

execute copy constructor.
execute constructor.
execute copy constructor.
execute copy constructor.
s5 is flowing:
execute Sales_data(const string &)
test0:
test1:
execute copy constructor.
execute copy constructor.
execute copy constructor.
execute copy constructor.
test2
push_back():
execute copy constructor.
insert :
execute copy constructor.
execute copy constructor.
emplace:
execute Sales_data(const string &)
execute copy constructor.
execute copy constructor.
last one:
execute copy constructor.

下面给一个习题:

对于一个类定义,下列叙述中错误的是
A 如果没有定义拷贝构造函数,编译器将产生一个拷贝构造函数
B 如果没有定义缺省的构造函数,编译器将一定生成一个缺省的构造函数
C 如果没有定义构造函数,编译器将会产生一个缺省的构造函数和一个拷贝构造函数
D 如果已经定义了构造函数和拷贝构造函数,编译器不会生成任何构造函数.
D肯定是对的,A我觉得也没错,C我也觉得没错,那就是B了吗?我试过,编译出错,B就是错误的吧
我查了答案,选B

拷贝初始化和直接初始化:

拷贝构造函数的定义:如果构造函数的第一个参数是自身类类型的引用,且所有其它参数(如果有的话)都有默认值,则此构造函数就是拷贝构造函数。拷贝构造函数在以下几种情况下使用:

1.拷贝初始化(用=定义变量)

2.将一个对象作为实参传递给非引用类型的形参。

3.一个返回类型为非引用类型的函数返回一个对象。

4.用花括号列表初始化一个数组中的元素或者一个聚合类中的成员。

5.初始化标准库容器或调用其insert/push操作时,容器会对元素进行拷贝初始化。

注意,编译器虽然会优化如下代码:

string str = "hello,world!!!";
Sales_data s1 = str;

优化成,

Sales_data s1(str);

但是,必须拷贝构造函数必须可以调用,否则会出错。语法上是一回事,编译器执行是另一回事。具体参考c++ primer 5th 第442页之----编译器可以绕过拷贝构造函数。

13.1.2拷贝赋值运算符

1.什么是拷贝赋值运算符:

拷贝赋值运算符本身是一个重载的赋值运算符,定义为类的成员函数,左侧运算对象绑定到隐含的this参数,而右侧的运算对象是所属类类型的,作为函数的参数,函数返回指向左侧运算对象的引用。特点是:

(1)返回值:指向左侧运算对象的引用

(2)名字:由operator关键字后接一个表示要定义的运算符的符号(=),通常是operator=

(3)参数:表示运算符运算的对象。拷贝赋值运算符接受一个与其所在类相同类型的参数。

(4)定义的操作:类似拷贝构造函数,某些拷贝赋值运算符禁止该类型对象的赋值。如果拷贝构造运算符并非出于此目的,它会将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员。

#include <string>
#include <iostream>
#include <memory>
using namespace std;
class Sales_data{public:Sales_data() = default;Sales_data(const string &);Sales_data(const Sales_data &);
//返回类型也可以是const的,那么什么时候该const,什么时候不该?Sales_data& operator=(const Sales_data &);private:string bookno;double revenue = 0.0;unsigned units_sold = 0;
};
Sales_data::Sales_data(const Sales_data &s):
bookno(s.bookno),revenue(s.revenue),units_sold(s.units_sold){
cout << "execute copy contructor." << endl;
}
Sales_data::Sales_data(const string &s):bookno(s){cout << "execute constructor." << endl;}
Sales_data&
Sales_data::operator=(const Sales_data &s){
//必须在函数体内用拷贝完成,不能在函数题和函数参数列表之间采用成员初始化,否则
//会报错:only constructors take member initializers.
bookno = s.bookno;
units_sold = s.units_sold;
revenue = s.revenue;
cout << "execute copy assignment operator." << endl;
return *this;
}
int main()
{
Sales_data s1;
Sales_data s2("This is only a test.");
s1 = s2;return 0;
}

运行结果:

execute constructor.
execute copy assignment operator.

疑问?拷贝赋值运算符返回值可以是本身类类型引用或者const本身类类型的引用。什么时候定义成const的呢?

1.什么时候使用拷贝赋值运算符?答:当对类对象进行赋值时,会使用拷贝赋值运算符。

2.合成拷贝赋值运算符完成什么工作?答:通常情况下,合成拷贝赋值运算符会把右侧对象的所有非static成员逐个赋予左侧对象的对应成员,这些赋值操作是由成员类型的拷贝赋值运算符来完成的。某些拷贝赋值运算符会起到禁止该类型对象赋值的效果。

3.什么时候编译器会生成合成拷贝赋值运算符?答:当一个类未定义自己的拷贝赋值运算符,那么编译器会自动生成一个拷贝赋值运算符。

13.1.3析构函数

1.析构函数的作用:析构函数和构造函数执行相反的操作,构造函数初始化对象的非static数据成员,还可能做一些其它工作;析构函数释放对象所使用的资源,并销毁对象的非static数据成员。

2.析构函数的定义:

析构函数是类的一个成员函数,由波浪号接类名构成。它没有返回值,也不接受参数。所以,析构函数不能被重载。对于一个类,只有唯一的析构函数。

class Sales_data{
public:....
~Sales_data(){}   //析构函数
private:...
};

3.组成:由一个函数体和一个析构部分;构造函数由一个初始化部分和一个函数题构成。

4.析构函数执行过程:首先执行函数体,然后销毁成员。成员按照初始化顺序的逆序销毁。

5.处理内置指针:隠式销毁一个内置指针类型的成员不会delete它所指向的成员。

6.智能指针:与普通指针不同,智能指针是类类型,所以具有析构函数,因此,与普通指针不同,智能指针的成员在析构阶段会被自动销毁。

7.何时调用析构函数:

(1)变量在离开作用域时被销毁。

(2)当一个对象被销毁时,其成员被销毁。

(3)容器(无论是标准库容器还是数组)被销毁时,其元素被销毁。

(4)对于动态分配的对象,当对指向它的指针调用delete运算符时被销毁。

(5)对于临时对象,当创建它的完整表达式结束时被销毁。

注意:

1.隐式销毁一个内置指针类型的成员不会delete它所指向的对象。(只有显式delete才调用该对象的析构函数,这和内置的其它类型或者智能指针不一样)

2.当指向一个对象的引用或者指针离开作用域时,析构函数不会执行。

(只有显式地调用delete时,才调用该对象的类类型的析构函数)

3.析构函数体自身并不销毁成员。成员是在析构函数体之后隐含的析构阶段中被销毁的。在整个对象销毁的过程中,析构函数体是作为成员销毁步骤之外的另一部分而进行的。

c++ primer 5th  13.1.3节习题

练习13.9.

答:析构函数就是波浪号加类名构成的类的成员函数,不接受参数也没有返回值。完成的工作和构造函数相反,构造函数初始化类的非static数据成员。析构函数销毁类的非static数据成员,释放类使用的资源。当用户未定义析构函数时,编译器会生成合成析构函数。

习题13.10

答:当StrBlob对象销毁时,执行完析构函数的空函数体之后,会进行隐含的析构阶段,销毁非静态数据成员data,这会调用shared_ptr的析构函数,会递减data的引用计数,如果data的引用计数变为0,那么释放data所指向的vector对象的内存。

对StrBlobPtr对象,合成析构函数在隐含的析构阶段会销毁数据成员wptr和curr,销毁wptr会调用weak_ptr的析构函数,引用计数不变,而curr是内置类型,销毁它不会有特殊动作。

习题13.11

class HasPtr{public:HasPtr(const string &s = string()):ps(new string(s)),i(0){}~HasPtr(){delete ps;}private:string *ps;int i;
};

函数体内,放delete语句。

习题13.12

这段代码会发生三次析构函数的调用:

  (1)函数结束时,局部变量item1的生命期结束,被销毁,Sales_data的析构函数被调用。

(2)函数结束时,item2在函数结束时被销毁,Sales_data的析构函数被调用。

(3)函数结束时,参数accum的生命期结束,被销毁,Sales_data的析构函数被调用。

在函数结束时,trans的生命期也结束了,但它是Sales_data的指针,并不是指向它的Sales_data对象的生命期结束(只有delete指针时,指向的动态对象的生命期才结束),所以不会引起析构函数的调用。

习题13.13

#include <string>
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class X{public:X(){cout << "X()" << endl;}X(const X&a):num(a.num),bookno(a.bookno){cout << "X(const X&)" << endl;}X(int i,const string &s):num(i),bookno(s){cout << "X(int i,const string &s)" << endl;}X(int i):bookno(string()){cout << "X(int i)" << endl;}~X(){cout << "~X()" << endl;}X& operator=(const X&a){num = a.num;bookno = a.bookno;cout << "X& operator=(const X&)" << endl;return *this;}private:int num = 0;string  bookno;};const X & test1(const X & a)
{
return a;
}
X test2(const X a,const X b)
{return a;
}int main()
{
//默认初始化,调用默认构造函数
X a;
//调用构造函数
X b(2,"hello,world");
//调用拷贝构造函数
X c(b);
//调用拷贝构造函数,用c给d拷贝
X d = c;
//调用拷贝构造函数和构造函数,具体过程中可能略过拷贝构造函数
X e = 1234;
//调用拷贝赋值运算符
e = c;
//调用函数,参数和反语i类型都是引用
cout << "call function test1:" << endl;
//结果执行一次拷贝赋值运算符,函数参数是引用,不执行拷贝构造函数
c = test1(e);
cout << endl << endl;cout << "call function test2:" << endl;
//执行2次拷贝构造函数,因为函数2个参数都是非引用的,然后执行
c = test2(e,d);//定义一个vector
vector<X> xvec;
cout << "vec 's push_back:" <<  endl;
xvec.push_back(a);
cout << "xvec insert:" << endl;
xvec.insert(xvec.begin(),c);cout << "execute: X *p = new X" << endl;
X *p = new X();
delete p;
cout << "this is the end.." << endl; return 0;}

运行结果:

X()
X(int i,const string &s)
X(const X&)
X(const X&)
X(int i)
X& operator=(const X&)

参考教程:c++ primer 第五版    2021.6.24日晚上

c++拷贝、赋值和销毁的简单介绍相关推荐

  1. vector的简单介绍

    1.vector的简单介绍 vector作为STL提供的标准容器之一,是经常要使用的,有很重要的地位,并且使用起来也是灰常方便.vector又被称为向量,vector可以形象的描述为长度可以动态改变的 ...

  2. shell语法简单介绍

    一.基本的语法 1.1.shell文件开头 shell文件必须以以下的行開始(必须方在文件的第一行):  #!/bin/sh  符号#!用来告诉系统它后面的參数是用来运行该文件的程序.在这个样例中我们 ...

  3. C++移动构造函数以及move语句简单介绍

    转自https://www.cnblogs.com/qingergege/p/7607089.html 首先看一个小例子: #include <iostream> #include < ...

  4. Java虚拟机内存模型简单介绍

    一.虚拟机 同样的java代码在不同平台生成的机器码肯定是不一样的,因为不同的操作系统底层的硬件指令集是不同的. 不知道同学们还记不记得,在下载jdk的时候,我们在oracle官网,基于不同的操作系统 ...

  5. Python的简单介绍(二)

    接Python的简单介绍(一): 九.条件语句 if 判断条件: 执行语句-- else: 执行语句-- if 判断条件1: 执行语句1-- elif 判断条件2: 执行语句2-- elif 判断条件 ...

  6. C++ shared_ptrweak_ptr的简单介绍和仿写

    文章目录 shared_ptr 一.shared_ptr的简单介绍 二.shared_ptr的使用 1.函数介绍 2.使用 三.shared_ptr对象创建方法的讨论 1. 有两种常见的创建的方法: ...

  7. 【C++11智能指针】shared_ptr的初始化、拷贝构造和拷贝赋值、移动构造和移动赋值

    文章目录 1.智能指针概述 2.shared_ptr的初始化 2.1 shared_ptr和new结合使用(直接初始化) 2.2 make_shared函数 3.shared_ptr的拷贝构造和拷贝赋 ...

  8. 简单介绍一下R中的几种统计分布及常用模型

    统计学上分布有很多,在R中基本都有描述.因能力有限,我们就挑选几个常用的.比较重要的简单介绍一下每种分布的定义,公式,以及在R中的展示. 统计分布每一种分布有四个函数:d――density(密度函数) ...

  9. iOS开发UI篇—UIWindow简单介绍

    iOS开发UI篇-UIWindow简单介绍 一.简单介绍 UIWindow是一种特殊的UIView,通常在一个app中只会有一个UIWindow iOS程序启动完毕后,创建的第一个视图控件就是UIWi ...

最新文章

  1. 微服务下的容器部署和管理平台Rancher
  2. cad线性标注命令_CAD线性标注如何使用的
  3. 解决:Module not found: node_modules\sass-loader\package.json (directory description file)
  4. php对象存储hadoop存储,三个理由告诉你对象存储替换HDFS还不错
  5. PHP Object对象转换为Array数组
  6. volume image
  7. php fpm 平滑重启,nginx、php-fpm平滑重启和重载配置
  8. PreScan传感器(零)——通用配置
  9. 中兴B860AV2.1-T_3.0_s905l3-b_uwe5621_线刷固件(附短接点示意图)
  10. 读《Machine Learning Done Wrong》(机器学习易犯错误)有感
  11. 2018-9-30-C#-从零开始写-SharpDx-应用-画三角
  12. 职高c语言,C语言职高班教学计划
  13. Java游戏培训机构哪家专业
  14. 马氏距离 (马哈拉诺比斯距离) (Mahalanobis distance)
  15. 万兆单模模块_万兆(10G SFP+)单模光模块的介绍及应用
  16. 漫画版的你,离线版AnimeGANv2初体验
  17. 05、Python中转义字符与字符串
  18. Javascript frameworks
  19. 贴片陶瓷电容材质NPO、C0G、X7R、X5R、Y5V、Z5U区别
  20. 4. Python脚本学习实战笔记四 新闻聚合

热门文章

  1. 剑指Offer_12_数值的整数次方
  2. MySql连接——内连接、外连接(左连接、右连接、全连接)
  3. 【vbs】vbs写ini文件
  4. 一块网卡设置多个IP地址
  5. 老生常谈:装饰者模式
  6. oracle段管理方式设为自动,oracle自动段管理ASSM笔记
  7. App设计灵感之十二组精美的家具产品电商App设计案例
  8. OpenGL ES之GLSL常用内建函数
  9. 841. Keys and Rooms 钥匙和房间
  10. 使用pytz模块进行时区转换及时间计算