1. 程式人生 > >C++回爐之_C++PrimerPlus_第十二章 類和動態記憶體分配

C++回爐之_C++PrimerPlus_第十二章 類和動態記憶體分配

複製建構函式
  • 如果沒有定義複製建構函式 – C++會自動提供
  • 原型

    class_name(const class_name&);
    Point(const Point&);
  • 功能
    • 逐個複製非靜態成員的值 – 淺複製
    • 如果含有成員的型別也是類, 則使用此成員的複製建構函式來複制此物件
    • 當類成員裡含有指標的時候,那麼這兩個物件的此成員都會指向同一個記憶體 – 這很不好
      • 此時會使用new初始化指標成員,然後手動定義一個複製建構函式,進行深複製
        • 注意要保證所有的建構函式裡new的使用都與解構函式保持一致 – 有時候需要調整預設構造以實現這一點
  • 何時會呼叫

    • 新建一個物件並將其初始化為同類現有物件時

      Point a(b);
      Point a = b;
      Point a = Point(b);
      Point a = new Point(b);
    • 每當程式生成物件副本的時候,都會呼叫複製建構函式

      • 函式按值傳遞物件 或 函式返回物件時 – 可按引用傳遞
複製賦值函式
  • 實質是賦值運算子的過載
  • 如果沒有定義, C++會自動提供
  • 初始化類時不一定呼叫賦值運算子, 也可能只調用複製建構函式

    • 初始化總會呼叫複製建構函式,但只是有可能呼叫賦值運算子

      Point a = b; // 是否呼叫賦值運算子取決於實現
  • 原型

    class_name& class_name::operator=(const
    class_name&);
  • 使用

    • 同複製構造一樣,當成員函式含有指標的時候,同樣需要手動建立
    • 手動建立時需要注意一些事項
      • 需要先釋放目標物件之前使用new分配的舊資料
      • 應儘量避免將物件賦值給本身 – 否則可能會在賦值前釋放其舊資料
      • 應返回一個指向呼叫物件的引用 – 為了可以連續賦值
      • 由此可知賦值函式並不建立新的物件
    • 可以進一步過載賦值運算子,以支援其他型別到此類的轉換
    • 如果目前還沒有必要實現複製構造和賦值運算子,但也不希望有複製或賦值現象發生,可將這兩個函式宣告為private且忽略其實現(將永遠不會被呼叫)
過載[]運算子
  • 對於[]操作符,兩個運算元一個在[左邊,一個在[]

    裡面

    operator[](type_name);
  • a[4] 等價於a.operator[](4);

  • 有時候將返回型別宣告為引用,即可使用[]對元素(即使是私有成員)進行賦值
  • 當物件為const型別時,便不能使用[]進行賦值

    • 此時需要宣告一個const版本的[]過載版本

      const return_name& oeprator[](typename) const;
類中的new和delete*
  • 在類中使用new的注意事項

    • 建構函式中使用new, 解構函式中要使用delete
    • new對應delete – new[] 對應delete[]
    • 如果有多個建構函式,需要以相同的方式使用new – 只有一個解構函式
      • 也可以將指標初始化為空 – 0 NULL nullptr(C++11) – delete可以用於空指標
    • 應當定義一個複製建構函式,通過深度複製將一個物件初始化為另一個物件

      String::String(const String& s) {
          len = s.len;
          str = new char[len+1];          // 分配空間 -- 深複製
          strcpy(str, s.str);
      }
    • 應當定義一個賦值運算子,通過深度複製將一個物件複製給另一個物件

      String& String::operator=(const String& s) {    // 返回引用 -- 防止建立額外副本
          if(this == &s) return *this;            // 自我賦值時 -- 直接返回this
          delete[] str;                           // 清理舊資料
          len = s.len;
          str = new char[len+1];
          strcpy(str, s.str);
          return *this;
      }
函式的返回物件型別
  • 返回指向const物件的引用

    • 返回物件將呼叫複製建構函式,而返回引用不會
    • 返回引用將提高效率
    • 返回的引用不能是所在函式的區域性變數
    • const型別引用的返回值也應該是const
    • 返回const型別引用可使函式在呼叫的時候不被修改

      const int& Max(int& x, int& y);
      Max(a, b)++; // 這樣便是不對的, 但如果不是const的話便可以
    • 返回const引用的函式可以賦值給此型別變數(賦值了一下),也可賦值給const型別的引用,但是不能賦值給非const的引用

      int c = Max(a, b); // 正確
      const int& c = Max(a, b); // 正確
      int& c = Max(a, b); // 錯誤
      const Point& Max(const Point& a, const Point& b) {
          if(a.x > b.x) return a;
          return b;
      }
  • 返回指向非const物件的引用

    • 有時候是為了提高效率 – 如過載賦值運算子
      • 也可以返回物件,也可以返回引用 – 返回引用可避免建立新的副本
    • 有時候是為了必須這樣做 – 如過載 cout的<< 運算子
      • ostream物件沒有公有的複製建構函式
  • 返回物件
    • 返回物件是函式中的區域性變數時,只能使用返回物件而非引用 – 如過載+運算子
    • 存在複製建構函式建立返回的物件的開銷,但無法避免
  • 返回const物件
    • 主要是為了防止函式(或過載了操作符的表示式)本身成為左值而導致的一些問題
      • a+b = x; 這種式子無意思 且如果用在if()時還有可能出現不易察覺的錯誤
指向物件的指標
Point* p = new Point(1, 2);
cout << p->get_x() << endl;
delete p;
  • 定位new運算子不能用delete釋放 – 可顯示呼叫解構函式

    char* buffer = new buffer[maxn];
    Point* p1 = new(buffer) Point;
    Point* p2 = new(buffer + sizeof(Point)) Point(1, 2);
    p2->~Point();
    p3->~Point();
    delete[] buffer;