C++第13章 拷贝控制 第一节

拷贝、赋值与销毁

拷贝构造函数

拷贝构造函数:若一个构造函数的第一个参数是自身类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数

class Foo
{
public:
	Foo();//默认构造函数
	Foo(const Foo&)//拷贝构造,注意默认构造与拷贝构造之间的区别
};

 一般来说,如果类中没有定义拷贝构造函数,编译器会默认定义一个合成拷贝构造函数,与合成默认构造函数不同的是,即使我们定义了其他的构造函数,编译器也会定义一个合成拷贝构造函数

string dots(10, '.')  //直接初始化
string s(dots);   //直接初始化
string s2 = dots;   //拷贝初始化
string null_book = "9-999-9999-9";  //拷贝初始化
string nines=string(100,'9')  //拷贝初始化

拷贝初始化通常使用拷贝构造函数完成,拷贝初始化也会使用移动构造函数完成,在后面会讲到。

拷贝初始化不仅在用 = 定义的时候会发生,在以下情况:

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

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

3.从一个花括号列表初始化一个数组中的元素或一个聚合类中的成员

在函数调用过程中,具有非引用类型的参数要进行拷贝初始化

当一个函数具有非引用的返回类型时,返回值会被用来初始化调用方


拷贝构造函数自己的参数为什么必须是引用类型?

若拷贝构造函数的参数不是引用类型,当我们调用拷贝构造函数时,我们必须拷贝它的实参,为了拷贝实参,又会调用拷贝构造函数,会如此循环下去。

编译器不是必须要使用拷贝构造.

课后题
//13.1 
拷贝构造函数是参数为同类对象引用其它参数都有默认值的构造函数。使用其它对象初始化新对象时使用。

//13.2 
sales_data::Sales_data(Sales_data rhs);//错误
sales_data::Sales_data(Sales_data &rhs);
应该使用引用参数,否则会陷入循环调用。

//13.3 当我们拷贝一个strB1ob时,会发生什么?拷贝一个strB1 ober

这两个类都未定义拷贝构造函数,因此编译器为它们定义了合成的拷贝构造函数。合成的拷贝构造函数逐个拷贝非 const成员,对内置类型的成员,直接进行内存拷贝,对类类型的成员,调用其拷贝构造函数进行拷贝。
因此,拷贝一个StrBlob时,拷贝其唯一的成员data,使用 shared ptr的拷贝构造函数来进行拷贝,因此其引用计数增加1。
拷贝一个 StrBlobptr时,拷贝成员wptr,用 weak ptr的拷贝构造函数进行拷贝,引用计数不变,然后拷贝curr,直接进行内存复制。

//13.4    指出使用拷贝构造函数的地方
Point global;
Point foo bar(Point arg)
{
    Point local = arg;//使用
    Point *heap = new Point (global);
    *head = local;//使用
    Point pa[4]= {local, *heap};//使用
    return *heap;//使用
}

//13.5
HasPtr(HasPtr& hp) 
{
   ps = new string(*hp.ps);
   i = hp.i;
}

拷贝赋值运算符

    class A
    {

    };
    A a1,a2;
    a1=a2;//调用类A中的拷贝赋值运算符

若类中不包含拷贝赋值运算符,则编译器会定义一个合成拷贝运算符

重载运算符在本质上是函数,名字有operator关键字后接表示要定义的运算符符号组成

重载运算符的参数表示运算符的运算对象,对于一些运算符例如赋值运算符必须定义成员函数

拷贝赋值运算符接受一个与其所在类相同类型的参数

	Employee& operator = (const Employee& n)//拷贝赋值运算符
	{
		name = n.name;
		return *this;
	}

赋值运算符通常应该返回一个指向其左侧运算对象的引用

 

课后题
//13.6
   拷贝赋值运算符本身是一个重载的赋值运算符,定义为类的成员函数,左侧运算对象绑定到隐含的this参数,而右侧运算对象是所属类类型的,作为函数的参数,函数返回指向其左侧运算对象的引用。
当对类对象进行赋值时,会使用拷贝赋值运算符。通常情况下,合成的拷贝赋值运算符会将右侧对象的非 static成员逐个赋予左侧对象的对应成员,这些赋值操作是由成员类型的拷贝赋值运算符来完成的。
   若一个类未定义自己的拷贝赋值运算符,编译器就会为其合成拷贝赋值运算符,完成赋值操作,但对于某些类,还会起到禁止该类型对象赋值的效果

//13.7
StrBlob赋值对应的资源和对象,计数加一   
StrBlobPtr赋值对应对象

