c++ primer plus 学习笔记(1)——复制构造函数与赋值运算符
2016-09-12 22:20
399 查看
来源:c++ primer plus (6th edition) page 433
注:以下内容用到书中一个自己设计的类StringBad,由成员变量 str、len与静态成员变量 num_strings 组成。
复制构造函数
对于复制构造函数,需要知道两点:何时调用和有何功能
何时调用
新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。这在很多情况下都可能发生,最常见的情况是将新对象显式地初始化为现有的对象。例如,假设motto是一个StringBad对象,则下面四种声明都将调用复制构造函数:
其中中间两种声明可能会使用复制构造函数直接创造metoo和also,也可能使用复制构造函数先创建临时对象,这取决于具体的实现。最后一种声明使用motto初始化一个匿名StringBad对象,并将其地址赋给pString指针。
每当程序生成了对象副本时,编译器都将使用复制构造函数。具体的说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。编译器生成临时对象时,也将使用复制构造函数:例如将三个Vector(向量)相加时,编译器可能生成临时的Vector对象保存中间结果。何时生成临时对象随编译器而异,但无论哪种编译器,当按值传递和返回对象时,都将调用复制构造函数。(因此应该按引用传递对象,来节省调用构造函数的时间和存储新对象的空间。)
2.默认复制构造函数的功能
默认的复制构造函数逐个复制每个非静态成员(成员复制也叫浅复制),复制的是成员的值。例如:
与下面的代码等效(只是由于私有成员无法访问,因此这些代码不能通过编译)
如果成员本身就是类对象,则将使用这个类的复制构造函数复制成员对象。静态函数(如num_strings)不受影响,因为它们属于整个类,而不是各个对象。
我们发现,浅复制的结果是,如果成员str是一个指向字符串的指针,复制的结果仅仅是将地址做了备份,原始字符串还是只有一份。更令人担忧的是,复制产生的临时变量如果调用了析构函数,就会将原始数据删除,从而产生错误。
解决办法是采用显式的复制构造函数,并且在其中采用深复制。例如:
赋值运算符
赋值运算符的功能及何时使用它
将已有的对象赋值给另一个对象时,将使用重载的赋值运算符:
初始化对象时,并不一定要用赋值运算符:
如果是直接初始化,就只用到复制构造函数;如果是先创建临时对象,再传给metoo,也会用到赋值运算符。
2.会出现的问题
与复制构造函数相似,由于浅复制,将headline1赋值给knot后,如果knot调用了析构函数,就会将headline1保存的字符串内容抹掉。
3.解决赋值问题
由于目标对象可能引用了以前分配的数据,所以函数应使用delete[] 来释放这些数据。
函数应该避免将对象赋值给自身;否则,在对象重新赋值前,释放内存操作可能删除对象的内容。
函数返回一个指向调用对象的引用。
进行深复制。
下面的代码说明了如何为StringBad类编写赋值运算符:
首先检查自我复制,这是通过检查赋值运算符右边的地址(&st)是否与接收对象(this)的地址相同来完成的。如果相同,程序将返回*this,然后结束。
如果不同,进行深复制。
注:以下内容用到书中一个自己设计的类StringBad,由成员变量 str、len与静态成员变量 num_strings 组成。
复制构造函数
复制构造函数用于将一个对象复制到新创建的对象中。 也就是说,它用于初始化过程中(包括按值传递参数),而不是常规的赋值过程中。类的复制构造函数原型通常如下:
Class_name(const Class_name&);
它接受一个指向类对象的常量引用作为参数。
对于复制构造函数,需要知道两点:何时调用和有何功能
何时调用
新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。这在很多情况下都可能发生,最常见的情况是将新对象显式地初始化为现有的对象。例如,假设motto是一个StringBad对象,则下面四种声明都将调用复制构造函数:
StringBad ditto(motto); // calls StringBad(const StringBad&); StringBad metoo = motto; // calls StringBad(const StringBad&); StringBad also = StringBad(motto); // calls StringBad(const StringBad&); StringBad* pStringBad = new StringBad(motto); // calls StringBad(const StringBad&);
其中中间两种声明可能会使用复制构造函数直接创造metoo和also,也可能使用复制构造函数先创建临时对象,这取决于具体的实现。最后一种声明使用motto初始化一个匿名StringBad对象,并将其地址赋给pString指针。
每当程序生成了对象副本时,编译器都将使用复制构造函数。具体的说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。编译器生成临时对象时,也将使用复制构造函数:例如将三个Vector(向量)相加时,编译器可能生成临时的Vector对象保存中间结果。何时生成临时对象随编译器而异,但无论哪种编译器,当按值传递和返回对象时,都将调用复制构造函数。(因此应该按引用传递对象,来节省调用构造函数的时间和存储新对象的空间。)
2.默认复制构造函数的功能
默认的复制构造函数逐个复制每个非静态成员(成员复制也叫浅复制),复制的是成员的值。例如:
StringBad sailor = sports;
与下面的代码等效(只是由于私有成员无法访问,因此这些代码不能通过编译)
StringBad sailor; sailor.str = sports.str; sailor.len = sports.len;
如果成员本身就是类对象,则将使用这个类的复制构造函数复制成员对象。静态函数(如num_strings)不受影响,因为它们属于整个类,而不是各个对象。
我们发现,浅复制的结果是,如果成员str是一个指向字符串的指针,复制的结果仅仅是将地址做了备份,原始字符串还是只有一份。更令人担忧的是,复制产生的临时变量如果调用了析构函数,就会将原始数据删除,从而产生错误。
解决办法是采用显式的复制构造函数,并且在其中采用深复制。例如:
StringBad::StringBad(const StringBad& st) { num_strings++; len = st.len; str = new char [len+1]; std::strcpy(str, st.str); **//copy string to new location** cout << num_strings << ":\"" << str << "\" object created\n"; }
赋值运算符
同样的问题可能不止出在默认复制构造函数上,还有默认赋值运算符。ANSI C 允许结构赋值,而C++允许类对象赋值,这是通过自动为类重载赋值运算符实现的。这种运算符的原型如下:
Class_name& Class_name::operator=(const Class_name&); 它接受并返回一个指向类对象的引用。例如 StringBad& StringBad::operator=(const StringBad&);
赋值运算符的功能及何时使用它
将已有的对象赋值给另一个对象时,将使用重载的赋值运算符:
StringBad headline1("Cerely Stalks at Midnight"); StringBad knot; knot = headline1; //assignment operator invoked
初始化对象时,并不一定要用赋值运算符:
StringBad metoo = knot; //use copy constructor, possibly assignment,too
如果是直接初始化,就只用到复制构造函数;如果是先创建临时对象,再传给metoo,也会用到赋值运算符。
与复制构造函数相似,赋值运算符的隐式(默认)实现也是对成员进行逐个复制。(静态数据成员不受影响)
2.会出现的问题
与复制构造函数相似,由于浅复制,将headline1赋值给knot后,如果knot调用了析构函数,就会将headline1保存的字符串内容抹掉。
3.解决赋值问题
由于目标对象可能引用了以前分配的数据,所以函数应使用delete[] 来释放这些数据。
函数应该避免将对象赋值给自身;否则,在对象重新赋值前,释放内存操作可能删除对象的内容。
函数返回一个指向调用对象的引用。
进行深复制。
下面的代码说明了如何为StringBad类编写赋值运算符:
StringBad& StringBad::operator=(const StringBad& st) { if (this==&st) //object assigned to itself return *this; //all done delete [] str; //free old string len = st.len; str = new char [len+1]; std::strcpy(str, st.str); return *this; //return reference to the invoking object }
首先检查自我复制,这是通过检查赋值运算符右边的地址(&st)是否与接收对象(this)的地址相同来完成的。如果相同,程序将返回*this,然后结束。
注:赋值运算符是只能由类成员函数重载的运算符之一。
如果不同,进行深复制。
相关文章推荐
- [学习笔记][C++Primer Plus]String类的使用
- [学习笔记][C++Primer Plus]使用cout格式化输出字符串,3q xuzhong
- 2012/1/21 《C++ Primer Plus》第八章:函数探幽 学习笔记
- 2012/2/7 《C++ Primer Plus》第十六章:string类和标准模板库 学习笔记
- 2012/1/11 《C++ Primer Plus》第四章:复合类型 学习笔记
- 2012/1/25 《C++ Primer Plus》第十一章:使用类 学习笔记
- 2012/1/27 《C++ Primer Plus》第十二章:类和动态内存分配 学习笔记
- 2012/1/25 《C++ Primer Plus》第十一章:使用类 学习笔记
- 2012/1/31 《C++ Primer Plus》第十三章:类继承 学习笔记
- [学习笔记][C++Primer Plus]使用cout格式化输出字符串
- 2012/1/8 《C++ Primer Plus》第二章:开始学习C++ 学习笔记
- 2012/1/13 《C++ Primer Plus》第五章:循环和表达式 学习笔记
- 2012/1/22 《C++ Primer Plus》第十章:对象和类 学习笔记
- 2012/1/9 《C++ Primer Plus》第三章:处理数据 学习笔记
- 2012/1/14 《C++ Primer Plus》第六章:分支语句和逻辑操作符 学习笔记
- 2012/1/13 《C++ Primer Plus》第五章:循环和表达式 学习笔记
- 2012/1/11 《C++ Primer Plus》第四章:复合类型 学习笔记
- 2012/2/7 《C++ Primer Plus》第十六章:string类和标准模板库 学习笔记
- 2012/1/31 《C++ Primer Plus》第十三章:类继承 学习笔记
- 2012/1/21 《C++ Primer Plus》第八章:函数探幽 学习笔记