1.默认拷贝构造函数
1.1 编译器自动为我们合成一个拷贝构造函数。A(const A &).
1.2 对象复制的时机:
a) 显式复制。
b) 使用对象做形参
c) 使用对象做返回值
d) 往容器中放入对象
#include <iostream> #include <string> #include <vector> using namespace std; /* *用一个对象初始化另一个对象时 需要调用拷贝构造函数 *这里 编译器 自动提供了一个默认的拷贝构造函数 */ class Student{ public: Student(); void setStudent(const string &name, int age, int score); void print() const; private: string name_; int age_; int score_; }; Student::Student() :name_(""), age_(0), score_(0) {} void Student::setStudent(const string &name, int age, int score){ name_ = name; age_ = age; score_ = score; } void Student::print()const{ cout << name_ << " " << age_ << " " << score_ << endl; } int main(int argc, const char *argv[]) { Student s; s.setStudent("monica", 22, 99); Student s2(s); //此时需要调用拷贝构造函数 s2.print(); Student s3("copy", 33, 89); return 0; }
1.3 拷贝构造函数不能为A(A a)的形式,因为A a这种形参能够触发拷贝语义(copy semantics),而拷贝构造函数本身是为了解决拷贝语义而存在的,所以它自身要避免拷贝。
2.深拷贝和浅拷贝
2.1 概念:浅拷贝和深拷贝是对类中持有指针而言,如果对象复制的时候,仅仅去拷贝指针的值,这称为浅拷贝(shallow copy),如果不是去拷贝指针的值,而是去拷贝指针指向的内存空间,称为深拷贝(deep copy)。
2.2 浅拷贝的例子 ,自己实现一个 string类,并用一个对象去初始化另一个对象,这里没有写拷贝构造函数,因此编译器会自动生成默认的拷贝构造函数,但是请注意,这里的string 成员变量是用指针实现的,默认的拷贝构造函数只复制了指针,因此两个对象将指向同一个内存区域,这样,在调用析构函数的时候,同一片内存就会delete两次,这就造成了错误。
#include <iostream> #include <string.h> using namespace std; /* * 自定义string类 调用默认构造函数时, * 仅仅复制了str的值,复制后,两个string对象 * 指向同一块内存,在析构的时候 该内存被delete * 两次 因此出错 */ class String{ public: String(); String(const char* str); ~String(); void debug() const; private: char *str_; }; String::String() :str_ (new char[1]) { str_[0] = 0; } String::String(const char* str) //用C 字符串初始化一个string 对象 :str_(new char[strlen(str) + 1]); { strcpy(str_, str); } String::~String(){ delete[] str_; } void String::debug()const{ cout << str_ << endl; } int main(int argc, const char *argv[]) { String s1("apple"); s1.debug(); String s2(s1); s2.debug(); return 0; }
2.3 深拷贝的例子,还是上例,定义自己的拷贝构造函数,在初始化另一个对象时,为对象分配新的内存空间,并且将值拷贝过去。这里在执行完深拷贝之后,两个对象之间没有任何关联。
#include <iostream> #include <string.h> using namespace std; /* * 深拷贝 */ class String{ public: String(); String(const char* str); String(const String &other); //自定义 拷贝构造函数 ~String(); void debug() const; private: char *str_; }; String::String() :str_ (new char[1]) { str_[0] = 0; } String::String(const char* str) //用C 字符串初始化一个string 对象 :str_(new char[strlen(str) + 1]) { strcpy(str_, str); } String::String(const String &other) :str_(new char[strlen(other.str_) + 1]) { strcpy(str_, other.str_); } String::~String(){ delete[] str_; } void String::debug()const{ cout << str_ << endl; } int main(int argc, const char *argv[]) { String s1("apple"); s1.debug(); String s2(s1); s2.debug(); return 0; }
3. 赋值运算
3.1 对象的赋值,调用的是类的赋值运算符。编译器自动为我们合成一个赋值运算符。这里我为string类编写一个复制运算符函数,但是没有考虑自身赋值的问题。
#include <iostream> #include <string.h> using namespace std; /* * 编译器会自动为我们合成一个赋值运算符 * 也可以自定义一个 赋值运算符 */ class String{ public: String(); String(const char* str); String(const String &other); String &operator= (const String &other); // 自定义赋值运算符 ~String(); void debug() const; private: char *str_; }; String::String() :str_ (new char[1]) { str_[0] = 0; } String::String(const char* str) :str_(new char[strlen(str) + 1]) { strcpy(str_, str); } String::String(const String &other) :str_(new char[strlen(other.str_) + 1]) { cout << " call copy construction " << endl; strcpy(str_, other.str_); } String& String::operator=(const String &other){ cout << "call operator= func " << endl; delete[] str_; str_ = new char[strlen(other.str_) + 1]; strcpy(str_, other.str_); return *this; } String::~String(){ delete[] str_; } void String::debug()const{ cout << str_ << endl; } int main(int argc, const char *argv[]) { String s1("apple"); //调用第二个构造函数 s1.debug(); String s2(s1); // 调用拷贝构造函数 s2.debug(); String s3 ;//调用自定义的赋值运算符 s3 = s1; s3.debug(); return 0; }
3.2 编写赋值运算符和拷贝构造函数的区别:
a) 赋值运算符可能需要先释放资源 ;
b) 赋值预算符需要考虑自身赋值问题。
#include <iostream> #include <string.h> using namespace std; /* * 编写赋值运算符 必须考虑 自身赋值的问题 * 并且 返回值必须是自身的引用 */ class String{ public: String(); String(const char* str); String(const String &other); String &operator= (const String &other); // 自定义赋值运算符 ~String(); void debug() const; private: char *str_; }; String::String() :str_ (new char[1]) { str_[0] = 0; } String::String(const char* str) :str_(new char[strlen(str) + 1]) { strcpy(str_, str); } String::String(const String &other) :str_(new char[strlen(other.str_) + 1]) { cout << " call copy construction " << endl; strcpy(str_, other.str_); } String& String::operator=(const String &other){ cout << "call operator= func " << endl; if(&other != this){ //这里比较用的是指针 而不是那对象本身直接去比较 delete[] str_; str_ = new char[strlen(other.str_) + 1]; strcpy(str_, other.str_); } return *this; } String::~String(){ delete[] str_; } void String::debug()const{ cout << str_ << endl; } int main(int argc, const char *argv[]) { String s1("apple"); //调用第二个构造函数 s1.debug(); String s3 ;//调用自定义的赋值运算符 s3 = s1; s3.debug(); s3 = s3; s3.debug(); return 0; }
3.3 总结
3.3.1 编写赋值预算符的准则:
a) 必须处理自身赋值问题;
b) 必须返回自身引用。
3.3.2 三法则:拷贝构造函数、赋值运算符、析构函数,三者都与类内部的指针密切相关。当需要编写其中一个时,一般需要编写其他两个。
3.3.3 当需要禁止一个类进行复制或者赋值时,只需将类的拷贝构造函数和赋值运算符设为私有,而且只提供声明(注意设为私有后 该类的友元还可以访问,因此要只提供声明,这样友元类在要调用这两个函数时,会在链接时导致错误)。根据谷歌编程规范(http://zh-google-styleguide.readthedocs.org/en/latest/google-cpp-styleguide/contents/),可以将其定义为宏。
#include <iostream> #include <string> #include <vector> using namespace std; /* * 禁止 拷贝和赋值 * 将对函数设为私有 */ class Student{ public: Student(); void setStudent(const string &name, int age, int score); void print() const; private: Student(const Student &other); Student &operator= (const Student &other); string name_; int age_; int score_; }; Student::Student() :name_(""), age_(0), score_(0) {} void Student::setStudent(const string &name, int age, int score){ name_ = name; age_ = age; score_ = score; } void Student::print()const{ cout << name_ << " " << age_ << " " << score_ << endl; } int main(int argc, const char *argv[]) { Student s; s.setStudent("monica", 22, 99); Student s2(s); s2.print(); return 0; }
该宏定义如下:
#define DISALLOW_COPY_AND_ASSIGN(TypeName) TypeName(const TypeName&); void operator=(const TypeName&)