C++是一門OOP(面向物件)的語言,
而C語言只是一門面向程序的語言,
里面有些思路需要重新改變,
前情提要:文章較長,請根據目錄自行選擇
目錄
- 一、守望先鋒
- 二、物件和類
- 1、什么是類
- C語言創建一個堆疊的”類“
- C++創建一個堆疊的類
- 2、類的定義
- 宣告與定義的區別
- 訪問限定符
- 類大小的計算
- 3、this指標
- 簡單的日期類
- 成員變數與成員函式的空間分布
- this指標的分析
- this指標的特性
- this指標儲存在哪?
- 看看這個,來理解
- 三、類的六個默認成員函式
- 1、 建構式
- 建構式的概念
- 什么?你不相信系統呼叫了建構式
- 建構式的特性
- 自定義型別與內置型別
- 對于建構式的建議
- 2、解構式
- 解構式的定義
- 解構式的特性
- 多個物件,解構式呼叫的順序
- 3、拷貝建構式
- 拷貝建構式的特性
- 為什么傳值傳參會引發無窮遞回呼叫
- 拷貝構造與傳參考
- 深拷貝與淺拷貝
- 4、賦值運算子多載
- 運算子多載的特性
- 復制拷貝與拷貝構造
- 5、對前4個默認成員函式的總結
- 6、const成員函式
- 特性
- 幾個問題理解const
- 7、取地址運算子多載
- 保護物件不被讀取地址
- 四、一個完整的日期類(選讀,可跳)
- 日期類.cpp
- Date.cpp
- Date.h
- 五、友元與>>、<<運算子多載
- 為什么cin>>、cout<<可以自動識別內置型別
- cout
- cin
- 友元函式
- 友元類
- 六、深入了解建構式
- 1、函式體內初始化
- 2、初始化串列
- 3、為什么要多此一舉準備兩種初始化模式呢?
- 初始化串列才是單個成員變數定義的階段
- 初始化串列初始化的順序
一、守望先鋒

天使姐姐不漂亮嗎?

為一個場守望先鋒街機先鋒統計資料
如果是面向程序的程式員,可能會考慮:
記錄每個玩家所選的英雄,擊殺人數,對對面傷害量,治療量,承受傷害量,命中率等等,
同時還要記錄每個英雄的移動,釋放技能,技能是否命中,技能造成的傷害的計算,甚至還要考慮技能的運動等等,
甚至還要考慮對方的技能對自己的影響,以及隊友的技能對自己的影響,
用一個主函式 main 來獲取所有資料,
呼叫另外一些函式來分別計算英雄的移動,技能傷害,技能影響,收到的傷害等等,
再呼叫另外一部分函式來顯示結果,玩過守望先鋒的玩家都知道,對于這些資料的 計算,統計,顯示,都是瞬時性的,會隨著玩家的操作不斷變化,而且,一次組隊并不是只打一場,可能會由兩場左右,

守望先鋒并不是傳統的FPS游戲,而是FPS與Moba游戲的結合
而且,對于不同的英雄都要根據英雄的技能去計算,統計不同的資料,
如:安娜要分別統計開鏡和不開鏡的命中率,以及睡針的命中人數,
而法拉要統計火箭彈直接命中人數等等,


而不同模式,又會有不同的變化,對英雄的移動,技能的影響也是不同的,
如:戰斗狂歡,讓所有英雄的血量翻倍,技能CD減半,這就會造成很多不一樣的“化學反應”,
像我有一次戰斗狂歡打了 1.5h 才打完(路霸一直卡車旁邊,都在卡加時),對于普通快速10分鐘左右可是相當長的,對于比賽的各個資料上限又該怎么設定?

而不一樣的地圖對角色的移動,技能的影響又是不一樣的,比如:地形殺,
但對于資料怎么辦?又要重新統計,這相對于計算機而言都太復雜,(如果是這樣,我相信網易的服務器早就崩了),
總之,對于程序性編程,首先要考慮遵循的步驟,然后要考慮如何表示這些資料,
如果是一位OOP程式員,其不僅要考慮如何表示資料,還要考慮如何使用資料 ,
我要跟蹤什么呢?當然是每個玩家,因此要有一個物件來表示每個玩家的各個方面的資料,可以選擇為各個不同的英雄定義不同的類,在通過類來為玩家創建物件,讓計算機執行計算玩家之間的資料互動,可以自動計算,我們要做的僅僅是研究如何跟蹤或表示,每個玩家之間的資料互動,
對于OOP程式員,只需為每個不同英雄定義屬于他們的類,每次游戲開始,再為每個玩家根據其選擇創建物件,再利用演算法跟蹤玩家之間的資料互動即可
這不比面向程序編程簡單?(不知道我理解的對不對)

ps:Dv.A愛你呦~
二、物件和類
這個世界太復雜,處理復雜性的方法之一是簡化和抽象,
守望先鋒游戲中,通過為每個英雄定義一個類,為每個玩家創建一個物件來統籌資料,
在C++中,用戶定義型別指點是實作抽象介面的類的設計,
(說人話就是按照需求,定義類,)
1、什么是類
我們都知道建一棟房子首先需要什么?
需要圖紙,房子的結構圖紙,

有了圖紙,我們就可以根據圖紙見很多大同小異的房子(一張圖紙建出來的么),


房子可以建很多,而圖紙始終是那一張,
這樣可以幫助我們更好理解,

對像是一個物體,而類只是一個自定義的型別,
就像int a;
a 是一個物體變數,在記憶體中有空間,而 int 只是一個型別,表明 a 的身份,
這樣應該理解了吧,
基本型別(內置型別)定義變數完成了三項操作
- 決定資料物件需要的記憶體數量
- 決定如何解釋記憶體在的位(long和float在記憶體中占用的位數相同,但是他們轉換成數值的方法不同);
- 決定可使用資料物件執行的操作或方法,
對內置型別而言,有關資訊全部被內置到編譯器中,但是C++在自定義型別是,必須要自己提供所有資訊,
類的實體化:就是根據類創建一個物件

