# 构造析构/拷贝构造/拷贝赋值


## ***三个特殊函数***

```cpp
class String {
public:
  String(const char* cstr = 0);  // 构造函数
  String(const String &str);  // 他是接受自己的东西 所以这是一个拷贝构造 
  String& operator = (const String& str);  // 操作符重载 =右手边也是自己的东西 所以这是拷贝赋值 
  ~String();  // 析构函数 当这个类死亡的时候 就会调用 析构函数

  char* get_c_str() const { return m_data; }
private:
  char* m_data
};
```

## ***构造函数和析构函数***

1. *代码演示*
   
   - ```cpp
     inline String::String(const char* cstr = 0) {
       if (cstr) {
         m_data = new char[strlen(cstr)+1];
         strcpy(m_data, cstr);
       } else {
         m_data = new char[1];
         *m_data = '\0';
       }
     }  // c里面字符串是以 '\0' 为结束符号 这是构造函数
     
     inline String::~String() {
       delete[] m_data;
     }  // 析构函数 因为上述构造函数 为str分配了一个内存 所以要释放内存 要不然会内存泄漏
     
     // class 有指针 多半要做动态分配 所以就要在析构函数 释放内存
     ```

2. *构造函数析构函数里能不能抛出异常*
   
   - *C++只会析构已经完成的对象，对象只有其构造函数执行完毕才算是完成，在构造函数中发生异常，控制权转交给析构函数之外。因此，在对象b的构造函数中发生异常，并不会调用对象b的析构函数，所以会造成内存泄漏*
     
     *用 auto_ptr 对象来取代指针类成员，便对构造函数做了强化，免除了抛出异常时发生资源泄 漏的危机，不再需要在析构函数中手动释放资源*
     
     *如果控制权基于异常的因素离开析构函数，而此时正有另一个异常处于作用状态，C++ 会调 用 terminate 函数让程序结束*
   
   - *如果异常从析构函数抛出，而且没有在当地进行捕捉，那个析构函数便是执行不全的。如果析构函数执行不全，就是没有完全他应该做的每一件事*

## ***深拷贝与浅拷贝***

1. *什么是浅拷贝*
   
   - *拷贝后，两个对象指向同一个内存地址，如果修改了其一个，另一个也会受到影响*
     
     ```cpp
     String a("Hello"); // 这个时候 a 的 data 指向了 'Hello\0' 的地址
     String b("World"); // 这个时候 b 的 data 指向了 ‘World\0’ 的地址
     b = a; // 这个时候 b 就会指向 'Hello\0' 的地址 a&b都指向了同一个地址 可是 'World\0' 还在  造成内存泄漏 而且你改a b就会受到影响 所以这种 'b = a' 叫做浅拷贝
     ```
   
   - *出现类的等号赋值时，会调用拷⻉函数，在未定义显示拷⻉构造函数的情况下， 系统会调 用默认的拷⻉函数-即浅拷⻉，它能够完成成员的一一复制。当数据成员中没有指针时，浅拷⻉是可行的，但当数据成员中有指针时，如果采用简单的浅拷⻉，则两类中的两个指针指向同一个地址，当对象快要结束时，会调用两次析构函数，而导致野指针的问题*

2. *什么是深拷贝*
   
   - *拷贝后，两个对象的值相同，但是不是同一个内存地址*

3. *简而言之，当数据成员中有指针时，必需要用深拷⻉更加安全*

## ***拷贝构造函数***

1. *什么是拷贝构造函数及实现*
   
   - ```cpp
     inline String::String(const String& str) { // 为什么叫构造 因为这是一个构造函数 为什么叫拷贝 因为传参是自己
       m_data = new char[strlen(str.m_data)+1]; // 直接取另一个object的private data
       strcpy(m_data, str.m_data);
     }   // 这也是深拷贝
     ```

2. *什么情况会调用拷贝构造函数*
   
   - *一个对象以值传递传入函数体，需要拷贝构造函数创建一个临时对象压入到栈空间*
   - *一个对象以值传递的方式从函数返回，需要执行拷贝构造函数创建一个临时对象作为返回值*
   - *一个对象需要通过另一个对象进行初始化*

3. *为什么拷贝构造函数必须是pass by reference，不能是 pass by value*
   
   - *是为了防止死递归*
   - *当一个对象需要以值方式进行传递时，编译器会生成代码调用它的拷⻉构造函数生成一个副本，如果类 A 的拷⻉构造函数的参数不是引用传递，而是采用值传递，那么就又需要为了创建传递给拷⻉构造函数的参数的临时对象，而又一次调用类 A的拷⻉构造函数，这就是一个死递归*

## ***拷贝赋值函数***

```cpp
// 这个函数就是 如果 有 1 a&b 先把 a 清销毁, 2 在new一个内存空间 , 3 在把 b 拷贝到 a 里面来
inline String& String::operator = (const String& str) {
  if (this == &str) {  // 检测自我赋值
    return *this;
  }
  delete[] m_data;  // 1
  m_data = new char[ strlen(str.m_data)+1 ];  // 2
  strcpy(m_data, str.m_data);  // 3
  return *this;
}
// 一定要检测自我赋值 要不然 a&b 同时指向一个内存地址 然后杀掉之后 a&b就指定不到值
```

## ***Effective c++ Item***

1. *对于更多的三个特殊函数一些规范及其知识点，请看《[Effective c++ Item5~Item12](https://vlicecream.github.io/effective-c-%E6%9D%A1%E6%AC%BE5-12-%E6%9E%84%E9%80%A0%E6%9E%90%E6%9E%84%E8%B5%8B%E5%80%BC%E8%BF%90%E7%AE%97/)》*

