C++—特殊成员函数
2015-11-10 20:30
363 查看
华电北风吹
天津大学认知计算与应用重点实验室
最后修改日期 2015/11/10
先看一个有问题的类定义:
类实现代码:
使用如下的测试函数,会发现好几个析构函数在释放的时候输出乱码(因为指针指向的内容已经被释放)。
会发现sb.str指针指向的内容已经被释放了。下面主要讨论一下怎么解决这个问题。
C++包含五种特殊成员函数,分别是默认构造函数,默认析构函数,复制构造函数,复制运算符,地址运算符。C++11还提供了移动构造函数和移动赋值元算符。
1、默认构造函数
如果类声明没有提供任何构造函数,C++将创建默认构造函数。默认的构造函数不接受任何参数,也不执行任何操作(创建对象时里面的值是未知的)。
如果定义了构造函数,C++将不会定义默认构造函数。这里需要注意如果希望创建对象时不显式对他进行初始化,则必须显式定义默认构造函数。
例如,若想使用下面的语句,必须在构造函数声明默认构造函数。
带参数的构造函数也可以使默认构造函数,但只能有一个默认的构造函数。例如下面的构造函数定义将会引发编译错误。
因为如果出现StringBad sb;将会与这两个默认构造函数都匹配。
2、默认析构函数
如果没有定义析构函数,C++默认定义一个不进行任何操作的析构函数。
3、复制构造函数
类的复制构造函数原型如下
有一下四种情况调用类的复制构造函数。假设motto是一个StringBad对象:
C++默认的复制构造函数逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。如果成员本身就是类对象,则将使用这个类的复制构造函数来复制类成员对象。所以如果类中包含了使用new初始化的指针成员的时候应当定义一个复制构造函数,以复制指针指向的数据,而不是指针,也就是所说的深度复制。而浅复制只是复制指针值。
因此,上面的StringBad类应该定义自己的如下复制构造函数
4、赋值运算符
进行上述修改后,可以解决大部分问题,运行程序发现,执行最后一个析构函数的时候,还是出现了指针指向内容被释放的情况。问题出在下面两行代码:
第一行执行无参数的默认构造函数。第二行执行默认的赋值运算符。有了上面的经验不难猜测出这里的问题是赋值运算符的隐含实现是对成员逐个复制。
只需要重载赋值运算符进行深度复制即可。
至此,已经讲一个StringBad类修复好了。
地址运算符、移动构造函数、移动赋值元算符部分等待后续补充。
天津大学认知计算与应用重点实验室
最后修改日期 2015/11/10
先看一个有问题的类定义:
#include <iostream> class StringBad { private: char* str; int len; static int strcount; public: StringBad(); StringBad(const char *s); ~StringBad(); friend std::ostream & operator<<(std::ostream & os, const StringBad & sb); };
类实现代码:
int StringBad::strcount = 0; StringBad::StringBad() { len = 4; str = new char[8]; strcpy(str, "nullStr"); ++strcount; cout << "StringBad " << strcount << ":" << str << " created." << endl; } StringBad::StringBad(const char *s) { len = strlen(s); str = new char[len+1]; strcpy(str, s); ++strcount; cout << "StringBad " << strcount << ":" << str << " created." << endl; } StringBad::~StringBad() { cout << "StringBad " << strcount << ":" << str << " deleted." << endl; --strcount; delete[] str; } ostream & operator<<(ostream & os, const StringBad & sb) { cout << sb.str; return os; }
使用如下的测试函数,会发现好几个析构函数在释放的时候输出乱码(因为指针指向的内容已经被释放)。
void callme1(StringBad & rsb) { cout << "String passed by reference:\n" << " \"" << rsb << "\"\n"; } void callme2(StringBad sb) { cout << "String passed by value:\n" << " \"" << sb << "\"\n"; } int main() { using std::endl; { cout << "Starting an inner block.\n"; StringBad headline1("China"); StringBad headline2("Japan"); StringBad sports("America"); cout << "headline1: " << headline1 << endl; cout << "headline2: " << headline2 << endl; cout << "sports: " << sports << endl; cout << endl; callme1(headline1); cout << "headline1: " << headline1 << endl; cout << endl; callme2(headline2); cout << "headline2: " << headline2 << endl; cout << endl; cout << "Initialize one object to another:\n"; StringBad sailor = sports; cout << "sailor: " << sailor << endl; cout << endl; cout << "Assign one object to another:\n"; StringBad knot; knot = headline1; cout << "knot: " << knot << endl; cout << endl; cout << "Exiting the block. Destruct function start to execute.\n"; cout << endl; } cout << "End of main()\n"; std::cin.get(); return 0; }
会发现sb.str指针指向的内容已经被释放了。下面主要讨论一下怎么解决这个问题。
C++包含五种特殊成员函数,分别是默认构造函数,默认析构函数,复制构造函数,复制运算符,地址运算符。C++11还提供了移动构造函数和移动赋值元算符。
1、默认构造函数
如果类声明没有提供任何构造函数,C++将创建默认构造函数。默认的构造函数不接受任何参数,也不执行任何操作(创建对象时里面的值是未知的)。
如果定义了构造函数,C++将不会定义默认构造函数。这里需要注意如果希望创建对象时不显式对他进行初始化,则必须显式定义默认构造函数。
例如,若想使用下面的语句,必须在构造函数声明默认构造函数。
StringBad knot; //必须有构造函数 StringBad()
带参数的构造函数也可以使默认构造函数,但只能有一个默认的构造函数。例如下面的构造函数定义将会引发编译错误。
StringBad(){}; StringBad(const char *s="second default construct function"){};
因为如果出现StringBad sb;将会与这两个默认构造函数都匹配。
2、默认析构函数
如果没有定义析构函数,C++默认定义一个不进行任何操作的析构函数。
3、复制构造函数
类的复制构造函数原型如下
Class_name(const Class_name &);
有一下四种情况调用类的复制构造函数。假设motto是一个StringBad对象:
StringBad ditto(motto); StringBad metoo=motto; StringBad also=StringBad(motto); StringBad *pStringBad=new StringBad(motto);
C++默认的复制构造函数逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。如果成员本身就是类对象,则将使用这个类的复制构造函数来复制类成员对象。所以如果类中包含了使用new初始化的指针成员的时候应当定义一个复制构造函数,以复制指针指向的数据,而不是指针,也就是所说的深度复制。而浅复制只是复制指针值。
因此,上面的StringBad类应该定义自己的如下复制构造函数
StringBad::StringBad(const StringBad & sb) { len = sb.len; str = new char[len + 1]; strcpy(str, sb.str); ++strcount; cout << "StringBad " << strcount << ":" << str << " created." << endl; }
4、赋值运算符
进行上述修改后,可以解决大部分问题,运行程序发现,执行最后一个析构函数的时候,还是出现了指针指向内容被释放的情况。问题出在下面两行代码:
StringBad knot; knot = headline1;
第一行执行无参数的默认构造函数。第二行执行默认的赋值运算符。有了上面的经验不难猜测出这里的问题是赋值运算符的隐含实现是对成员逐个复制。
只需要重载赋值运算符进行深度复制即可。
StringBad & StringBad::operator=(const StringBad &sb) { if (this == &sb) return *this; this->len = sb.len; delete[] this->str; this->str = new char[this->len + 1]; strcpy(this->str, sb.str); return *this; }
至此,已经讲一个StringBad类修复好了。
地址运算符、移动构造函数、移动赋值元算符部分等待后续补充。
相关文章推荐
- 由C++构造函数初始值列表想到的
- C++编程经验-返回局部变量的讨论
- c++primer第五版第十二章12.20习题用一个StrBlobPtr打印出StrBlob中的元素
- 操作系统中轮转法的模拟
- c++从入门到实践
- 题目1.2.4 The Seven Percent Solution(C++)
- Rational Rose 2003 逆向工程转换C++源代码成UML类图
- C语言学习——怪异的事情
- 在C语言中利用PCRE实现正则表达式
- 【c语言】 编写一个函数reverse_string(char * string)(递归实现)
- C++单链表实现冒泡排序
- C++中实数(以float为例)的存储方式
- C++链表翻转
- 黑马程序员--C语言基础--指针
- C++标准库中的list的实现原理
- 01-C++11的使用
- C语言基础-指针
- C++基础::类设计的几大原则
- C++中指针和引用的区别(超详细)
- C++内存管理学习堆和栈