1. 程式人生 > >1、【C++】類&物件/建構函式/拷貝建構函式/操作符過載/解構函式

1、【C++】類&物件/建構函式/拷貝建構函式/操作符過載/解構函式

一、C++類 & 物件

    C++ 在 C 語言的基礎上增加了面向物件程式設計,C++ 支援面向物件程式設計。類是 C++ 的核心特性,通常被稱為使用者定義的型別。

    類用於指定物件的形式,它包含了資料表示法和用於處理資料的方法。類中的資料和方法稱為類的成員。函式在一個類中被稱為類的成員。

1、定義C++ 類

    定義一個類,本質上是定義一個數據型別的藍圖這實際上並沒有定義任何資料,但它定義了類的名稱意味著什麼,也就是說,它定義了類的物件包括了什麼,以及可以在這個物件上執行哪些操作。

    類定義是以關鍵字 class 開頭,後跟類的名稱。類的主體是包含在一對花括號中。類定義後必須跟著一個分號或一個宣告列表。例如,我們使用關鍵字 class 定義 Box 資料型別,如下所示:

class Box
{
   public:
      double length;   // 盒子的長度
      double breadth;  // 盒子的寬度
      double height;   // 盒子的高度
};

    關鍵字 public 確定了類成員的訪問屬性。在類物件作用域內,公共成員在類的外部是可訪問的。您也可以指定類的成員為 private 或 protected。

2、定義C++物件

    類提供了物件的藍圖,所以基本上,物件是根據類來建立的。宣告類的物件,就像宣告基本型別的變數一樣。下面的語句聲明瞭類 Box 的兩個物件:

 Box Box1;
// 宣告 Box1,型別為 Box Box Box2; // 宣告 Box2,型別為 Box

    物件 Box1 和 Box2 都有它們各自的資料成員。

3、訪問資料成員

    類的物件的公共資料成員可以使用直接成員訪問運算子 (.) 來訪問。為了更好地理解這些概念,讓我們嘗試一下下面的例項:

#include <iostream>
 
using namespace std;
 
class Box
{
   public:
      double length;   // 長度
      double breadth;  // 寬度
double height; // 高度 }; int main( ) { Box Box1; // 宣告 Box1,型別為 Box Box Box2; // 宣告 Box2,型別為 Box double volume = 0.0; // 用於儲存體積 // box 1 詳述 Box1.height = 5.0; Box1.length = 6.0; Box1.breadth = 7.0; // box 2 詳述 Box2.height = 10.0; Box2.length = 12.0; Box2.breadth = 13.0; // box 1 的體積 volume = Box1.height * Box1.length * Box1.breadth; cout << "Box1 的體積:" << volume <<endl; // box 2 的體積 volume = Box2.height * Box2.length * Box2.breadth; cout << "Box2 的體積:" << volume <<endl; return 0; }

    需要注意的是,私有的成員和受保護的成員不能使用直接成員訪問運算子 (.) 來直接訪問。

4、類的成員函式

    類的成員函式是指那些把定義和原型寫在類定義內部的函式,就像類定義中的其他變數一樣。類的成員函式是類的一個成員,它可以操作類的任意物件,可以訪問物件中的所有成員。

    讓我們看看之前定義的類 Box,現在我們要使用成員函式來訪問類的成員,而不是直接訪問這些類的成員:

class Box
{
   public:
      double length;         // 長度
      double breadth;        // 寬度
      double height;         // 高度
      double getVolume(void);// 返回體積
};

    成員函式可以定義在類定義內部,或者單獨使用範圍解析運算子 :: 來定義在類定義中定義的成員函式把函式宣告為內聯的,即便沒有使用 inline 識別符號。所以可以按照如下方式定義 Volume() 函式:

class Box
{
   public:
      double length;      // 長度
      double breadth;     // 寬度
      double height;      // 高度
  	//這裡的成員函式getVolume()是一個行內函數
      double getVolume(void)
      {
         return length * breadth * height;
      }
};

也可以在類的外部使用範圍解析運算子 :: 定義該函式,如下所示:

double Box::getVolume(void)
{
    return length * breadth * height;
}

