类有一些特殊的函数,在我们不显式定义的情况下,编译器会自动生成。主要包括以下函数:
- 默认构造函数
- 默认析构函数
- 拷贝(复制)构造函数
- 赋值运算符(=)
- 地址运算符(&)
#include <iostream> using namespace std; class A { public: A() // 自实现的默认构造函数 { this->a = 100; cout << "A()" << endl; } A(int a) // 普通构造函数 { this->a = a; cout << "A(int a)" << endl; } A(const A &a) // 拷贝(复制)构造函数 { this->a = a.a; cout << "A(const A &a)" << endl; } A &operator=(const A &a) // 赋值运算符(重载=,需要返回引用) { this->a = a.a; cout << "A &operator=(A a)" << endl; return *this; } ~A() { cout << "~A()" << endl; } public: int GetA() { return this->a; } private: int a; }; int main() { // 默认构造函数 会触发析构 A a; cout << a.GetA() << endl; // 调用普通带参构造函数 会触发析构 A b = A(200); cout << b.GetA() << endl; // 赋值运算符 不会触发析构 b = a; cout << b.GetA() << endl; // 调用拷贝构造 会触发析构 A c = a; cout << c.GetA() << endl; return 0; }
对于编译器来说,在对象进行按值传递的时候,如函数传参或函数返回对象时,总会调用一次拷贝构造函数。
也就是说,按值传递对象会创建对象的一个副本,程序就会多进行一次构造和析构函数的调用。
如上的例子,实现赋值运算符时,传参类型是引用,返回值也是引用。这就省去了两次拷贝构造和两次析构的调用。
因此,在我们编写程序的时候,对象传参或返回对象时,最好能够进行引用传递,如果不希望修改这个对象被修改,可以加上const。
另外,默认拷贝构造函数和赋值运算符进行的都是浅拷贝(值拷贝),而非深拷贝(内容拷贝)。看下面的例子:
#include <iostream> #include <cstring> using namespace std; class A { public: A() { a = 100; p = new char[100]; const char *s = "hello C++"; memcpy(p, s, strlen(s) + 1); } ~A() { delete []p; p = NULL; }
int a; char *p; }; int main() { A a; A b = a; cout << a.p << endl; cout << b.p << endl; // 修改a.p的值,期望是不会影响b.p memcpy(a.p, "hello Java", strlen("hello Java") + 1); cout << a.p << endl; cout << b.p << endl; // 代码最终会异常退出,double free or corruption // 因为b.p的值与a.p的值是一样的 // a,b分别对同一块内存进行释放了两次 return 0; }
一般来说,此时需要自己实现拷贝构造函数、赋值运算符、析构函数。
#include <iostream> #include <cstring> using namespace std; const char *cpp = "hello C++"; const char *java = "hello Java"; class A { public: A() { len = 100; p = new char[len]; int iLen = len > strlen(cpp) + 1 ? strlen(cpp) + 1 : len; memcpy(p, cpp, iLen); } ~A() { delete []p; p = NULL; } A(const A &a) { this->len = a.len; this->p = new char[a.len]; int iLen = this->len > strlen(a.p) + 1 ? strlen(a.p) + 1 : this->len; memcpy(this->p, a.p, iLen); } A& operator =(const A &a) { if (this == &a)// 避免自我赋值 { return *this; } delete []this->p; // 删除原先的内容,否则造成内存泄露,这里固定 this->len = a.len; this->p = new char[a.len]; int iLen = this->len > strlen(a.p) + 1 ? strlen(a.p) + 1 : this->len; memcpy(this->p, a.p, iLen); return *this; } int len; char *p; }; int main() { A a; A b = a; cout << a.p << endl; cout << b.p << endl; // 修改a.p的值,期望是不会影响b.p memcpy(a.p, java, strlen(java) + 1); cout << a.p << endl; cout << b.p << endl; b = a; cout << a.p << endl; cout << b.p << endl; // 修改a.p的值,期望是不会影响b.p memcpy(a.p, cpp, strlen(cpp) + 1); cout << a.p << endl; cout << b.p << endl; return 0; }
以上实现赋值运算符时,有三点需要注意:
- 传参 const typename &, 返回typename &
- 避免自身赋值 先判断this与传入参数的地址是否是同个对象
- 先释放之前动态内存,避免内存泄露