赋值操作符、复制构造函数、析构函数、static成员练习

时间:2022-03-17 17:30:03
 /**
* 定义一个Employee类,包含雇员名字和一个唯一的雇员标识,为该类定义默认构造函数和参数为表示
* 雇员名字的string构造函数。如果该类需要复制构造函数或赋值操作符,实现这些函数
*
* 注意:static数据成员必须在类定义体的外部定义(正好一次),static成员不是通过类构造函数进行初始化
* 而是应该在定义时进行初始化。
* (保证对象正好定义一次的最好方法就是将static数据成员的定义放在包含类的非内联成员函数定义的文件中)
* static数据成员通常在定义时才初始化(例外:只要初始化式是一个常量表达式)
* const static数据成员就可以在类的定义体中进行初始化,但是,const static数据成员在类定义体中初始化时,
* 该数据成员扔必须在类的定义体之外进行定义。
*/
class Employee
{
public:
//构造函数
Employee():name("Noname"), id(counter)
{
++counter;
}
Employee(const std::string &nm):name(nm), id(counter)
{
++counter;
}
//复制构造函数
Employee(const Employee &other):name(other.name), id(counter)
{
++counter;
}
//赋值操作符
Employee& operator = (const Employee &other)
{
name = other.name;
return *this;
} private:
std::string name;
int id;
static int counter;
};
int Employee::counter = ;//另外需要在类外对static成员进行定义和初始化:

 复制构造函数可用于:

1、根据另一个同类型的对象显示或隐式初始化一个对象;

2、复制一个对象,将它作为实参传给一个函数;

3、从函数返回时复制一个对象;

4、初始化顺序容器中的元素;

例如:

 /*
可以用表示容量的单个形参初始化容器,这种方式使用了默认构造函数和复制构造函数
*/
vector<string> svec();
/*
编译器首先使用string函数的默认构造函数创建一个临时值来初始化svec,然后使用复制构造函数将临时值复制到svec的每个元素中。
*/

5、根据元素初始化式列表初始化数组元素。

例如:

 /*
如果没有为类类型的数组提供元素初始化式,则将用默认构造函数初始化每个元素。然而,如果使用了常规的花括号括住的数组初始化列表来提供显示元素初始化式,则使用复制初始化来初始化每个元素。根据指定值创建适当类型元素,然后用复制构造函数将该值复制到相应元素:
*/
sales_item primer_eds[] = { string("0-201-15487-6").
string("0-201-14387-8").
string("0-201-23487-3").
Sales_item()
} ;
/*
前三个元素的初始化式中可以直接指定一个值,用于调用元素类型的单实参构造函数(前提是该构造函数不能为 explicit 类型的),如果不希望指定实参或指定多个实参,就需要使用完整的构造函数语法,如最后一个元素的初始化那样。
*/

对于类类型对象:

直接初始化:直接调用与实参匹配的构造函数

复制初始化:总是调用复制构造函数,首先使用指定的构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象

一般而言,如果一个类拥有指针成员,或者在复制对象时有一些特殊工作要做,则该类需要复制构造函数

一般而言,如果一个类需要定义复制构造函数,则该类也需要定义赋值操作符

复制构造函数的形参并不限制为const,但必须是一个引用,解释这个限制的基本原理,例如,解释为什么下面的定义不能工作,

       Sales_item::Sales_item( const Sales_item rhs );

它不能工作的原因是:当形参为非引用类型时,将复制实参的值,给这个copy constructor,但是,每当以传值方式传递参数时,会导致调用复制构造函数,因此,如果要使用以传值方式传递参数的copy constructor,必须使用一个“不以传值方式传递参数”的copy constructor,否则就会导致copy constructor的无穷递归调用。这个“不以传值方式传递参数”的方法就是使用 形参是一个引用 的copy constructor,即以传地址的方式传递参数。

什么是析构函数?合成析构函数有什么用?什么时候会合成析构函数?什么时候一个类必须定义自己的析构函数?

析构函数是一个成员函数,它的名字与类的名字相同,在名字前加一个代字符~,没有返回值,没有形参(因此析构函数不能重载),用于类的对象超出作用域时释放对象所获取的资源,或删除指向动态分配对象的指针。

合成析构函数的作用:1,按对象创建时的逆序撤销每个非static成员,2,对于类类型的成员,合成析构函数调用该成员的析构函数来撤销对象。

编译器总会为每个类合成一个析构函数。当1,需要释放指针成员的资源时,2,需要执行某些特定工作时,必须自己定义析构函数。

析构函数与复制构造函数或赋值操作符之间的一个重要区别是,即使我们编写了自己的析构函数,合成析构函数仍然运行

 class Exmp1
{
public:
//默认构造函数
Exmp1()
{
std::cout << "Exmpl()" << std::endl;
}
//复制构造函数
Exmpl(const Exmpl&)
{
std::cout << "Exmpl(const Exmpl &)" << std::endl;
}
//赋值操作符
Exmpl& operator = (const Exmpl &rhe)
{
std::cout << "Exmpl& operator = (const Exmpl &) << std::endl";
reurn *this;
}
//析构函数
~Exmpl()
{
std::cout << "~Exmpl()" << std::endl;
}
};
void fun1(Exmpl obj) //形参为Exmpl对象
{
}
void fun2(Exmpl& obj) //形参为Exmpl对象引用
{
}
Exmpl fun3()
{
Exmpl obj;
return obj; // 返回Exmpl对象
} int main()
{
Exmpl eobj; //调用默认构造函数创建Exmpl对象eobj fun1(eobj); //调用复制构造函数--将形参Exmpl对象创建为实参Exmpl对象副本---函数执行完毕后调用析构函数撤销形参Exmpl对象 fun2(eobj); //形参为引用,无需调用复制构造函数传递实参 eobj = fun3();
/*调用默认构造函数创建局部Exmpl对象---函数返回时调用复制构造函数创建作为返回值副本的Exmpl对象
---调用析构函数撤销局部Exmpl---调用赋值操作符---调用析构函数撤销作为返回值副本的Exmpl对象 */ Exmpl *p = new Exmpl; // 调用默认构造函数动态创建Exmpl对象 std::vector<Exmpl> evec();
/*调用默认构造函数创建一个临时值Exmpl对象---然后三次调用复制构造函数将临时值Exmpl对象复制到vector容器
evec的每个元素---调用析构函数撤销临时值Exmpl对象*/ delete p; //调用析构函数撤销动态创建的Exmpl对象 return ; // evec及eobj生命周期结束,自动调用析构函数撤销---撤销evec需调用析构函数三次(因为其有三个元素,按照逆序撤销)
}

复制控制练习:

 1 class TreeNode
2 {
3 public:
4 TreeNode():count(0), left(0), right(0){}
5 TreeNode(const TreeNode&);
6 ~TreeNode();
7 private:
8 std::string value;
9 int count;
10 TreeNode *left;
11 TreeNode *right;
12 };
13
14 TreeNode::TreeNode(const TreeNode &orgi):value(orgi.value), count(orgi.count)
15 {
16 if(orgi.left)
17 left = new TreeNode(*orgi.left);
18 else
19 left = 0;
20 if(orgi.right)
21 right = new TreeNode(*orgi.right);
22 else
23 right = 0;
24 }
25 TreeNode::~TreeNode()
26 {
27 if(left)
28 delete left;
29 if(right)
30 delete right;
31 }