构造函数、析构函数、赋值函数是每一个类最基本的的函数。每一个类只有一个析构函数和一个赋值函数。可是有不少构造函数(一个为复制构造函数,其余为普通构造函数。对于一个类A,若是不编写上述四个函数,c++编译器将自动为A产生四个默认的函数,即:html
- A(void) //默认无参数构造函数
- A(const A &a) //默认复制构造函数
- ~A(void); //默认的析构函数
- A & operator = (const A &a); //默认的赋值函数
既然能自动生成函数,为何还须要自定义?缘由之一是“默认的复制构造函数”和"默认的赋值函数“均采用”位拷贝“而非”值拷贝“ios
位拷贝 v.s. 值拷贝c++
为便于说明,以自定义String类为例,先定义类,而不去实现。程序员
#include <iostream> using namespace std; class String { public: String(void); String(const String &other); ~String(void); String & operator =(const String &other); private:
char *m_data;
int val; };
位拷贝拷贝的是地址,而值拷贝拷贝的是内容。函数
若是定义两个String对象a, b。当利用位拷贝时,a=b,其中的a.val=b.val;可是a.m_data=b.m_data就错了:a.m_data和b.m_data指向同一个区域。这样出现问题:post
- a.m_data原来的内存区域未释放,形成内存泄露
- a.m_data和b.m_data指向同一块区域,任何一方改变,会影响到另外一方
- 当对象释放时,b.m_data会释放掉两次
所以this
当类中还有指针变量时,复制构造函数和赋值函数就隐含了错误。此时须要本身定义。url
结论spa
- 有一种特别常见的状况须要本身定义复制控制函数:类具备指针哈函数。
- 赋值操做符和复制构造函数能够当作一个单元,当须要其中一个时,咱们几乎也确定须要另外一个
- 三法则:若是类须要析构函数,则它也须要赋值操做符和复制构造函数
注意指针
- 若是没定义复制构造函数(别的无论),编译器会自动生成默认复制构造函数
- 若是定义了其余构造函数(包括复制构造函数),编译器毫不会生成默认构造函数
- 即便本身写了析构函数,编译器也会自动生成默认析构函数
所以此时若是写String s是错误的,由于定义了其余构造函数,就不会自动生成无参默认构造函数。
复制构造函数 v.s. 赋值函数
#include <iostream> #include <cstring> using namespace std; class String { public: String(const char *str); String(const String &other); String & operator=(const String &other); ~String(void); private: char *m_data; }; String::String(const char *str) { cout << "自定义构造函数" << endl; if (str == NULL) { m_data = new char[1]; *m_data = '\0'; } else { int length = strlen(str); m_data = new char[length + 1]; strcpy(m_data, str); } } String::String(const String &other) { cout << "自定义拷贝构造函数" << endl; int length = strlen(other.m_data); m_data = new char[length + 1]; strcpy(m_data, other.m_data); } String & String::operator=(const String &other) { cout << "自定义赋值函数" << endl; if (this == &other) { return *this; } else { delete [] m_data; int length = strlen(other.m_data); m_data = new char[length + 1]; strcpy(m_data, other.m_data); return *this; } } String::~String(void) { cout << "自定义析构函数" << endl; delete [] m_data; } int main() { cout << "a(\"abc\")" << endl; String a("abc"); cout << "b(\"cde\")" << endl; String b("cde"); cout << " d = a" << endl; String d = a; cout << "c(b)" << endl; String c(b); cout << "c = a" << endl; c = a; cout << endl; }
执行结果
说明几点
1. 赋值函数中,上来比较 this == &other 是很必要的,由于防止自复制,这是很危险的,由于下面有delete []m_data,若是提早把m_data给释放了,指针已成野指针,再赋值就错了
2. 赋值函数中,接着要释放掉m_data,不然就没机会了(下边又有新指向了)
3. 拷贝构造函数是对象被建立时调用,赋值函数只能被已经存在了的对象调用
注意:String a("hello"); String b("world"); 调用自定义构造函数
String c=a;调用拷贝构造函数,由于c一开始不存在,最好写成String c(a);
C++赋值运算符函数
为类添加赋值运算符函数:
类型定义
class CMyString { public: CMyString(char *pData = NULL); CMyString(const CMyString &str); ~CMyString(void); CMyString &operator=(const CMyString &); private: char *m_pData; };
要点:
一、返回值类型为该类型的引用,并在函数结束前返回实例自身的引用(即 *this);
二、是否把传入的参数声明为常量引用【const CmyString &str】;
三、是否释放自身已有内存,不然会形成“内存泄漏”;
四、是否判断参数与当前示例是指向的同一个对象;
解法:
/* 适用于初级C++程序员的解法 */ CMyString &CMyString::operator=(const CMyString &str) { //首先检测两个指针是否指向同一个对象 if (this == &str) return *this; //释放原内存 delete []m_pData; m_pData = NULL; //从新申请内存 m_pData = new char[strlen(str.m_pData) + 1]; strcpy(m_pData,str.m_pData); //谨记:返回*this return *this; } /* 适用于高级C++程序员的解法 */ CMyString &CMyString::operator=(const CMyString &str) { if (this != &str) //确保不指向同一个实例 { CMyString strTemp(str); char *pTemp = strTemp.m_pData; //指针指向须要更换的对象 //strTemp.m_pData指向原来的对象,确保内存不足时能够找到原来对象的值 strTemp.m_pData = m_pData; m_pData = pTemp; //更换原对象的值 } //自动调用strTemp的析构函数,销毁strTemp对象并回收pTemp的内存 return *this; }