C語言創建一個堆疊的”類“
C語言也是存在”類“,我們常用的結構體,
比如我們用C語言去是實作一個堆疊
#define CAP 4
typedef int STData;
typedef struct Stack//結構體用于維護堆疊
{
int top;//堆疊頂標記
STData* arr;//堆疊的指標
int capacity;//堆疊的容量
}STstack;
這是對于堆疊的資料的定義,堆疊就是我們用結構體定義的一個”自定義型別“–”堆疊型別“,(因為它并不具有自定義型別的全部資訊)
void InitStack(STstack* st);//堆疊的初始化
void StackPush(STstack* st, STData n);//元素入堆疊
STData StackPop(STstack* st);//元素退堆疊
void StackExpansion(STstack* st);//擴容
int StackEmpty(STstack* st);//判斷堆疊是否為空
void StackDestory(STstack* st);//銷毀堆疊,防止記憶體泄漏
void StackPrint(STstack* st);//列印堆疊的元素,但前提是要退堆疊才能得到元素
這些函式是我們能夠對堆疊執行的操作,
但是資料和執行方法都是分離的,
這就導致我們重點關注的是對堆疊操作的整個程序,
就是,我們這一步選擇入堆疊,下一步選擇彈堆疊,
C++創建一個堆疊的類
我們會將堆疊看為一個類,也就是一個型別,一個自定義型別,
而一個型別,我們需要兩個部分
1,類的成員變數(類的屬性)
2,類的成員函式(類的行為/能夠執行的操作)
這個也滿足上面關于型別的三項操作,
對于C++而言,就不再用結構體這個概念了,該叫類,
C++將struct從C語言的結構體 - 升級到 - 類
你目前可以認為C++中的類就是C語言的結構體除了定義結構體成員變數還有結構體成員函式,
比如:
#define CAP 4
typedef int STData;
struct Stack//結構體用于維護堆疊
{
//結構體成員變數
int top;//堆疊頂標記
STData* arr;//堆疊的指標
int capacity;//堆疊的容量
//結構體成員函式
void InitStack(STData capacity=4);//堆疊的初始化//預設
void StackPush(STData n);//元素入堆疊
STData StackPop();//元素退堆疊
void StackExpansion();//擴容
int StackEmpty();//判斷堆疊是否為空
void StackDestory();//銷毀堆疊,防止記憶體泄漏
void StackPrint();//列印堆疊的元素,但前提是要退堆疊才能得到元素
};
你目前可以理解成長這樣,
而且,其類名就可以是其自定義型別的類名,
直接Stack a1;,這就定義了一個物件,
而且也不用傳上面指標過去了,可以a1.Init(),就可以呼叫那些成員函式,
由于C++兼容C語言,這可以是一個類,但C++會使用class(其中文有類的意思),
2、類的定義
class ClassName//class是定義類的關鍵字 后面接一個類的名字
{
//類體:由成員變數和成員函陣列成
};
與定義結構體很像,
對于類而言,其中的成員函式,你可以直接在類中定義,也可以在.h檔案類中宣告,去.c檔案中定義,但是,在類中的成員變數,那只是宣告,并不是定義,
宣告與定義的區別
宣告:告訴編譯器,我有一個這樣的東西在這,但并未實作,
定義:根據宣告,去實作這個物件的需求,
形象來說就是:
游戲公司建了一個新游戲的檔案夾,并向外發布公告,說未來會發布一款新游戲,也可能檔案夾都還沒建好,
這就是宣告,只是告訴你有,但你不知道游戲劇情、內容、玩法是什么,只知道有這個游戲,
當游戲發售后,你買了,你就可以知道游戲的所有內容,
這就是定義,
宣告是對的是一個屬性,而定義對的是一個物體,
訪問限定符
C++對于類中的成員提供了訪問限定符
private(私有),public(公有),protected(保護),
他們描述了對類成員的訪問控制
根據類創建的物件,都可以訪問到物件類的公有部分,并且只能通過公有函式來訪問物件類的私有成員
訪問控制,也是對物件類的資料的保護,
OOP編程的主要目標之一是隱藏資料,因此這類資料通常放在私有部分,
而組成類介面的成員函式放在公有部分,否則就無法呼叫這些函式,
由于C++兼容C語言,且C++對C語言的結構進行了拓展,如
struct A
{};
class A
{};
在C++中,都是類,
兩者區別
對于struct創建的類,里面的成員默認公有,
對于class 創建的類,里面成員默認私有,
例如:創建一個OW英雄-安娜的類

class Ana
{
public:
void ShowData();//展示資料
void Teletherapy(bool input);//遠程治療
void SleepyNeedle(bool input);//睡針
private:
char Name[20];//名字
int Weight;//體重
int Height;//身高
double Speed;//移動速度
double Blood;//血量
//睡針類 睡針
};
私有資料,我們是不能修改或訪問的,
只能通過公有函式去操作,或通過公有函式來了解私有資料的狀態,
如果我們能直接訪問到私有資料,那么我們也能對私有資料進行修改,但那樣不就成開掛了嗎?(正常人誰打競技游戲開掛),
所以,一般情況下,成員變數都是私有的,想給你用的成員函式是公有的,不希望被呼叫的函式是私有的,
類大小的計算
根據規則:計算類的大小,是不考慮類中成員函式的,只計算類中成員變數,同時還要考慮結構體記憶體對齊規則,也就是計算結構體大小的規則,
結構體記憶體對齊規則
- 第一個成員在與結構體偏移量為0的地址處,
- 其他成員變數要對齊到某個數字(對齊數)的整數倍的地址處,
對齊數 = 編譯器默認的一個對齊數 與 該成員大小的較小值,
VS中默認的對齊數為8 - 結構體總大小為:最大對齊數(所有變數型別最大者與默認對齊引數取最小)的整數倍,
- 如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍處,結構體的整體大小就是
所有最大對齊數(含嵌套結構體的對齊數)的整數倍,
那就計算一下
#include <iostream>
class Ana
{
public:
void ShowData();//展示資料
void Teletherapy(bool input);//遠程治療
void SleepyNeedle(bool input);//睡針
private:
char Name[20];//名字
int Weight;//體重
int Height;//身高
double Speed;//移動速度
double Blood;//血量
};
int main(void)
{
Ana a1;
Ana a2;
std::cout << "a1 = "<<sizeof(a1)<<std::endl;
std::cout << "a2 = "<<sizeof(a2)<< std::endl;
return 0;
}
創建了兩個物件,

