复制控制 - 1【C++ Primer 学习笔记 - 第十三章】

复制构造函数:具有单个形参,该形参(通常是 const 类型)是对该类型的引用。
定义一个新对象,并用一个同类型的对象,来对它进行初始化,则:显示使用复制构造函数。
将该类型的对象,传递给函数,或者从函数返回该类型,则:隐式使用复制构造函数。

析构函数:对象超出作用域,或者,动态分配的对象被删除,则:自动应用析构函数。
可用于释放资源。不论是否定义了自己的析构函数,编译器都会自动执行,类中的 非static 数据成员的析构函数。

复制构造函数、赋值操作符、析构函数,总称:复制控制


常见的情况:类具有指针成员,此时,通常需要类定义自己的复制控制成员。


C++ 支持的两种初始化形式:1、直接初始化    2、复制初始化
直接初始化,将初始化式,放在圆括号()中; 而,复制初始化使用 = 符号。

// 复制初始化,
// string 类型的,接受 C 风格字符串的构造函数,创建临时对象
// 使用 string 的复制构造函数,将 null_book 初始化为该临时对象的副本
string null_book = "9-999-9999-9";

// 直接初始化
string dots(10, '.');

// 使用默认构造函数,创建临时对象,
// 再使用复制构造函数,将 empty_copy 初始化为该临时对象的副本
string empty_copy = string();

// 直接使用默认构造函数,进行初始化
string empty_direct;

// 直接初始化
ifstream file1("filename");

// 错误,ifstream 的复制构造函数是 private
ifstream file2 = "filename";

// 如果 Sales_item 类的,接受一个 string 参数的构造函数
// 是 explicit,则下式错误的
// 如果是普通的构造函数,而不是explicit,则下式正确
Sales_item item = string("9-99-999-9");

// 下式:
// 前两个,先按照给定的 string 实参,创建元素
// 再用复制构造函数,复制到相应元素
// 第三个,直接调用默认构造函数
Sales_item item_arr[] = {
	string("0-201-16848-6"),
	string("0-382-38238-9"),
	Sales_item()
}

// 一个函数,形参为,非引用类型,的时候。
// 将会复制实参的值。
// 同理,返回值是,非引用类型,将返回 return 语句中的值的副本。
// 以上情况,如果是类类型,则其复制过程,是由复制构造函数来完成的。

// 这里,编译器,首先使用 string 默认构造函数,创建一个临时值
// 然后,使用复制构造函数将临时值,复制到 svec 的每个元素中。
// 一般而言,除非确实要使用元素的默认初始值,否则,
// 建议:分配一个空容器,然后,将已知元素的值加入容器中。
vector<string> svec(5);


合成的复制构造函数
即使定义过其他构造函数,但是,只要,没有定义自己的复制构造函数,则,编译器就会为我们合成一个。
如果,定义了复制构造函数,那么,不会合成默认构造函数,因此,此时,还需要自己定义默认构造函数。

合成的复制构造函数,会执行逐个成员初始化,将新对象初始化为原对象的副本。
所谓,逐个成员,是指,将现有对象的所有 非 static 成员,依次复制到正在创建的对象中。
内置类型,直接复制;
类类型,使用该类的复制构造函数进行复制;
数组成员,虽然通常情况下,不能直接复制,
但是,类中的,数组成员,可以被合成的复制构造函数,复制。

class Foo
{
public:
	Foo();

	// 复制构造函数
	// 形参,通常是 const 类型
	// 并且,一般,不会设置为 explicit
	Foo(const Foo&);
};


某些类,不允许复制,如: iostream 类。
为了防止复制,类必须显示声明,它的复制构造函数,为 private。
但,此时,类的友元和成员,仍然可以进行复制,可以 只声明 private 的复制构造函数,而不进行定义。
大多数类,都应该定义复制构造函数 和 默认构造函数。


赋值操作符
如果,类没有定义自己的赋值操作符,则,编译器会合成一个。

重载操作符
重载操作符,是一些函数,其名字为 operator 后,跟着所定义的操作符的符号。
因此,可以定义名为 operator= 的函数,来对赋值进行定义。
操作符函数,有一个返回值,一个形参表。
形参表,必须具有与该操作符的,操作数数目相同的,形参。
如果,操作符是一个形参,则,包含隐式 this 形参。
赋值是二元运算,所以,有两个形参,第一个形参对应左操作数,第二个形参对应右操作数。

当操作符,是成员函数,的时候,第一个操作数,隐式绑定到 this 指针。
因为,赋值必须是类的成员,所以, this 绑定到指向左操作数的指针。
赋值操作符,接受单个形参,且该形参是同一类类型的对象。右操作数一般作为 const 引用传递。
赋值操作符,返回 同一类类型的引用。

class Sales_item
{
public:
	Sales_item& operator=(const Sales_item &);
};

合成赋值操作符,与合成复制构造函数,类似,会执行逐个成员赋值。

class Sales_item
{
public:
	Sales_item& operator=(const Sales_item &);
};

Sales_item& Sales_item::operator=(const Sales_item &rhs)
{
	/* ... */
	return *this;
}


一般情况下,如果类需要复制构造函数,它也会需要赋值操作符。几乎都是成对出现。

三法则:如果类需要析构函数,则,它也需要赋值操作符,和,复制构造函数。


析构函数

当对象的引用或指针超出作用域的时候,不会运行析构函数。
只有删除指向动态分配对象的指针 或者 实际对象(而不是对象的引用)超出作用域时,才会运行析构函数。

Sales_item *p = new Sales_item;
{
	// 调用复制构造函数,生成item
	Sales_item item(*p);
	delete p;	// p 所指向的 Sales_item 对象,调用析构函数
}	// 超出 item 所在作用域,item 这个 Sales_item 对象调用析构函数

{
	Sales_item *p = new Sales_item[10];
	vector<Sales_item> vec(p, p+10);
	delete [] p;	// 删除 p 指针,p 所指向的数组中的所有元素,都运行析构函数
}	// 超出 vec 作用域,vec 中所有元素,运行析构函数
// 容器中的元素,总是按照逆序进行撤销

合成析构函数

编译器总是会为我们合成一个析构函数。
该析构函数,会按照对象创建时的逆序,来撤销每个 非static 成员,
也就是说,它按照成员在类中的声明次序的逆序来撤销成员。
对于类类型的成员,合成析构函数,调用该成员的析构函数来撤销对象。

析构函数,是一个成员函数,其名字是,在类名之前加上一个代字号(~),没有返回值,没有形参。
由于,不能指定形参,所以,不能重载析构函数。也就是说,一个类只能指定一个析构函数

特别指出:即使编写我自己的析构函数,合成析构函数仍然运行。

class Sales_item
{
public:	

	// 撤销对象时,先运行自己的这个析构函数。
	// 然后,再执行合成析构函数,来撤销类的成员
	~Sales_item(){}
};


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值