面向對象:
更接近真實世界(關注各對象之間的關系,而非各步驟的進行)
-
將結構體升級成立類
-
類里面可以有:成員函數,成員變量
-
class Stack { public:void Init(int defaultCapacity=4 ) {_a = (int*)malloc(sizeof(int) * defaultCapacity);_capacity = defaultCapacity;_top = 0;}void Push(int x) {_a[_top++] = x;}void Destory() {free(_a);_a = nullptr;} private:int* _a;int _top;int _capacity; }; int main() {Stack st;st.Init();st.Push(1);st.Destory();return 0; }
-
訪問限定符(即訪問權限):
-
- public:類外可以訪問
- protected
- private
- 每一種訪問限定符的作用域是從當前限定符到下一個限定符
-
一個類里面的函數,也可以聲明與定義分離(如果函數太長了話)(如果聲明和定義都直接寫在類里面,如果這些函數符合內聯規則,則默認都是內聯函數)
-
//stack.c class Stack { public:void Init(int defaultCapacity=4);void Push(int x) {a[top++] = x;} }
-
//stack.h //指定類域 void Stack::Init(int defaultCapacity=4 ) {a = (int*)malloc(sizeof(int) * defaultCapacity);capacity = defaultCapacity;top = 0; }
封裝
-
本質上是一種更好的管理(便于用戶使用類)
-
對于成員變量,只有在實例化某一個類的時候,才會為類里面的成員變量開辟空間(即它的定義)(也就相當于類其實是一種設計,并沒有實際的東西,只有實例化之后,才有這種設計對應的實例)
-
class Stack { private:int* _a;int _top;int _capacity;/*這些變量是聲明,不是定義(對于變量來說,是否開空間是區分聲明和定義的最大區別)所以在實例化對象的時候,才定義這些變量*/ };
-
對于某一個類的實例化對象來說,
sizeof()
計算的是成員變量在經過內存對齊之后的總大小,不加成員函數。(因為對于每一個實例來說,都有一套成員變量都是單獨屬于它們自己的。但對于成員函數來說,所有實例的該方法都是一樣的,沒有必要每個實例都存儲一份成員函數,只需要在一個公用區域存一份,所有實例都訪問這一個就行了,所以sizeof()
只有成員變量,沒有成員函數) -
如果一個類只有成員函數,或者什么都沒有,
sizeof()
的計算結果是1。相當于這個類的對象默認占一個字節,相當于占一個位置,表示這個對象是存在的,但不存有效數據
This指針
-
其實對于實例化對象來說,雖然成員函數是共用的,但每個實例調用時都是使用自己的成員變量的值,其實本質上是因為編譯器默認使用了this指針,將當前實例的地址傳給了成員函數,所以不同實例在調用的時候,使用的是自己的成員變量
-
class Date { public:void print() {cout << year << " " << mon << " " << day;}void init() {year = 1;mon = 2;day = 3;} private:int year;int mon;int day; }; int main() {Date d;d.init();d.print();return 0; } /*本質上其實是將當前實例的地址傳給了成員函數,所以不同實例在調用的時候,使用的是自己的成員變量void print(Date* this) {cout << this->year << " " << this->mon;}Date d;d.print(&d); */
-
this不能在形參和實參傳遞,但可以直接在函數內部使用
-
this指針是形參,所以和普通參數一樣,是存在棧里面的,作為棧幀的一部分(隨壓棧一起)
類的六大默認成員函數(不顯式寫出編譯器就會自行生成)
-
一.構造函數(完成實例的初始化):
-
-
函數名與類名相同
-
無返回值(也不需要寫void)
-
在對象實例化的時候編譯器自動調用對應的構造函數(如果不寫構造函數,則編譯器會自動生成默認的構造函數)
-
構造函數可以重載(多種初始化的方式)
-
沒有自己寫的構造函數時,編譯器生成默認的構造函數,這個函數針對內置類型不做處理,針對自定義類型成員,會去調用該類型的構造函數(而對于成員變量的聲明,可以給默認參數,本質上就是給這個自動生成的構造函數使用的)
-
**默認構造函數(無參構造函數,全缺省構造函數,沒有構造函數時編譯器默認生成的構造函數)**最多只能有一個,也可以沒有(如果沒有,那就不能用默認方式初始化)如果當自己寫了別的不是默認構造函數形式的構造函數,那就需要自己在寫一個默認構造函數
-
有兩種情況不需要自己寫構造函數:
-
- 內置類型成員都有缺省值,且初始化符合我們的要求
- 全是自定義類型的數據,且這些類型都有自己的默認構造函數
-
class stack { public:stack(int capacity=4) {_a = (int*)malloc(sizeof(int) * capacity);_capacity = 0;_top = 0;}private:/*給的是缺省值,是給編譯器的默認構造函數用的,這里并沒有開辟空間,在實例化的時候才會開辟空間*/int* _a=nullptr;int _capacity=1;int _top=1; };
-
-
二.析構函數(相當于destroy)(完成對象中資源的清理工作,即free等,為了防止編譯器直接銷毀對象造成內存泄露):
-
-
在類名前加上
~
-
無返回值無參數
-
不可以重載
-
編譯系統自動調用
-
不寫析構函數,編譯器會自動生成(這個函數針對內置類型不做處理,針對自定義類型成員,會去調用該類型的析構函數)
-
有動態申請的資源,就需要自己寫析構函數(因為普通的變量會在函數本身創建的棧上,隨函數結束棧的銷毀而銷毀了,所以只有在堆區或其他區上有自行申請的資源才需要析構)
-
class stack { public:~stack() {free(_a);_a = nullptr;_capacity = 0;_top = 0;} private:int* _a;int _capacity;int _top; };
-
-
三.拷貝構造
-
-
是一種構造函數的特殊重載形式,相當于傳一個同類型的變量作為參數
-
在傳參時,必須以引用的方式傳遞參數,不然會發生無限遞歸
-
因為如果以傳值調用來傳遞參數,本質上來說其實是要調用該類型的拷貝構造函數來創建形參的,也就相當于對于內置數據類型形參是直接拷貝,而自定義數據類型則是調用拷貝構造函數來創建形參的
-
class Date { public:Date(const Date & d) {_year = d._year;_mon = d._mon;_day = d._day ;} private:int _year = 1;int _mon = 1;int _day = 1; };
-
雖然_year這些成員屬性是私有的,但私有屬性只有在類外不能訪問,此時在類內,是可以訪問的
-
如果不顯式定義,編譯器會自己生成默認拷貝構造函數,默認是:
-
- 內置數據類型完成值拷貝/淺拷貝
- 自定義數據類型會調用它的拷貝構造函數
-
在有些場景下,淺拷貝不足以滿足要求,就需要自己寫拷貝構造函數,以實現深拷貝(比如在stack里,如果進行淺拷貝,那么會使兩個棧里面的數組指針指向同一塊空間(因為淺拷貝只將數值原封不動的拷貝過去,所以它們指向同一塊空間),顯然不是我們想要的)
-
-
四.賦值運算符重載:
-
-
用于已經存在的兩個對象之間的賦值拷貝
-
本質就是將賦值運算符重載了一下
-
如果不顯式定義,編譯器會自己生成默認賦值運算符重載,默認是:
-
-
- 內置數據類型完成值拷貝/淺拷貝
-
- 自定義數據類型會調用它的賦值運算符重載
- 賦值運算符只能定義在類內部,不能重載成全局函數。(別的運算符既可以重載在類內,也可以重載成全局,但由于賦值運算符重載是一種默認成員函數,所以只能在類內)
-
-
class Date { public:Date& operator= (Date& x) {_day = x._day;return *this;} //之所以返回值是一個(Date&),是為了支持連續賦值的情景,如果不要求支持連續賦值,則返回值可以寫成void private:int _year = 1;int _mon = 1;int _day = 1; };int main() {Date d1(1);Date d2(2);Date d3(3);d3 = d1 = d2;return 0; }
-
-
五.取地址運算符的重載
-
Date* Date::operator& () {return this; } const Date* Date::operator& () const {return this; }//這兩個函數因為參數不同構成函數重載
-
const
-
對應一些成員實例,如果是const修飾的,那么在使用一些成員函數的時候可能會造成權限的放大,從而導致錯誤
-
下面這個代碼里,如果
print函數
右側不加const
,則無法正常打印,因為此時d1的類型是const Date
,而且指向該對象的指針this
是隱式傳遞的,類型是Date*
由于它的類型是編譯器底層傳遞的,我們不能顯式來寫出this
,所以只能將const
寫在最右邊,其實本質就是修飾this
指針的 -
void print() const//修飾this指針的const {cout << _year<<"--"<<_mon<<"--"<<_day << endl; }const Date d1(2023, 1, 26); d1.print();
-
在談構造函數
-
第一種:構造函數體賦值
-
Date::Date(int year, int mon, int day) {_year = year;_mon = mon;_day = day; }
-
第二種:初始化列表
- 有三類成員變量必須用初始化列表初始化:
-
- 沒有默認構造函數的結構體變量
- 引用類型
- const修飾的變量
- 對于初始化列表的理解:在實例化對象時相當于時整個對象的定義,而對于它的成員變量,其實是在初始化列表所在的位置定義的,因為引用和const修飾的變量只有在定義的時候才能賦值,所以它們只能在初始化列表里面初始化。其實在初始化列表里,如果不寫出所有成員變量,那么就是自定義類型調用默認構造函數,內置類型不做處理(其實內置類型的成員變量在聲明的時候給的默認值,就是傳到初始化列表里,用于內置類型成員變量初始化的。)它下面的那個
{}
,是為了某些多步初始化的過程無法在初始化列表里完成,需要在{}
里完成 - 成員變量的初始化順序是按類中的聲明順序初始化的,和其在初始化列表里的順序無關
-
Date(int year, int mon, int day):_year(year), _mon(mon), _day(day) {}
explicit 關鍵字
- 如果構造函數在傳參的時候不想讓其發生隱式類型轉換,就可以在函數名之前加上
explicit
靜態成員,函數
- 靜態成員變量是屬于這個類的,生命周期是全局的。而普通的成員變量是屬于該類的實例的。也就是說靜態成員變量是所有實例共享的變量。
- 靜態成員變量沒有初始化列表,所以靜態成員變量必須在類外邊,全局位置定義。(本質上相當于用類去封裝全局變量)
- 靜態成員函數:沒有this指針,只要指定類域和訪問限定符即可訪問。靜態成員函數只能訪問靜態成員變量,因為它沒有this指針,沒法訪問某一個實例的普通成員變量
- 非靜態函數可以訪問靜態成員變量,靜態函數不可以訪問靜態成員變量,函數(因為靜態函數沒有this指針)
class A {
public:static int _a;
private:
};int A::_a = 0;int main() {A aa;cout << ++A::_a << endl;cout << A::_a << endl;return 0;
}
-------------------------------------------------------------
-------------------------------------------------------------
class A {
public:static int Get() {return _a;}
private:static int _a;
};int A::_a = 0;int main() {A aa;cout << aa.Get() << endl;cout << A::Get() << endl;return 0;
}