01C++複習.類&物件/建構函式/拷貝建構函式/操作符過載/解構函式
一、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、訪問資料成員
類的物件的公共資料成員可以使用直接成員訪問運算子 (.) 來訪問。為了更好地理解這些概念,讓我們嘗試一下下面的例項:
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,現在我們要使用成員函式來訪問類的成員,而不是直接訪問這些類的成員:
{
public:
double length; // 長度
double breadth; // 寬度
double height; // 高度
double getVolume(void);// 返回體積
};
成員函式可以定義在類定義內部,或者單獨使用範圍解析運算子 :: 來定義。在類定義中定義的成員函式把函式宣告為內聯的,即便沒有使用 inline 識別符號。所以可以按照如下方式定義 Volume() 函式:
{
public:
double length; // 長度
double breadth; // 寬度
double height; // 高度
//這裡的成員函式getVolume()是一個行內函數
double getVolume(void)
{
return length * breadth * height;
}
};
也可以在類的外部使用範圍解析運算子 :: 定義該函式,如下所示:
{
return length * breadth * height;
}
在C++中建立一個類,這個類中肯定會包括建構函式、解構函式、拷貝建構函式和過載賦值操作。
二、類的建構函式
類的建構函式是類的一種特殊的成員函式,它會在每次建立類的新物件時執行。
建構函式的名稱與類的名稱是完全相同的,並且不會返回任何型別,也不會返回 void。建構函式可用於為某些成員變數設定初始值。
建構函式包括預設建構函式和帶參建構函式。
1、預設建構函式
預設建構函式沒有返回值,也沒有任何引數,下面是一個預設建構函式的例項:
{
public:
void setLength( double len );
double getLength( void );
Line(); // 這是建構函式
private:
double length;
};
// 建構函式定義
Line::Line(void)
{
cout << "Object is being created" << endl;
}
2、帶參建構函式
帶參建構函式可以理解為有引數的預設建構函式,這樣在建立物件時就會給物件賦初始值,如下面的例子所示:
{
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、使用初始化列表來初始化欄位
使用初始化列表來初始化欄位:
{
cout << "Object is being created, length = " << len << endl;
}
上面的語法等同於如下語法:
{
length = len;
cout << "Object is being created, length = " << len << endl;
}
假設有一個類 C,具有多個欄位 X、Y、Z 等需要進行初始化,同理地,您可以使用上面的語法,只需要在不同的欄位使用逗號進行分隔,如下所示:
{
....
}
初始化列表初始化和建構函式初始化的區別:
(1)初始化列表初始化,是對類的成員進行顯式的初始化,而建構函式初始化是對累的成員進行賦值;
(2)對於內建型別的成員變數,使用初始化列表進行初始化和使用建構函式初始化,在效能和結果上都是一樣的。
(3)對與非內建型別的成員變數,因為類型別的資料成員的資料成員物件,在進入函式體之前就已經構造完成,也就是說在成員初始化列表處進行構造物件的工作,呼叫建構函式,在進入函式體之後,進行的是對已經構造好的類物件的賦值,又呼叫一個賦值操作符才能完成(如果並未提供,則使用編譯器提供的預設成員賦值行為)。為了避免兩次構造,推薦使用類建構函式初始化列表。
但有很多場合必須使用帶有初始化列表的建構函式。例如,成員型別是沒有預設建構函式的類,若沒有提供顯示初始化時,則編譯器隱式使用成員型別的預設建構函式,若類沒有預設建構函式,則編譯器嘗試呼叫預設建構函式將會失敗。再例如const成員或者引用型別的成員,因為const物件或引用型別只能初始化,不能對它們進行賦值。
C++基本內建型別包括算術型別和空型別。算數型別包括整型(整數、字元、布林)和浮點型。空型別指void型別
三、類的拷貝建構函式
1、拷貝建構函式定義
拷貝建構函式是一種特殊的建構函式,其作用也是為類的成員初始化以及為物件的構造分配儲存空間。函式的名稱必須和類名稱一致,無返回型別,它的唯一的一個引數是本型別的一個引用變數,該引數是const型別,不可變。
拷貝建構函式原型如下:
對於一個類X, 如果一個建構函式的第一個引數是下列之一:
const & X;
volatile & X;
const volatile & X;
且沒有其他引數或其他引數都有預設值,那麼這個函式是拷貝建構函式,如下:
X::X(& X, int=1);
X::X(& X, int a=1, int b=2);
2、拷貝建構函式實現
{
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、過載賦值運算子的定義
過載賦值操作符是一個特別的賦值運算子,通常是用來把已存在的物件賦值給其它相同型別的物件。
過載賦值操作符的原型如下:
2、過載賦值運算子的實現
{
cout << "operator= 賦值運算子過載 " << this << endl;
if (this != &d) // 判斷是否自己對自己賦值
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
注意:過載賦值運算子的返回值是一個引用。
3、拷貝建構函式和過載賦值運算子的呼叫
當類的物件需要拷貝時,複製建構函式將會被呼叫。以下情況都會呼叫複製建構函式:
一個物件以值傳遞的方式傳入函式體;
一個物件以值傳遞的方式從函式返回;
一個物件需要通過另外一個物件進行初始化。
如果物件在宣告的同時將另一個已存在的物件賦給它,就會呼叫拷貝建構函式;如果物件已經存在了,然後再將另一個已存在的物件賦給它,呼叫的就是過載賦值運算子。
CTest obj;
CTest obj1(obj); // 呼叫複製建構函式
obj1 = obj; // 呼叫過載賦值操作符
4、深拷貝(deepcopy)與淺拷貝(shallowcopy)
如果在類中沒有顯式地宣告,那麼編譯器會自動生成預設的複製建構函式和過載賦值操作符。預設的複製建構函式和賦值運算子進行的都是“shallow copy”,只是簡單地複製欄位,把值一一賦給要拷貝的值。因此如果物件中含有動態分配的記憶體,就需要我們自己重寫複製建構函式和過載賦值操作符來實現“deep copy”,確保資料的完整性和安全性。
例如:類內成員變數需要動態開闢堆記憶體,如果實行淺拷貝,也就是把物件裡的值完全複製給另一個物件,如A=B。這時,如果B中有一個成員變數指標已經申請了記憶體,那A中的那個成員變數也指向同一塊記憶體。這就出現了問題:當B把記憶體釋放了(如:析構),這時A內的指標就是野指標了,出現執行錯誤。
深拷貝和淺拷貝可以簡單理解為:如果一個類擁有資源(堆,或者是其它系統資源),當這個類的物件發生複製過程的時候,資源重新分配,使物件擁有不同的資源,但資源的內容是一樣的,這個過程就是深拷貝;反之,沒有重新分配資源,兩個物件就有用共同的資源,同時對資源可以訪問,就是淺拷貝。
淺拷貝,只是對指標的拷貝,拷貝後兩個指標指向同一個記憶體空間,深拷貝不但對指標進行拷貝,而且對指標指向的內容進行拷貝,經深拷貝後的指標是指向兩個不同地址的指標。
五、類的解構函式
類的解構函式是類的一種特殊的成員函式,它會在每次刪除所建立的物件時執行。
解構函式的名稱與類的名稱是完全相同的,只是在前面加了個波浪號(~)作為字首,它不會返回任何值,也不能帶有任何引數。解構函式有助於在跳出程式(比如關閉檔案、釋放記憶體等)前釋放資源。
下面的例項有助於更好地理解解構函式的概念:
{
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)
轉自https://blog.csdn.net/sinat_33924041/article/details/83621017