    在C++中建立一個類,這個類中肯定會包括建構函式解構函式拷貝建構函式過載賦值操作

二、類的建構函式

    類的建構函式是類的一種特殊的成員函式,它會在每次建立類的新物件時執行。

    建構函式的名稱與類的名稱是完全相同的,並且不會返回任何型別,也不會返回 void。建構函式可用於為某些成員變數設定初始值。

建構函式包括預設建構函式帶參建構函式

1、預設建構函式

    預設建構函式沒有返回值,也沒有任何引數,下面是一個預設建構函式的例項:

class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line();  // 這是建構函式
 
   private:
      double length;
};
 
// 建構函式定義
Line::Line(void)
{
    cout << "Object is being created" << endl;
}
2、帶參建構函式

    帶參建構函式可以理解為有引數的預設建構函式,這樣在建立物件時就會給物件賦初始值,如下面的例子所示:

class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line(double len);  // 這是帶參建構函式
 
   private:
      double length;
};
 
// 帶參建構函式定義
Line::Line( double len)
{
    cout << "Object is being created, length = " << len << endl;
    length = len;
}
3、使用初始化列表來初始化欄位

使用初始化列表來初始化欄位:

Line::Line( double len): length(len)
{
    cout << "Object is being created, length = " << len << endl;
}

上面的語法等同於如下語法:

Line::Line( double len)
{
    length = len;
    cout << "Object is being created, length = " << len << endl;
}

    假設有一個類 C,具有多個欄位 X、Y、Z 等需要進行初始化,同理地,您可以使用上面的語法,只需要在不同的欄位使用逗號進行分隔,如下所示:

C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
  ....
}

初始化列表初始化和建構函式初始化的區別:

    (1)初始化列表初始化,是對類的成員進行顯式的初始化,而建構函式初始化是對累的成員進行賦值;

    (2)對於內建型別的成員變數,使用初始化列表進行初始化和使用建構函式初始化,在效能和結果上都是一樣的

    (3)對與非內建型別的成員變數,因為類型別的資料成員的資料成員物件,在進入函式體之前就已經構造完成,也就是說在成員初始化列表處進行構造物件的工作,呼叫建構函式,在進入函式體之後,進行的是對已經構造好的類物件的賦值,又**呼叫一個賦值操作符才能完成(**如果並未提供,則使用編譯器提供的預設成員賦值行為)。為了避免兩次構造,推薦使用類建構函式初始化列表。

    但有很多場合必須使用帶有初始化列表的建構函式。例如,成員型別是沒有預設建構函式的類,若沒有提供顯示初始化時,則編譯器隱式使用成員型別的預設建構函式,若類沒有預設建構函式,則編譯器嘗試呼叫預設建構函式將會失敗。再例如const成員或者引用型別的成員,因為const物件或引用型別只能初始化,不能對它們進行賦值。

    C++基本內建型別包括算術型別空型別算數型別包括整型(整數、字元、布林)和浮點型。空型別指void型別

三、類的拷貝建構函式

1、拷貝建構函式定義

    拷貝建構函式是一種特殊的建構函式,其作用也是為類的成員初始化以及為物件的構造分配儲存空間。函式的名稱必須和類名稱一致,無返回型別它的唯一的一個引數是本型別的一個引用變數,該引數是const型別,不可變。

拷貝建構函式原型如下:

	class_name(const class_name &src);

對於一個類X, 如果一個建構函式的第一個引數是下列之一:

   & X;
   const & X;
   volatile & X;
   const volatile & X;

且沒有其他引數或其他引數都有預設值那麼這個函式是拷貝建構函式,如下:

    X::X(const & X);   
    X::X(& X, int=1); 
    X::X(& X, int a=1, int b=2);
2、拷貝建構函式實現
class Date 
{ 
public: 
    Date(int year = 1995,int month = 12,int day = 8) // 帶參建構函式 
        :_year(year) , _month(month) , _day(day) //初始化列表初始化
    { 
        cout << "date()建構函式"<< this << endl; 
    } 
    Date(const Date& d)// 拷貝建構函式 
        :_year(d._year) , _month(d._month) , _day(d._day) 
    { 
        cout << "date(&d)" << this << endl; 
    }
private: 
    int _year; 
    int _month; 
    int _day; 
};

