引言
類(class)的使用分為兩種——基于物件(object Based)和面向物件(object oriented)
基于物件是指,程式設計中單一的類,和其他類沒有任何關系
單一的類又分為:不帶指標的類(class without pointer members)和帶指標的類(class with pointer members)
面向物件則是類(class)中涉及了類之間的關系:復合(composition)、委托(delegation)、繼承(inheritance)
1.頭檔案的防御式宣告
#ifndef xxx
#define xxx
...
#endif
在撰寫頭檔案時應該有這樣的一種習慣
目的是避免多次重復包含同一個頭檔案,否則會引起變數及類的重復定義
2.使用初始化串列的好處
- 只有建構式這類函式具有“初始化串列”這一特性
- 從結果上來看,建構式時使用初始化串列和在類內賦值是一樣的,但我們都知道一個變數必須先初始化然后才被賦值,而初始化串列顧名思義是只執行初始化這一步,在類內賦值時就要先初始化再被賦值,所以從執行效率上講,使用初始化串列會更快,也更簡潔
3.設計模式:singleton(單例類)
- demo:
class A {
public:
static A & getInstance();
setup() {...}
private:
A();
A(const A & rhs);
...
}
A & A::getInstance()
{
static A a;
return a;
}
...
//外部介面
A::getInstance().setup();
-
原理:將建構式設定為私有屬性,同時設定一個靜態函式介面回傳一個該類物件
-
作用:保證每一個類僅有一個實體,并為它提供一個全域訪問點
-
單例模式(Singleton)的主要特點不是根據用戶程式呼叫生成一個新的實體,而是控制某個型別的實體唯一性,它擁有一個私有建構式,這確保用戶無法通過new直接實體它,除此之外,該模式中包含一個靜態私有成員變數instance與靜態公有方法Instance(),Instance()方法負責檢驗并實體化自己,然后存盤在靜態成員變數中,以確保只有一個實體被創建,
這種模式主要有以下特征或條件:
- 有一個私有的無參建構式,這可以防止其他類實體化它,而且單例類也不應該被繼承,如果單例類允許繼承那么每個子類都可以創建實體,這就違背了Singleton模式“唯一實體”的初衷,
- 單例類被定義為sealed,就像前面提到的該類不應該被繼承,所以為了保險起見可以把該類定義成不允許派生,但沒有要求一定要這樣定義,
- 一個靜態的變數用來保存單實體的參考,
- 一個公有的靜態方法用來獲取單實體的參考,如果實體為null即創建一個,
-
參考:
設計模式詳解:Singleton(單例類)_singleton類_p_帽子戲法的博客-CSDN博客
單例模式(Singleton)的6種實作 - JK_Rush - 博客園 (cnblogs.com)
4.常成員函式的重要性
-
如果一個成員函式不改變類的資料成員時,就把它宣告為常函式,這是一個好的習慣
-
當實體化一個常物件時,常物件要求不能改變資料成員,如果成員函式不加const,將無法呼叫此成員函式,編譯器不會通過,即使此函式確實沒有改變資料成員;同時,即使成員函式被宣告為了常函式,實體化一個普通物件時依然可以呼叫,
-
簡單來說,不宣告為常成員函式可能不會有問題,但宣告為常成員函式能確保一定不出問題
5.如何解釋成員函式接收同類物件引數...
-
問:如何解釋一個類的成員函式在接收同類物件的引數(比如拷貝建構式)可以直接呼叫該物件的任何成員,明明既不是友元也不是嵌套?
-
答:相同class的各個objects互為friends(友元)
6.設計一個類要考慮什么
- 目的:高效、安全、簡潔、嚴密
- 1.資料成員私有
- 2.引數傳遞和回傳值優先考慮用參考(傳遞的是地址值,這樣不管傳遞的資料記憶體占用多大,依然固定傳入四個位元組,即使當傳遞字符這樣小于四個位元組時用值傳遞確實比參考或指標傳遞更快一些,但不必考慮這些細枝末節)
- 3.建構式優先去使用初始化串列
- 4.能宣告為常成員就宣告為常成員
7.回傳值加不加參考
- 取決于回傳的值是否要改變、是否可以改變,前者由我們決定,后者由語法限制
兩個案例
class A
{
int value;
...
};
...
A& fun1(A* x, const A& y)
{
x.value += y.value; //第一引數會改變,第二引數不會改變
return *x
}
class B
{
int value;
...
};
...
B fun2(const B& x, const B& y)
{
//第一引數和第二引數都不會改變
return B(x.value + y.value);
}
8.運算子多載成員函式的思考
e.x.
inline complex&
_doapl(comlex* ths, const complex& r)
{
...
return *ths;
}
inline complex&
complex::operator += (const comlex& r)
{
return _doapl(this, r);
}
...
comlex c1(2,1), c2(3), c3;
//c2 += c1;
//c3 += c2 += c1;
當多載一個二元運算子為成員函式時,我們知道多載函式除了右運算元是我們傳遞的,函式還會默認用一個this指標,來接收左運算元
那么可能會有疑問,我們想要改變的是左運算元,而且由于傳遞的是指標,函式內也確實可以改變,那回傳值又有什么用呢,宣告為空不就行了,
當我們使用多載運算子時只是像被注釋的第一行代碼一樣,那么回傳值確實不重要,但是當我們使用的形式像被注釋的第二行代碼時,那么回傳值就很重要,因為c2.+=(c1) 這個函式的回傳值就是 c3 += () 函式的引數
9.temp object(臨時物件)
- 語法: typename ();
- 生存期:僅宣告那一行
- 回傳值是臨時物件時不難return by reference
10.<<多載的一些注意點
-
只能多載為非成員函式
-
左運算元固定為系統定義的 ostream 型別,且為非常量參考
- 非常量:向流寫入內容其實就是改變了它的狀態
- 參考:無法復制一個 ostream 物件
- 注:ostream 類與 istream 類一樣,它的的拷貝建構式和賦值函式都是保護型別的,所以 ostream 是不允許拷貝或者賦值的,所以它也不能直接作為回傳型別和引數傳遞,很多時候需要使用參考來進行傳遞,
-
最好加回傳值且為參考,原因前面已經說明,且我們對于連續呼叫<<的頻率要大得多
-
連續呼叫時的呼叫順序
complex c1, c2; cout << c1 << c2; //先執行 <<(cout, c1) 函式 //回傳的 ostream型別的cout的參考 又作為<<(ostream &, c2)的第一引數 -
在語法上我們當然也可以多載為成員函式,只要左運算元為自定義型別即可,但這樣并不符合我們通常的書寫習慣