//13.8
Hasptr& operator=(const Hasptr& hp)
{
	auto newps = new string(*hp.ps);
	delete ps;
	ps = newps;
	i = hp.i;
	return *this;
}

析构函数

析构函数执行与构造函数相反的操作

析构函数释放对象使用的资源,并销毁对象的非static数据成员

%
Hasptr::~Hasptr();//析构函数声明
%

对于一个给定的类,有且只会有一个析构函数

在析构函数中,首先执行函数体,然后销毁数据成员。成员按照初始化顺序的逆序销毁。

什么时候调用析构函数?

1.变量在离开作用域时

2.当一个对象被销毁,其成员被销毁

3.容器被销毁,其元素被销毁

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

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

 

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

练习题

课后题
//13.9
   析构函数释放对象使用的资源并销毁对象的非static数据成员。合成析构函数释放类的成员对象。在用户未定义析构函数的时候会自动合成。

//13.10 
可参照第12章

//13.11

    ~HasPtr() 
    {
        delete ps;
    }

//13.12
这段代码中会发生三次析构函数调用:
1.函数结束时,局部变量item1的生命期结束,被销毁, Sales data的析构函数被调用。
2.类似的,item2在函数结束时被销毁, Sales data的析构函数被调用。
3.函数结束时,参数 accum的生命期结束,被销毁,sa1 es data的析构函数被调用。在函数结束时, trans的生命期也结束了,但它是 Sales data的指针,并不是它指向的sa1 es data对象的生命期结束(只有 delete指针时,指向的动态对象的生命期才结束),所以不会引起析构函数的调用。
课后题
//13.13
#include<iostream>
#include<string>
using namespace std;
class X
{
public:
	X() = default;
	X(const string& rhs)
	{
		k = rhs;
		cout << "X()..." << endl;
	}
	X(const X& rhs)
	{
		k = rhs.k;
		cout << "X(const X&)..." << endl;
	}
	X& operator = (const X& rhs)
	{
		cout << "X& operator = (const X& rhs)..." << endl;
	}
	~X()
	{
		cout << "~X()..." << endl;
	}
private:
	string k;
};
int main()
{
	X b;
	X a("hello");
	X c = a;
	X* ps = new X();
	delete ps;
	return 0;

}

三/五法则

拷贝构造函数  拷贝赋值运算符  析构函数  移动构造函数  移动构造运算符

需要析构函数的类也需要拷贝和赋值工作


 

课后题
//13.14-16
#include<iostream>
using namespace std;
class numbered 
{
public:
	int  mysn;
	numbered() { mysn = seq++; }//默认构造函数
	numbered(numbered& n) { mysn = seq++; }
private:
	int static seq;
};
int  numbered::seq = 0;

void f(numbered &s)
{
	cout << s.mysn << endl;
}
#if 0
void f(const numbered& s)
{
	cout << s.mysn << endl;
}
#endif
int main()
{
	numbered a, b = a, c = b;
	f(a);
	f(b);
	f(c);
	return 0;
}
13.14输出结果为 0 0 0
13.15输出结果为 3 4 5
13.16输出结果为 0 1 2


使用default

通过将拷贝控制成员定义为=default来显示的要求编译器生成合成的版本

阻止拷贝

在新标准下,可以通过将拷贝函数和拷贝赋值运算符定义为删除的函数来阻止拷贝和赋值

%
A::A()=default;
A::A(const A &rhs)=delete;//阻止拷贝
A::A& operator = (const A &rhs)=delete;//阻止赋值
%

析构函数不能是删除的成员

课后题13.18
#include<iostream>
#include<string>
using namespace std;
class Employee
{
private:
	static int seq;
public:
	int mysn;
	Employee()
	{
		mysn = seq++;
	}

	Employee (const string &s)
	{
		name = s;
		mysn = seq++;
	}

	Employee (Employee& n)
	{
		name = n.name;
		mysn = seq++;
		
	}
	Employee& operator=(const Employee& n)
	{
		name = n.name;
		return *this;
	}
	const string &get_name()
	{
		return name;
	}
	int get_mysn()
	{
		return mysn;
	}
private:
	string name;
};
int Employee::seq = 0;
void f( Employee &n )
{
	cout << n.get_name() << n.get_mysn() << endl;
}
int main()
{
	
	Employee a("赵");
	Employee b=a;
	Employee c=b;
	f(a);
	f(b);
	f(c);
	return 0;
}

 

 

 

 

 

 

 

 

 

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值