目录

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

三个特殊函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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. 代码演示

    •  1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      
      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. 什么是浅拷贝

    • 拷贝后,两个对象指向同一个内存地址,如果修改了其一个,另一个也会受到影响

      1
      2
      3
      
      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. 什么是拷贝构造函数及实现

    • 1
      2
      3
      4
      
      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的拷⻉构造函数,这就是一个死递归

拷贝赋值函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 这个函数就是 如果 有 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