11.Big Three(三位一體原則)
-
三大件:拷貝構造、拷貝賦值、解構式
-
解釋:當一個類需要我們去主動設計解構式時,那它很大概率也需要一個拷貝建構式和賦值運算子多載成員函式
-
應用:當一個類具有指標成員時(class with point member)或者說當我們設計了一個有動態記憶體管理的類時
-
原因:
-
解構式角度:默認解構式會僅洗掉指向物件的指標,而洗掉一個指標不會釋放指標指向物件占用的記憶體,最侄訓導致記憶體泄露,
-
拷貝構造角度:默認的建構式是淺拷貝,復制的只是指標也就是地址值,這樣導致兩個物件共享一個記憶體空間,這是十分危險的,當其中一個物件被洗掉后,解構式將釋放那片共享的記憶體空間,接下來對這片已經釋放了記憶體的任何參考都將會導致不可遇見的后果,
-
賦值運算角度:
賦值相比于拷貝構造要考慮更多
首先是自我賦值判斷,如果不判斷,當左右運算元指向的是同一個地址時,會造成將左運算元物件的元素洗掉并釋放其占用的記憶體,同時由于左右運算元指向同一物件,導致右運算元同時被洗掉,但接下來還要將右操作物件復制,這會造成不可預知的結果,這也被稱為證同測驗,
其次是進行三步必要操作:
- 釋放已有記憶體
- 開辟新的記憶體
- 內容賦值
-
-
代碼示例:
#ifndef __MYSTRING__
#define __MYSTRING__
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;//字符陣列指標
};
#include <cstring>
//建構式
inline
String::String(const char* cstr)
{
//開辟記憶體、計算長度、內容拷貝
if (cstr) {
m_data = https://www.cnblogs.com/BlickWinkel/p/new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data ='\0';
}
}
//解構式
inline
String::~String()
{
delete[] m_data;//釋放指標指向空間
}
//多載=
inline
String& String::operator=(const String& str)
{
//檢測自我賦值(self assignment)
if (this == &str)
return *this;
delete[] m_data;
m_data = https://www.cnblogs.com/BlickWinkel/p/new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
//建構式
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
#include
using namespace std;
//多載<<
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
#endif
12.new和建構式,delete和解構式
-
當我們使用new創建了一個指向類的物件的指標時
這里的new干了三件事:
- 呼叫 operator new 函式,這個函式內部又呼叫了malloc函式來分配記憶體
- operator new 函式回傳的是空指標,顯式轉換為型別別指標后賦值給我們創建的指標
- 呼叫指標指向物件的建構式
- 當我們使用delete釋放一個指向物件的指標時
- 首先呼叫物件的解構式,這里該類的解構式又用delete釋放了類內動態分配的陣列指標
- 然后再釋放這個指向物件的指標
- 結合本例來看,就是delete ps,先delete它指向的成員,再delete它自己
13.malloc()動態分配記憶體的結構
-
紅色部分是 cookie ,記錄記憶體分配的總大小,就是圖中的41,其最低位用于表示是否已分配(1表示已分配,0表示已回收),之所以最低位可以變,是因為分配的記憶體總空間一定是16的倍數,其16進制表示時最低位一定為0,也就是說這個位置是空出來的,剛好用來表示記憶體狀態,每一個 new 的物件都會有上下兩個 cookie,來預先申請一塊記憶體池,然后供物件實體化,
-
綠色部分是呼叫malloc()時向系統申請的記憶體,該函式回傳時,也會回傳這塊區域開頭的指標,
-
綠色部分上下兩塊 gap 預先被填充為了0xfdfdfdfd,用來分隔客戶可以使用的記憶體區和不可使用的記憶體區,同時,當這塊記憶體被歸還時,編輯器也可以通過下gap的值區判斷當前記憶體塊是否被越界使用了
-
從gap向上連續的7個記憶體空間共同組成了debug header,從上向下標號為1-7
- 1、2兩塊空間保存了兩根指標,目的是使多個記憶體塊連接成鏈表,
- 3空間保存了申請本記憶體塊的檔案名
- 4空間保存了申請本記憶體塊的代碼行數
- 5空間記錄了本記憶體塊中實際可以被用戶使用的記憶體空間的大小
- 6空間記錄了當前記憶體塊的流水號,即是鏈表中的第幾個,從1開始
- 7空間記錄了當前記憶體塊被分配的形式
-
填補區pad
參考:
https://zhuanlan.zhihu.com/p/492161361
https://www.cnblogs.com/zyb993963526/p/15682014.html#_label2
https://blog.csdn.net/qq_61500888/article/details/122170203
14.array new 搭配 array delete
動態分配陣列時要注意的:
- 其記憶體區域相比上面所提到的多了一個記憶體塊用來記錄陣列長度(分配物件數量)
- 當申請記憶體后,回傳的指標指向資料開始處,而使用 delete[] 釋放時,指標會指向它的上一塊,也就是記錄陣列長度的那一塊,從而可以根據物件的數量呼叫相應次數的解構式,如果使用 delete 釋放的話,它不會去獲取物件的長度,而是只呼叫指標指向的那一個物件的解構式,
- 如果物件的型別是內置型別或者是無自定義的解構式的型別別,是可以使用 delete 來釋放 new[] 物件的,但是,如若不然,使用 delete 來釋放物件,物件所分配的記憶體空間雖然會照樣全部釋放,但是只會呼叫第一個物件的解構式,這就導致記憶體泄漏,所以,養成良好的習慣,new [] 必 delete []
15.this指標
類的每一個非靜態成員函式(包括建構式、拷貝構造等)都隱含著一個指標形參名為this,當物件呼叫成員函式時就會隱含傳遞該物件的地址給它,這也是為什么一個類的成員函式雖然只有一份但也會根據接收的訊息不同產生不同的行為,而靜態成員函式不隱含this指標,所以即使呼叫它的物件不同維護的依然是同一段代碼
16.namespace
三個案例:
- 使用using引入一個命名空間的全部
- 使用using引入一個命名空間的個體
- 不用using,使用時手動引入
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/554294.html
標籤:面向對象
下一篇:返回列表