編譯器輸出結果,
來看看其記憶體的概念圖

對于類中的成員函式是不會進行計算大小的,
3、this指標

這也是類中一個比較重要的知識點
通過一個日期類來分析(因為日期類比較簡單)
簡單的日期類
#include <iostream>
class Date
{
public:
void Init(int year = 1, int month = 1, int day = 1)//預設引數
{
_year = year;
_month = month;
_day = day;
}
void print(void)
{
std::cout << _year << "年" << _month << "月" << _day<<"日"<<std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d1;//類的實體化
Date d2;
d1.Init(2002,2,5);//物件d1初始化
d2.Init(2019,4,1);//物件d2初始化
d1.print();//列印
d2.print();//列印
}
輸出結果

在呼叫物件類的成員函式初始化物件 d1,d2 ,
在成員函式中可以訪問到物件封裝的成員變數,
再介紹一下
對于類的實體化,創建物件,
Date d1;//類的實體化
Date d2;
系統會為物件d1 d2在堆疊中分配空間,
但是只是物件中的成員變數分配在該物件的空間內,
成員變數與成員函式的空間分布
但是對于成員函式,其是在一個代碼公共區段,而不是在每個物件空間的內部,不會每呼叫一次就分配一塊空間,
每個物件都可以訪問那個區段,

this指標的分析
我們就該想,當呼叫函式時,進入公共區段,如何確定呼叫的是那個物件的成員變數呢?
其實,傳遞過去的引數,不僅僅有顯示宣告定義的引數,還有一個隱藏的this指標,
即,C++編譯器會為每一個非靜態成員函式配備一個隱藏的指標引數,
該指標指向當前物件(函式運行時呼叫該函式的物件),


當然,你也可以這樣寫
void Init(int year = 1, int month = 1, int day = 1)//預設引數
{
this->_year = year;
this->_month = month;
this->_day = day;
}
void print(void)
{
std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
}
但是我們不能自己在引數串列中加一個this指標,因為編譯器自己會傳遞,會處理,要是自己加了,就會造成引數缺失,
這樣也是允許的,不容易搞混,
成員函式呼叫的真實樣子
d1.Init(2002, 2, 5);//->Init(&d1,2002, 2, 5);
d2.Init(2019, 4, 1);//->Init(&d2,2019, 4, 1);
d1.print();//->print(&d1)
d2.print();//->print(&d2)
圖片更好看一些,

注意
而且,對于類的成員變數,最好命名獨特一點,不然,如果和預設引數名字一樣,編譯器會無法識別,
this指標指向不明,從而報錯
this指標的特性
- this指標的型別:型別const
- 只能在成員函式的內部使用
- this指標本質上其實是一個成員函式的形參,是物件呼叫成員函式時,將物件地址作為實參傳遞給this形參,所以物件中不存盤this指標,
- this指標是成員函式第一個隱含的指標形參,一般情況由編譯器通過ecx暫存器自動傳遞,不需要用戶傳遞
this指標儲存在哪?
就我目前微薄的知識,this指標是儲存在堆疊中的,
因為,this指標畢竟是一個形參,而形參和區域變數都是儲存在堆疊中的,
可以隨著成員函式的呼叫而創建,函式結束就銷毀,但是不同編譯器是按照不同的規則的,比如VS就是通過ecx暫存器自動傳遞,
看看這個,來理解
class A
{
public:
void PrintA()
{
cout<<_a<<endl;
}
void Show()
{
cout<<"Show()"<<endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->PrintA();
p->Show();
}
創建一個物件指標,并把它初始化為空,
p->PrintA();如果this->_a是行不通的,本身就是空指標,指向空,根本就不會有指向有權限的空間,算是非法訪問,
但是p->Show();,并未訪問物件內,而是訪問到類的公共區段,不會造成非法訪問,
三、類的六個默認成員函式
類的默認成員函式即使我們
不自己宣告定義,編譯器也會自動創建定義
對于一個什么成員函式都沒有的類,是一個空類,但是,當編譯器處理時,會自動生成6個默認成員函式來防止出錯,

事實上,真正用處大的是前4個,后面兩個,基本上沒多大用處,
1、 建構式
該函式可以完成對物件的初始化,
C++提供這個默認成員函式,是為了解決,沒有初始化就使用物件的問題,
對于日期類
class Date
{
public:
//void Init(int year = 1, int month = 1, int day = 1)//預設引數
//{
// this->_year = year;
// this->_month = month;
// this->_day = day;
//}
void print(void)
{
std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
}
private:
int _year;
int _month;
int _day;
};
如果沒有Init()函式,如果是C語言的話,就會報錯,因為沒有初始化,
就比如這樣
int main(void)
{
Date d1;
Date d2;
d1.print();//->print(&d1)
d2.print();//->print(&d2)
}
我們沒有初始化,卻直接列印,事實上,編譯器輸出

雖然沒有報錯,但是卻輸出了隨機值,這樣也是防止了程式直接崩潰的問題,
建構式的概念
建構式是一個特殊的成員函式,名字與類名相同,創建型別別物件時由編譯器自動呼叫,保證每個資料成員
都有 一個合適的初始值,并且在物件的生命周期內只呼叫一次
這是一個默認成員函式,意思就是如果我們自己不定義,系統就會自動生成,
加上建構式
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
輸出結果

如果我們自己不定義,系統會自動生成,我們定義,系統就呼叫我們定義的函式,
什么?你不相信系統呼叫了建構式
那我就加一個列印,來看看結果
Date(int year = 1, int month = 1, int day = 1)
{
std::cout << "Date()" << std::endl;
_year = year;
_month = month;
_day = day;
}
看看輸出結果:

現在相信了吧,

建構式的特性
建構式雖然名字感覺像是負責為物件創建分配空間,
但實際上,起作用只是完成對物件的成員變數的初始化,防止未初始化就使用而造成的程式崩潰,
特性
- 名字與類名一樣
- 函式無回傳值(注意:是無回傳值,
void是回傳一個空,實際上還是有回傳值,而建構式其根本就沒有回傳型別) - 在物件實體化時,編譯器會自動呼叫對應的建構式(我們沒定義就呼叫默認的)
- 建構式可以多載
雖然可以多載
但是
Date()
{
std::cout << "Date()" << std::endl;
_year = 10;
_month = 1;
_day = 1;
}
Date(int year=1, int month = 1, int day = 1)
{
std::cout << "Date()" << std::endl;
_year = year;
_month = month;
_day = day;
}
卻不受歡迎的,因為編譯器也不知道如何處理,會報錯,
但可以這樣
Date(int year,int month = 1, int day = 1)
{
std::cout << "Date()" << std::endl;
_year = year;
_month = month;
_day = day;
}
在初始化的時候,自己無論如何都要提供一個值,
或者
Date()
{
std::cout << "Date()" << std::endl;
_year = 10;
_month = 1;
_day = 1;
}
Date(int year, int month, int day)
{
std::cout << "Date()" << std::endl;
_year = year;
_month = month;
_day = day;
}
可以運行,但還不如全預設的建構式,
自定義型別與內置型別
內置型別:內置型別就是語法已經定義好的型別:如int/char…,
自定義型別:是我們使用class/struct/union自己定義的型別
對于普通的成員變數而言,其默認的建構式,感覺效果并不怎樣,但是,如果有一個成員變數也是類?(自定義型別),
#include <iostream>
class A
{
public:
A()
{
std::cout << "A()" << std::endl;
}
private:
int a;
};
class Date
{
public:
void print(void)
{
std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
}
private:
int _year;
int _month;
int _day;
A a1;
};
int main(void)
{
Date d1;
Date d2;
d1.print();//->print(&d1)
d2.print();//->print(&d2)
}
其輸出結果為

像上面那段代碼,對于類Date,我們自己沒有定義建構式,而編譯器會生成默認建構式,取對成員變數初始化,
而對于內置型別,直接初始化一個隨機值,防止程式崩潰,
但是對于自定義型別,編譯去會取呼叫它的建構式對自定義型別的成員變數進行初始化
我定義了A的建構式是為了可以更好的看到自定義型別的建構式被呼叫,
對于內置型別,編譯器基本不會處理,
對于自定義型別,編譯器會去呼叫自定義型別的建構式
大多數情況下,默認建構式都不太頂用,最好還是自己寫一個滿足要求的,
對于建構式的建議
一般而言,建構式有三種(不需要傳遞引數就可以呼叫的函式)
- 編譯器默認生成的(直接賦隨機值的那種)
- 我們自己寫的無引數的
Date()
{
_year = 10;
_month = 1;
_day = 1;
}
3.我們自己寫的全預設的
Date(int year=1, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
不過,就我而言,還是推薦使用全預設的建構式,
因為,這個完全可以兼容前面兩種,
既可以帶參,
也可以不帶參,
還可以帶部分參,
豈不美哉!!!!!

猩猩看了都說好
2、解構式
這個和名字其實不太一樣,解構式并不是負責銷毀物件的空間,只是做一些,資源清理的作業,
就跟內置型別的物件一樣,類物件的銷毀都是由編譯器完成的,其本身不具備這種操作,
解構式的定義
解構式:與建構式功能相反,解構式不是完成物件的銷毀,區域物件銷毀作業是由編譯器完成的,
而物件在銷毀時會自動呼叫解構式,完成類的一些資源清理作業,
解構式的特性
特征:
- 解構式名是在類名前加上字符 ~,
- 無引數無回傳值,不能多載,
- 一個類有且只有一個解構式,若未顯式定義,系統會自動生成默認的解構式,
- 物件生命周期結束時,C++編譯系統系統自動呼叫解構式,
對于日期類,額,這個解構式基本沒什么作用,但是對于堆疊、佇列等資料結構的類還是相當有意義的,
清理資源可以防止記憶體泄漏等等,
比如陣列堆疊呼叫結束后,解構式就會自動呼叫,去銷毀其陣列,就不會因為我們忘記呼叫free()而造成記憶體泄漏
對于內置型別,解構式不會起什么作用
對于自定義型別,解構式會清理物件的資源,進行析構,
多個物件,解構式呼叫的順序
對于日期類
class Date
{
public:
Date(int year=1, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
void print(void)
{
std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d1(2002);
Date d2;
Date d3;
Date d4;
d1.print();//->print(&d1)
d2.print();//->print(&d2)
}
物件d1 d2 d3 d4都是通過函式調用在堆疊上為物件分配空間,當其生命周期結束后,編譯器就會銷毀物件在堆疊上的空間,
但是,物件銷毀的順序是什么呢?資料結構-堆疊
要知道,這幾個物件都是存放在堆疊上的,
對于作業系統中的堆疊與資料結構中的堆疊,基本沒啥關系,但是兩者都符合后進先出的條件

我們知道資料結構堆疊對于資料元素的處理規則是后進先出,則,對于在堆疊上的物件而言,也會符合這個要求,
在生命周期結束后,
先創建的物件,后呼叫解構式;
后創建的物件,先呼叫解構式,
則
解構式呼叫順序為
- d4
- d3
- d2
- d1
要注意,當生命周期到的時候,
3、拷貝建構式

回聲開大:人格復制
就是在一定時間內(好像最近時間又削短了),回聲可以復制敵方任意一個英雄,并擁有其所有技能(充能時間也大大縮短),
就相當于,在這一定時間內,你所操作的英雄換了一個類物件,
或者說,
對面一個被你選中的英雄的所有物件資料被你Ctrl C + Ctrl V,完全被你拷貝過來了,
換句話說,在這一定時間內,你根據他的類,創建了一個一模一樣的物件,
拷貝建構式的特性
特性如下:
- 拷貝建構式是建構式的一個多載形式,即函式名也是類名,不過其引數串列是與物件有關,
- 拷貝建構式的引數只有一個且必須使用參考傳參,使用傳值方式會引發無窮遞回呼叫,
為什么傳值傳參會引發無窮遞回呼叫
正常的拷貝構造-傳參考呼叫
對于參考的介紹請參考C++入門語法
Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date d1;
Date d2(d1);
如果是傳值呼叫
Date(Date d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
因為,
對于形參要開辟一個物件空間,再將實參物件傳遞過來,將實參拷貝到這個形參空間,但,又需要呼叫拷貝建構式,又需要開辟一個形參空間,,,,就這樣不斷拷貝,不斷開辟空間,無限遞回,
概念圖

而對于傳參考呼叫 :
傳遞的是要拷貝的物件的別名,只是拿到了目標物件的別名,可以對其進行訪問,并且,這個程序并沒有再開辟空間,
其實,也可以傳址呼叫
Date(Date* d)
{
_year = d->_year;
_month = d->_month;
_day = d->_day;
}
Date d1;
Date d2(&d1);
雖然也可以達到拷貝的效果,但是,其語法上并不是拷貝建構式,而且比較麻煩,畢竟指標這容易出錯,
拷貝構造與傳參考
如果是對于一個物件而言,
void test1(Date d)
{
}
void test2(Date& d)
{
}
test1(d1);
test2(d1);
對于test1()而言,是傳值呼叫,要先開辟一個空間,將物件d1拷貝過去,就需要呼叫拷貝建構式,
但是test2(),是傳參考,直接就可以訪問到源空間,根本不需要呼叫拷貝建構式,這樣對于效率也要快好多,
而且,拷貝構造畢竟是傳參考,如果不小心會改變源空間的值,因為拷貝構造也是在類的公共區段,是在類內,不會被訪問限定,
那么最好加一個const去防止源物件的成員變數被修改,
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
這樣,哪怕寫反了復制順序,編譯器自己也會報錯,而不會影響程式,
深拷貝與淺拷貝
拷貝構造也是一個默認成員函式
如果我們不自己定義,編譯器也會自動生成,
如果是一個日期類,類的成員變數全部都是內置型別的那種,編譯器會自動生成一個拷貝建構式,一個個直接拷貝過去,簡稱:值拷貝或淺拷貝
#include <iostream>
class A
{
public:
A()
{
std::cout << "A()" << std::endl;
}
private:
int a;
};
class Date
{
public:
Date(int year=1, int month=1, int day=1)
{
_year = year;
_month = month;
_day = day;
}
void print(void)
{
std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d1(2002);
Date d2(d1);
d1.print();//->print(&d1)
d2.print();//->print(&d2)
}

這就是輸出結果,依舊完成了拷貝構造,
但是對于一些自定義型別而言會出大問題,
比如:陣列堆疊類
class Stack
{
private:
int* arr;
int size;
int capacoty;
};
當其對于堆疊物件進行拷貝構造,對這三個內置型別進行淺拷貝

這樣淺拷貝完全不符合我們的要求,我們是希望通過拷貝,可以得到另一個與原物件一模一樣的物件,
而這樣的淺拷貝,當對物件 s1 進行改變時,物件 s2 也會受到改變,
就像
int a=10;
int b=a;
這是我們希望達到的效果,
而對,像堆疊、佇列這一類的類,淺拷貝都無法達到正確效果,只有依靠深拷貝去完成,(嘿嘿,這個下次再完成)

而如果日期類中有自定義型別的成員變數,其會去呼叫自定義型別的拷貝建構式,來完成對自定義型別的拷貝,
4、賦值運算子多載
這也是類的默認成員函式
- 函式多載:支持定義同名函式
- 運算子多載:為了讓自定義型別可以像內置型別一樣去使用運算子
C++為了增強代碼的可讀性引入了運算子多載,運算子多載是具有特殊函式名的函式,也具有其回傳值類
型,函式名字以及引數串列,其回傳值型別與引數串列與普通的函式類似,
C++為了讓自定義型別的物件也能像內置型別的物件一樣進行例如+、-、*、/、=、==的一系列操作,提供了運算子多載這種特性,
運算子多載的特性
特性如下:
- 函式名字為:關鍵字
operator后面接需要多載的運算子符號, - 函式原型:回傳值型別 operator運算子(引數串列)
- 不能通過連接其他符號來創建新的運算子:比如operator@
- 多載運算子必須有一個型別別或者列舉型別的運算元
- 用于內置型別的運算子,其含義不能改變,例如:內置的整型+,不 能改變其含義
- 作為類成員的多載函式時,其形參看起來比運算元數目少1成員函式的
運算子有一個默認的形參this,限定為第一個形參
注意:.* 、:: 、sizeof 、?: 、. 注意以上5個運算子不能多載,
像這類函式,都是定義在類中,不然,訪問不到成員變數,
繼續拿日期類舉例
#include <iostream>
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
bool operator==(const Date& d)
{
return (_year == d._year) && (_month == d._month) && (_day = d._day);
}
void print(void)
{
std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d1(2020,1,3);
Date d2(d1);
std::cout << (d1 == d2) << std::endl;
d1.print();//->print(&d1)
d2.print();//->print(&d2)
}
在類中定義運算子多載函式,使用時,就像內置型別一樣使用運算子即可,
同時,
要注意,在自定義型別使用運算子時,第一個引數會是this指向的物件,
第一個引數
this指標是左運算元,第二個引數是右運算元
再來看一個陣列類,有點神奇,
#include <iostream>
class Array
{
public:
Array()
{
for (int i = 0; i < 10; i++)
{
_arr[i] = i;
}
_size = 10;
}
int& operator[](int pos)
{
return _arr[pos];
}
int GetSize(void)
{
return _size;
}
private:
int _arr[10];
int _size;
};
int main(void)
{
Array arr;
for (int i = 0; i < arr.GetSize(); i++)
{
std::cout << arr[i] << " ";
}
return 0;
}

這個[]運算子多載,讓物件可以像陣列一樣訪問,如果是回傳參考的話
甚至可以對陣列的值進行修改,就像普通陣列那樣操作
復制拷貝與拷貝構造
內置型別
int a=10;
int b=a;//拷貝構造
//
int a=10;
int b;
b=a;//復制拷貝
自定義型別
Date d1(2021,6,5);
Date d2(d1);//拷貝構造
//
Date d1(2021,6,5);
Date d2;
d2=d1;//復制拷貝
復制拷貝:
物件已經初始化好后,再把一個物件拷貝給另一個物件
拷貝構造:
在物件還在創建時,拿另一個同類物件去初始化這一個物件
對于自定義型別,
拷貝構造呼叫拷貝建構式,
而復制拷貝呼叫=運算子多載函式
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
為了讓自定義型別能夠像內置型別一樣,連續賦值,與防止自己給自己賦值,
a=b=c;
畢竟,賦值運算子多載函式是默認成員函式,即使我們不寫,編譯器也會自動生成,
#include <iostream>
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
bool operator==(const Date& d)
{
return (_year == d._year) && (_month == d._month) && (_day = d._day);
}
void print(void)
{
std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
}
private:
int _year;
int _month;
int _day;
};
int main(void)
{
Date d1(2020,1,3);
Date d2;
d2=d1;
d1.print();//->print(&d1)
d2.print();//->print(&d2)
}
輸出結果

對于內置型別的復制拷貝,編譯器會通過默認的賦值運算子多載完成淺拷貝,
對于自定義型別的復制拷貝,編譯器會呼叫自定義型別的賦值運算子多載完成拷貝,
5、對前4個默認成員函式的總結
- 建構式:完成物件的初始化,但大部分情況下,還是需要我們自己去定義建構式
- 解構式:在對像生命周期結束時,對物件的資源進行清理
- 拷貝建構式:建構式的多載,其中一個物件還未初始化,完成對物件之間的拷貝(深/淺拷貝)
- 賦值運算子多載:也是拷貝行為,但是是基于兩個物件已經被初始化的情況下,
對于構造和析構的特性是類似的,
對于內置型別,我們不寫,編譯器基本不會處理,
而自定義型別,編譯器會呼叫自定義型別的析構和構造
對于拷貝構造和賦值運算子多載的特性是類似的,
對于內置型別,編譯器會進行淺拷貝(直接賦值),
對于自定義型別,編譯器要呼叫自定義型別的拷貝構造和賦值運算子多載,進行深拷貝,

6、const成員函式
特性
將const修飾的類成員函式稱之為const成員函式,const修飾類成員函式,
實際修飾該成員函式隱含的this指標,表明在該成員函式中不能對類的任何成員進行修改,
注意:只有成員函式才能加const,如構造,析構等等都不能加
就是當我們確幸該成員函式中不會,也不會造成物件的任何成員變數被修改
就使用const去修飾成員函式,這樣也是為了保險,防止錯誤操作,
比如:
bool operator==(const Date& d)const
{
return (_year == d._year) && (_month == d._month) && (_day = d._day);
}
幾個問題理解const
- const物件可以呼叫非const成員函式嗎?
- 非const物件可以呼叫const成員函式嗎?
- const成員函式內可以呼叫其它的非const成員函式嗎?
- 非const成員函式內可以呼叫其它的const成員函式嗎?
被const修飾后,對成員函式或者物件所能操作的權限就被縮小,而且,權限不能放大,且不能有放大權限的可能,
對于題目
- 不可以,因為非const成員函式中其this指標沒有const修飾,也就存在放大權限的可能,可能會對this指標指向的成員變數進行修改,
- 可以,這屬于權限縮小,而縮小權限,是不會報錯的,
- 不可以,本身this指標就是const,當呼叫非const成員函式后,其隱含的this指標未被const修飾,就存在權限放大的可能,
- 可以,this指標在const成員函式中被const修飾,屬于權限縮小,
7、取地址運算子多載
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
}
這個運算子基本沒什么那啥價值,根本不需要我們去實作它,默認的成員函式就完全夠用了,
保護物件不被讀取地址
但是,如果你不希望物件的地址被讀出來,要對其進行保護
class Date
{
public :
Date* operator&()
{
return nullptr ;
}
const Date* operator&()const
{
return nullptr ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
}
這樣,當別人想讀物件的地址時,就只能讀出00000000,可以對你的物件進行保護,
四、一個完整的日期類(選讀,可跳)
直接看代碼,這里面是學會對運算子多載的應用,本身沒什么演算法知識,
日期類.cpp
#include "date.h"
void test(void)
{
Date d1(-11,5,26);
Date d2(2025,1,1);
d1.print();
//date d3= d1 + 4;//->operator(&d2,4)
//d3 = d1 + 3;//->operator(&d2,4)
d3.print();
//date d3 = d1 - 10;
//d1.print();
//d2.print();
d3.print();
//int days = d1 - d2;
//std::cout << days;
Date d3=d2++;
d3.print();
d2.print();
if (d3 >= d2)
{
std::cout << "d3<=d2" << std::endl;
}
//d1 += 4;//->operator+=(&d1,4)
//d1 += 1;
/*d1.print();
d2.print();
d3.print();*/
}
int main(void)
{
test();
}
Date.cpp
#include "Date.h"
inline int MonthDays(int year, int month)
{
static int DayArrary[] = { 31,28,31,30,31,30,31,31,30,31,30,31 };
int day = DayArrary[month - 1];
if (month == 2 && (((year % 4 == 0) && (year % 100 != 0)) || year % 400 == 0))
{
day = 29;
}
return day;
}
Date::Date(int year, int month, int day)
{
if (year != 0 && month <= 12 && month > 0 && day <= MonthDays(year, month) && day > 0)
{
_year = year;
_month = month;
_day = day;
}
else
{
std::cout << "date illegal" << std::endl;
assert(false);
}
}
void Date::print(void)
{
if (_year >= 1)
std::cout << "公元 ";
else
std::cout << "公元前 ";
std::cout << _year << "年" << _month << "月" << _day<<"日"<<std::endl;
}
Date::Date(const Date& d)//拷貝構造
{
_year =d._year;
_month = d._month;
_day = d._day;
}
Date& Date::operator+=(int day)//+=運算賦多載
{
_day += day;
while (_day > MonthDays(_year, _month))
{
_day -= MonthDays(_year, _month);
_month++;
if (_month > 12)
{
_year++;
if (_year == 0)
_year = 1;
_month = 1;
}
}
return *this;
}
Date Date::operator+(int day)//+運算子多載
{
//建立一個臨時物件
Date tmp(_year,_month,_day);//
tmp += day;//->operator+=(&tmp,day);//對這個臨時物件的操作
return tmp;
}
Date& Date::operator-=(int day)//-運算子多載
{
_day -= day;
while (_day<=0)//終止條件->>_day > 0 && _day <= MonthDays(_year, _month)
{
_month--;
if (_month < 1)
{
_year--;
if (_year == 0)
_year = -1;
_month = 12;
}
_day += MonthDays(_year, _month);
}
return *this;
}
Date Date::operator-(int day)//-運算子多載
{
Date tmp = (*this);
tmp -= day;
return tmp;
}
//日期之間的天數
int YearDays(int year)
{
if (((year % 4 == 0) && (year % 100 != 0)) || year % 400 == 0)
return 366;
return 365;
}
int Date::operator-(Date& d)//日期減日期
{
int Days = 0;
Date bigDate;
Date smallDate;
if (_year > d._year)
{
bigDate = *this;
smallDate = d;
}
else if (_year < d._year)
{
bigDate = d;
smallDate = *this;
}
else//同一年
{
//小月的剩余天數加上中間月份的天數,再加上大月的天數
if (_month > d._month)
{
bigDate = *this;
smallDate = d;
}
else if (_month < d._month)
{
bigDate = d;
smallDate = *this;
}
else//同一個月
{
int tmp = abs(_day - d._day);
return tmp;
}
}
int yearTmp = bigDate._year - smallDate._year - 1;
int monthTmp = bigDate._month - smallDate._month - 1;
for (int i = 1; i <= yearTmp; ++i)
{
Days += YearDays(smallDate._year + i);
}
Days += MonthDays(smallDate._year, smallDate._month) - smallDate._day;
for (int i = smallDate._month + 1; i <= 12; i++)
Days += MonthDays(smallDate._year, i);
for(int i=1;i<bigDate._month;i++)
Days+= MonthDays(smallDate._year, i);
Days += bigDate._day;
return Days;
}
Date& Date::operator++()//前置++
{
*this += 1;
return *this;
}
Date Date::operator++(int) 后置++
{
Date tmp(*this);
*this += 1;
return tmp;
}
Date Date::operator--(int)// 后置--
{
Date tmp(*this);
--* this;
return tmp;
}
Date& Date::operator--()// 前置--
{
*this -= 1;
return *this;
}
// >=運算子多載
bool Date::operator>=(const Date& d)
{
if (_year > d._year)
return 1;
else if(_year<d._year)
return 0;
else
{
if (_month > d._month)
return 1;
else if (_month < d._month)
return 0;
else
{
if (_day > d._day)
return 1;
else if (_day < d._day)
return 0;
else
return 1;
}
}
}
// <運算子多載
bool Date::operator<(const Date& d)
{
if (_year < d._year)
return 1;
else if (_year > d._year)
return 0;
else
{
if (_month < d._month)
return 1;
else if (_month > d._month)
return 0;
else
{
if (_day < d._day)
return 1;
else if (_day > d._day)
return 0;
else
return 0;
}
}
}
// <=運算子多載
bool Date::operator<=(const Date& d)
{
if (_year < d._year)
return 1;
else if (_year > d._year)
return 0;
else
{
if (_month < d._month)
return 1;
else if (_month > d._month)
return 0;
else
{
if (_day < d._day)
return 1;
else if (_day > d._day)
return 0;
else
return 1;
}
}
}
// !=運算子多載
bool Date::operator!=(const Date& d)
{
if (_year == d._year && _month == d._month && _day == d._day)
return 0;
return 1;
}
這么長?源氏看來都要拔刀了

Date.h
#pragma once
#include <iostream>
#include <cassert>
class Date
{
public:
//建構式
Date(int year = 1, int month = 1, int day = 1);
Date(const Date& d);//拷貝構造
Date& operator+=(int day);//+=運算賦多載
Date operator+(int day);//+運算子多載
Date& operator-=(int day);//-=運算子多載
Date operator-(int day);//-運算子多載
int operator-(Date& d);//日期減日期
Date& operator++();//前置++
Date operator++(int); 后置++
Date operator--(int);// 后置--
Date& operator--();// 前置--
// >運算子多載
bool operator>(const Date& d);
// ==運算子多載
bool operator==(const Date& d);
// >=運算子多載
bool operator>=(const Date& d);
// <運算子多載
bool operator<(const Date& d);
// <=運算子多載
bool operator<=(const Date& d);
// !=運算子多載
bool operator!=(const Date& d);
void print(void);
private:
int _year;
int _month;
int _day;
};
實作>,<之類的運算子多載過于瑣碎,大家小心觀看,因為我當時的順序不一樣,寫多了,
五、友元與>>、<<運算子多載

為什么cin>>、cout<<可以自動識別內置型別
因為,在庫中早已經多載好了,并且可以自動識別型別,各個函式之間構成多載,
cout

我以我LZ的英文水平翻譯看看,cout是一個ostream類的物件,可以通過運算子<<將格式化或未格式化的資料通過成員函式寫入到,,,,我就不知道咋翻了,
cin

很明顯,cin是一個istream的類的物件,通過<<與成員函式完成對輸入流的寫入,
這些都不重要,大概了解是啥就行,
cout是根據ostream創建的一個物件
cin是根據istream創建的一個物件
這樣,我們可以完成對自定義型別簡化為內置型別.
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << d._month << d._day;
return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
_cin >> d._year >> d._month >> d._day;
return _cin;
}
按照這樣的形式,我們可以做到像內置型別一樣直接輸入,
如果,要定義在類中,我們的cout與cin都是運算子的左邊,導致,this指標就無法指向類中的成員變數,
所以定義在類中并不合適,只能定義在類外,
但是,在類外,我們就無法訪問到類中的成員變數,
所以,引入一個友元
友元函式
提供友元函式,
友元提供了一種突破封裝的方式,有時提供了便利,但是友元會增加耦合度,破壞了封裝,所以友元不宜多用,
class Date
{
public:
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
bool operator==(const Date& d)
{
return (_year == d._year) && (_month == d._month) && (_day = d._day);
}
void print(void)
{
std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout << d._year << d._month << d._day;
return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
_cin >> d._year >> d._month >> d._day;
return _cin;
}
int main()
{
Date d;
cin>>d;
cout<<d<<endl;
return 0;
}
這樣,友元可以突破封裝,相當于開了一個后門,
注意:
- 友元函式可訪問類的私有和保護成員,但不是類的成員函式
- 友元函式不能用const修飾
- 友元函式可以在類定義的任何地方宣告,不受類訪問限定符限制
- 一個函式可以是多個類的友元函式
- 友元函式的呼叫與普通函式的呼叫和原理相同
友元類
友元類的所有成員函式都可以是另一個類的友元函式,都可以訪問另一個類中的非公有成員,
例如:
class Time;//時間類前置宣告
class Date
{
public:
friend class Time;//宣告時間類是日期類的友元
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
bool operator==(const Date& d)
{
return (_year == d._year) && (_month == d._month) && (_day = d._day);
}
void print(void)
{
std::cout << _year << "年" << _month << "月" << _day << "日" << std::endl;
}
private:
int _year;
int _month;
int _day;
};
class Time
{
public:
Time(int second = 0, int minute = 0, int hour = 1)
{
_second = second;
_minute = minute;
_hour = hour;
}
private:
int _second;
int _minute;
int _hour;
};
時間類是日期類的友元,則,時間類中的所有成員函式都可以說日期類的友元函式,
則,在時間類中可以訪問到日期類在的私有成員變數,
但是,日期類依舊不能訪問到時間類中的私有成員變數,
總結:
- 友元關系是單向的,不具有交換性,(如上)
- 友元關系不能傳遞
如果B是A的友元,C是B的友元,則不能說明C時A的友元,

六、深入了解建構式
在創建物件的時候,物件通過呼叫建構式來初始化物件,
C++在建構式上提供了兩種方案來初始化成員變數,
1、函式體內初始化
就是我們常見的初始化的模式
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
2、初始化串列
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
此模式的初始化規則:
初始化串列:以一個冒號開始,接著是一個以逗號分隔的資料成員串列,每個"成員變數"后面跟一個放在括號中的初始值或運算式,
3、為什么要多此一舉準備兩種初始化模式呢?
事實上,雖然函式體內呼叫建構式后,物件中已經有了一個初始值,但是不能將其稱作為類物件成員的初始化,建構式體中的陳述句只能將其稱作為賦初值,而不能稱作初始化,因為初始化只能初始化一次,而建構式體內可以多次賦值,
例如:

初始化串列兩次初始化,編譯器直接報錯,因為在串列中初始化只能初始化一次,但是,

編譯器卻可以允許,
來看看結果

這已經不叫初始化了,而是叫賦值,初始化畢竟只能初始化一次,
這也再一次證明了,函式體內“初始化”,準確來叫應該是賦值,而非初始化,
初始化串列才是真正的初始化成員變數,
從而可以推出,創建一個物件,對于物件而言,其中的成員變數是在初始化串列的時候定義的,
初始化串列才是單個成員變數定義的階段
而且,對于有些成員變數,只有在初始化的時候才能賦初值,且之后就不能修改了,
例如
- const修飾的成員變數
- 參考成員變數
- 自定義型別的成員變數(沒有默認建構式)
對于沒有要求一定要在定義的時候初始化的成員變數,可以在函式體內”初始化“,
例如這樣
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
,i(10)
,ret(_year)
{
_year = 100;
_year = 200;
}
private:
int _year;
int _month;
int _day;
const int i;
int& ret;
};
對于第三種,沒有默認建構式的自定義成員變數
也就是說,自定義類中沒有不需要傳參就能初始化的建構式,也就是說,忘記傳參就會導致報錯,
這樣將自定義成員變數,顯示定義到初始化串列中,可以我i自定義成員變數初始化,還不會報錯,
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
,i(10)
,ret(_year)
,t(0)
{
_year = 100;
_year = 200;
}
private:
int _year;
int _month;
int _day;
const int i;
int& ret;
Time t;
};
class Time
{
public:
Time(int t)
{
_t = t;
}
private:
int _t;
};
如果不顯示初始化串列,編譯器就會報錯沒有默認的建構式,
還有就是注意,初始化串列是成員函式定義的階段,無論是哪種“初始化”模式,最終都會經歷初始化串列這個階段,
所以還是推薦使用初始化串列,保險一點
初始化串列初始化的順序
成員變數在類中宣告次序就是其在初始化串列中的初始化順序,與其在初始化串列中的先后次序無關
這個知識點有點繞,但還蠻重要的,
學會了嗎?不會?死神手把手教你,他很會繞的

文章關于類與物件的知識比較零碎,又比較多,還比較重要,其實更多是對我自己的總結,
看到這也挺不容易的,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/286495.html
標籤:其他
