本博文我们讨论OOP复制控制的一些内容;
首先考虑对象复制的时机: 非引用类型
1):根据一个类去显式或者隐式初始化一个对象;
2):复制一个对象,将它作为实参传给一个函数;
3):从函数返回时复制一个对象。( string tolittle(string word))
4):初始化顺序容器中的元素。(例如vector 必须具备 copy,assignment功能)
一个空类,编译器提供默认无参数构造函数、拷贝构造函数、赋值运算符以及析构函数,一共四个函数。(面试)!!
11.复制构造函数、赋值运算符以及析构函数,称为三法则,一旦提供了其中一个,务必提供其余两个。以String为例: a) 涉及到深拷贝、浅拷贝问题,所以需要提供拷贝构造函数 b) 然后,为了保持一致,赋值运算符也应该实现深拷贝 c) 既然实现深拷贝,那么必定申请了资源(例如内存),所以必然需要析构函数来手工释放。
一:复制构造函数:
复制构造函数调用的时机就是在对象复制的时候。方式有两种:
1):当用户不提供时,编译器自动为我们合成一个拷贝构造函数;
示例代码如下:
#include <iostream> #include <string> using namespace std; class Student { public: Student() {} Student(int id, const string&name, int age) :id_(id),name_(name),age_(age) {} void print() { cout << id_ <<":" << name_ <<":" << age_ << endl; } private: int id_; string name_; int age_; }; int main(int argc, const char *argv[]) { Student s(11,"zhangsan",23); s.print(); Student s2(s); //执行此句,调用默认复制构造函数 s2.print(); return 0; }
2):用户自己定义:
放入class Student 的public中即可;
1 Student(const Student &s) 2 { 3 id_ = s.id_; 4 name_ = s.name_; 5 age_ = s.age_; 6 }
复制构造函数之 深copy 和 浅copy;
含有指针成员变量的类在复制时,有两种选择:
a) 复制指针的值,这样复制完毕后,两个对象指向同一块资源,这叫做 浅拷贝shallow copy(有可能出错)
b) 复制指针所指向的资源,复制完毕后,两个对象各自拥有自己的资源,这叫做 深拷贝 deep copy
示例代码:
1 #include "iostream" 2 #include "string.h" 3 using namespace std; 4 5 //copy String 时,仅仅复制指针的值 6 //导致析构时发生了问题 7 class String 8 { 9 public: 10 String(); 11 String(const char*s); 12 String(const String &s); 13 ~String(); 14 void print()const 15 { 16 cout << str_ << endl; 17 } 18 private: 19 char *str_; 20 }; 21 String::String() 22 :str_(new char(0)) 23 { } 24 String::String(const char *s) 25 :str_(new char[strlen(s)] +1) 26 { 27 ::strcpy(str_, s); 28 } 29 //程序结束时会调用析构函数,两个对象s1,s2必然会析构两次,而str_指向同一片区域; 30 //当析构s1时,s1.str_所指向的区域被释放;而s2.str_ 也同时被释放 31 //当析构s2时,由于s2.str_已经被s1.str_释放,所以会产生错误. 32 String::String(const String &s) 33 :str_(s.str_) 34 {} 35 36 /* 修改成以下即可: 37 *String::String(const String &s) 38 * :str_(new char[strlen(s)] +1) 39 * { 40 * ::strcpy(str_, s.str_); 41 * } 42 */ 43 String::~String() 44 { 45 delete[] str_; //注意 46 } 47 int main(int argc, const char *argv[]) 48 { 49 String s("helloworld"); 50 s.print(); 51 52 String s2(s); 53 s.print(); 54 return 0; 55 }
二: 赋值运算符的重载:【因为我们自定义的类没有内置比较运算符(<、>、!=)】
示例代码如下:
1 #include <iostream> 2 #include <string.h> 3 using namespace std; 4 5 class String 6 { 7 public: 8 String(); 9 String(const char *s); 10 String(const String &s); 11 ~String(); 12 String &operator = (const String &s); 13 size_t size()const //自己构造size函数 14 { 15 return strlen(str_); 16 } 17 18 void print() 19 { cout << str_ << endl; } 20 private: 21 char *str_; 22 }; 23 String::String() 24 :str_(new char[1]) 25 { *str_ = 0; } 26 27 String::String(const char*s) 28 :str_(new char(strlen(s)+1)) 29 { ::strcpy(str_, s); } 30 31 String::String(const String &s) 32 :str_(new char(strlen(s.str_)+1)) 33 { ::strcpy(str_, s.str_); } 34 35 String &String::operator = (const String &s) 36 { 37 if(this == &s)//自身赋值 38 return *this; 39 delete[]str_; 40 str_ = new char(s.size() + 1 ); 41 ::strcpy(str_, s.str_); 42 43 return *this; 44 } 45 46 String::~String() 47 { delete[] str_; } 48 49 int main(int argc, const char *argv[]) 50 { 51 String s("helle"); 52 s.print(); 53 54 String s2; 55 s2 = s; //赋值重载 56 s2.print(); 57 58 s2 = s2 ; //注意自身赋值情形 59 s2.print(); 60 61 return 0; 62 }
禁止类复制和赋值的方法:
a):把 copy构造函数和复制运算符设为private;
b):只声明,不实现;
1 #include <iostream> 2 #include <string> 3 #include <vector> 4 using namespace std; 5 6 //no copy,no assignment 7 //google 推荐写法 8 #define DISALLOW_COPY_AND_ASSIGN(TypeName) 9 TypeName(const TypeName&); 10 void operator=(const TypeName&) 11 12 class Test 13 { 14 public: 15 Test(){} 16 ~Test(){} 17 18 private: 19 Test(const Test&t); 20 void operator=(const Test &t); 21 }; 22 23 int main(int argc, const char *argv[]) 24 { 25 Test t; 26 27 Test t2(t);//error 28 29 Test t3; 30 t3 = t; //error 31 32 return 0; 33 }
a):如果一个类,不需要复制和赋值,那就禁用这种能力,这可以帮助避免大量潜在的bug。 b):如果一个类,实现了像value一样的复制和赋值能力(意味着复制和赋值后, 两个对象没有任何关联,或者逻辑上看起来无任何关联),那么就称这个类的对象为 值语义(value semantics)。 如果类不能复制,或者复制后对象之间的资源归属纠缠不清,那么称为 对象语义(object semantics),或者 引用语义(reference semantics)。