四、類的賦值運算子過載

1、過載賦值運算子的定義

    過載賦值操作符是一個特別的賦值運算子,通常是用來把已存在的物件賦值給其它相同型別的物件。

過載賦值操作符的原型如下:

class_name& operator=(const class_name &src);
2、過載賦值運算子的實現
Date& Date::operator=(const Date& d) // 賦值運算子過載 
{
	cout << "operator= 賦值運算子過載 " << this << endl;
	if (this != &d) // 判斷是否自己對自己賦值
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}

注意:過載賦值運算子的返回值是一個引用。

3、拷貝建構函式和過載賦值運算子的呼叫

    當類的物件需要拷貝時,複製建構函式將會被呼叫。以下情況都會呼叫複製建構函式:
  一個物件以值傳遞的方式傳入函式體;
  一個物件以值傳遞的方式從函式返回;
  一個物件需要通過另外一個物件進行初始化。

    如果物件在宣告的同時將另一個已存在的物件賦給它,就會呼叫拷貝建構函式;如果物件已經存在了,然後再將另一個已存在的物件賦給它,呼叫的就是過載賦值運算子

     //假設CTest是一個使用者自定義類
     CTest obj;
     CTest obj1(obj); // 呼叫複製建構函式
     obj1 = obj; // 呼叫過載賦值操作符
4、深拷貝(deepcopy)與淺拷貝(shallowcopy)

    如果在類中沒有顯式地宣告,那麼編譯器會自動生成預設的複製建構函式和過載賦值操作符。預設的複製建構函式和賦值運算子進行的都是“shallow copy”,只是簡單地複製欄位,把值一一賦給要拷貝的值。因此如果物件中含有動態分配的記憶體,就需要我們自己重寫複製建構函式和過載賦值操作符來實現“deep copy”,確保資料的完整性和安全性。

    例如:類內成員變數需要動態開闢堆記憶體,如果實行淺拷貝,也就是把物件裡的值完全複製給另一個物件,如A=B。這時,如果B中有一個成員變數指標已經申請了記憶體,那A中的那個成員變數也指向同一塊記憶體。這就出現了問題:當B把記憶體釋放了(如:析構),這時A內的指標就是野指標了,出現執行錯誤。

    深拷貝和淺拷貝可以簡單理解為:如果一個類擁有資源(堆,或者是其它系統資源),當這個類的物件發生複製過程的時候,資源重新分配,使物件擁有不同的資源,但資源的內容是一樣的,這個過程就是深拷貝;反之,沒有重新分配資源,兩個物件就有用共同的資源,同時對資源可以訪問,就是淺拷貝。
    淺拷貝,只是對指標的拷貝,拷貝後兩個指標指向同一個記憶體空間,深拷貝不但對指標進行拷貝,而且對指標指向的內容進行拷貝,經深拷貝後的指標是指向兩個不同地址的指標。

五、類的解構函式

    類的解構函式是類的一種特殊的成員函式,它會在每次刪除所建立的物件時執行。
    解構函式的名稱與類的名稱是完全相同的,只是在前面加了個波浪號(~)作為字首,它不會返回任何值,也不能帶有任何引數。解構函式有助於在跳出程式(比如關閉檔案、釋放記憶體等)前釋放資源。
    下面的例項有助於更好地理解解構函式的概念:

class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line();   // 建構函式宣告
      ~Line();  // 解構函式宣告
 
   private:
      double length;
};
 
//建構函式定義
Line::Line(void)
{
    cout << "Object is being created" << endl;
}
//解構函式定義
Line::~Line(void)
{
    cout << "Object is being deleted" << endl;
}

    三法則假如型別有明顯地定義下列其中一個成員函式,那麼程式設計師必須連其他二個成員函式也一同編寫至型別內,亦即下列三個成員函式缺一不可:

    (1)解構函式(Destructor)

    (2)拷貝建構函式(copy constructor)

    (3)過載賦值運算子(copy assignment operator)