網(wǎng)站建設(shè)的作用網(wǎng)站流量來源
?個人主頁: Yohifo
🎉所屬專欄: C++修行之路
🎊每篇一句: 圖片來源
I do not believe in taking the right decision. I take a decision and make it right.
- 我不相信什么正確的決定。我都是先做決定,然后把事情做好。
文章目錄
- 📘前言
- 📘正文
- 📖初始化列表
- 🖋?原初始化方式
- 🖋?使用初始化列表
- 🖋?注意事項(xiàng)
- 📖explicit關(guān)鍵字
- 🖋?隱式轉(zhuǎn)換
- 🖋?限制轉(zhuǎn)換
- 📖static修飾
- 🖋?static在類中
- 📖匿名對象
- 🖋?使用場景
- 📖友元
- 🖋?友元函數(shù)
- 🖋?友元類
- 📖內(nèi)部類
- 🖋?特性
- 📖編譯器優(yōu)化
- 🖋?參數(shù)優(yōu)化
- 🖋?返回優(yōu)化
- 🖋?編碼技巧
- 📖再次理解類和對象
- 📘總結(jié)
📘前言
在前兩篇關(guān)于類和對象的文章中,我們學(xué)習(xí)了C++
類的基本形式、對象的創(chuàng)建與使用以及每個類中都有的六大天選之子:默認(rèn)成員函數(shù)
,現(xiàn)在對類的基本框架已經(jīng)搭好,關(guān)于類和對象的學(xué)習(xí)還存在一些細(xì)節(jié),深入理解這些細(xì)節(jié)就是本文的主要目的
📘正文
先從上篇文章中的結(jié)論開始學(xué)習(xí)
📖初始化列表
初始化列表是祖師爺認(rèn)證的成員變量初始化位置,初始化列表緊跟在默認(rèn)構(gòu)造函數(shù)之后,形式比較奇怪:主要通過 :
、,
和 ()
實(shí)現(xiàn)初始化
class Date
{
public:Date(int year = 1970, int month = 1, int day = 1):_year(year) //以下三行構(gòu)成初始化列表, _month(month), _day(day){//………}
private:int _year;int _month;int _day;
};
學(xué)習(xí)初始化列表前先來簡單回顧下原初始化方式
🖋?原初始化方式
之前我們的默認(rèn)構(gòu)造函數(shù)是這樣的:
class Date
{
public:Date(int year = 1970, int month = 1, int day = 1){//此時是賦值,變量在使用前,仍然是隨機(jī)值狀態(tài)_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
實(shí)例化一個對象,調(diào)試結(jié)果如下
正式賦值前已被初始化為隨機(jī)值
并且如果我們的成員變量中新增一個 const
修飾的成員:
private:int _year;int _month;int _day;const int _ci
程序運(yùn)行結(jié)果就會變成這樣:
直接編譯報(bào)錯了,證明當(dāng)前初始化方式存在很大問題
原因:
- 原
默認(rèn)構(gòu)造函數(shù)
是以賦值
的方式實(shí)現(xiàn)“初始化”
的 - 被賦值的前提是已存在,而存在必然伴隨著
初始化
行為 - 此時由編譯器負(fù)責(zé),也就是編譯器的慣用手段:給變量置以
隨機(jī)值
實(shí)現(xiàn)初始化 - 成員變量在被賦值前已經(jīng)被初始化了
const
修飾的成員具有常性,只能初始化一次- 也就意味著此時的成員
_ci
已經(jīng)被初始化為隨機(jī)值
,并且被const
修飾,具有常性,無法被賦值 - 總結(jié): 原賦值的初始化方式在某些場景下行不通
原賦值初始化方式的缺點(diǎn):
- 無法給
const
修飾成員初始化 - 無法給
引用
成員初始化 - 無法給
自定義
成員初始化(且該類沒有默認(rèn)構(gòu)造函數(shù)時
)
此時祖師爺看不下去了,決定打造一種新的初始化方式:初始化列表
,并為初始化列表
指定了一個特殊位置:默認(rèn)構(gòu)造函數(shù)
之后
🖋?使用初始化列表
初始化列表基本形式:
- 緊跟在
默認(rèn)構(gòu)造函數(shù)
之后,首先以;
開始 - 初始化格式為
待初始化對象(初始值)
- 之后待初始化成員都以
,
開頭 - 不存在結(jié)尾符號,除了第一個成員用
;
開頭外,其他成員都用,
開頭
class Date
{
public:Date(int year = 1970, int month = 1, int day = 1, const int ci = 0, const int& ref = 0):_year(year) //以下三行構(gòu)成初始化列表, _month(month), _day(day), _ci(ci) //const 成員能初始化, _ref(ref) //引用成員也能初始化{//………}
private:int _year;int _month;int _day;const int _ci;const int& _ref;
};
在初始化列表的加持下,程序運(yùn)行結(jié)果如下
進(jìn)入默認(rèn)構(gòu)造函數(shù)體內(nèi)時,成員變量已被初始化
初始化列表
能完美彌補(bǔ)原賦值初始化的缺點(diǎn)
如此好用的初始化方式為何不用呢?
祖師爺推薦: 盡量使用初始化列表進(jìn)行初始化,全能又安心
強(qiáng)大的功能靠著周全的規(guī)則支撐,初始化列表有很多注意事項(xiàng)(使用規(guī)則)
🖋?注意事項(xiàng)
使用方式
;
開始,
分隔,()
內(nèi)寫上初始值
注意
初始化列表
中的成員只能出現(xiàn)一次初始化列表
中的初始化順序取決類中的聲明順序- 以下幾種類型必須使用
初始化列表
進(jìn)行初始化const
修飾引用
類型自定義類型
,且該自定義類型
沒有默認(rèn)構(gòu)造函數(shù)
建議
- 優(yōu)先選擇使用
初始化列表
列表
中的順序與聲明
時的順序保持一致
規(guī)范使用初始化列表,高高興興使用類
📖explicit關(guān)鍵字
explicit
是新的關(guān)鍵字,常用于修飾 默認(rèn)構(gòu)造函數(shù)
,限制隱式轉(zhuǎn)換,使得程序運(yùn)行更加規(guī)范
🖋?隱式轉(zhuǎn)換
所謂隱式轉(zhuǎn)換就算編譯器在看到賦值雙方類型不匹配時,會將創(chuàng)建一個同類型的臨時變量,將 =
左邊的值拷貝給臨時變量,再拷貝給 =
右邊的值,比如:
int a = 10;
double b = 3.1415926;
a = b; //成功賦值,將會截取浮點(diǎn)數(shù) b 的整數(shù)部分拷貝給臨時變量,再賦值給 a
具體賦值過程如下
需要借助一個同類型的臨時變量
將此思想引入類中,假設(shè)存在這樣一個類:
class A
{
public://默認(rèn)構(gòu)造函數(shù)A(int a = 0):_a(a){//表示默認(rèn)構(gòu)造函數(shù)被調(diào)用過cout << "A(int a = 0)" << endl;}//默認(rèn)析構(gòu)函數(shù)~A(){_a = 0;//表示默認(rèn)析構(gòu)函數(shù)已被調(diào)用cout << "~A" << endl;}//拷貝構(gòu)造函數(shù)A(const A& a){_a = a._a;cout << "A(const A& a)" << endl;}//賦值重載函數(shù)A& operator=(const A& a){if(this != &a){_a = a._a;}cout << "A& operator=(const A& a)" << endl;return *this;}private:int _a;
};
以下語句的賦值行為是合法的
int main()
{A aa1 = 100; //注:此時整型 100 能賦給自定義類型return 0;
}
合法原因:
- 類中只有一個整型成員
- 賦值時,會先生成同類型臨時變量,即調(diào)用一次構(gòu)造函數(shù)
- 再調(diào)用拷貝構(gòu)造函數(shù),將臨時變量的值拷貝給
aa1
我們可以看看打印結(jié)果是否真的如我們想的一樣
結(jié)果:只調(diào)用了一次構(gòu)造函數(shù)
難道編譯器偷懶了?
- 并不是,實(shí)際這是編譯器的優(yōu)化
- 與其先生成臨時變量,再拷貝,不如直接對目標(biāo)進(jìn)行構(gòu)造,這樣可以提高效率
這是編譯器的優(yōu)化行為,大多數(shù)編譯器都支持
看代碼會形象些:
A aa1 = 100; //你以為的
A aa1(100); //實(shí)際編譯器干的,優(yōu)化!
單參數(shù)類賦值時編譯器有優(yōu)化,那么多參數(shù)類呢?
class B
{
private:int _a;int _b;int _c;
}
多參數(shù)類編譯器也會有優(yōu)化
B bb1 = {1, 2, 3}; //你以為的
B bb1(1, 2, 3); //實(shí)際上編譯器優(yōu)化的
編譯器是這樣認(rèn)為的:構(gòu)造臨時變量+拷貝構(gòu)造不如讓我直接給你構(gòu)造
這是編譯器針對隱式轉(zhuǎn)換做出的優(yōu)化行為
不難發(fā)現(xiàn),這樣的隱式轉(zhuǎn)換雖然方便,但會影響代碼的可讀性和規(guī)范性,我們可以通過關(guān)鍵字explicit
限制隱式轉(zhuǎn)換行為
🖋?限制轉(zhuǎn)換
在默認(rèn)構(gòu)造函數(shù)
前加上explicit
修飾
class A
{
public://默認(rèn)構(gòu)造函數(shù)//限制隱式轉(zhuǎn)換行為explicit A(int a = 0):_a(a){//表示默認(rèn)構(gòu)造函數(shù)被調(diào)用過cout << "A(int a = 0)" << endl;}
private:int _a;
};
此時再次采用上面那種賦值方式會報(bào)錯
A aa1 = 100; //報(bào)錯,此時編譯器無法進(jìn)行隱式類型轉(zhuǎn)換,優(yōu)化也就不存在了
何時采用 explicit
修飾?
- 想提高代碼可讀性和規(guī)范性時
📖static修飾
static
譯為靜態(tài)的,修飾變量時,變量位于靜態(tài)區(qū),生命周期增長至程序運(yùn)行周期
static
有很多講究,可不敢隨便亂用:
- 修飾普通局部變量時,使其能在全局使用
- 修飾全局變量時,破壞其外部鏈接屬性
- static 修飾時,只能被初始化一次
static
不能隨便亂用
🖋?static在類中
類中被 static
修飾的成員稱為 靜態(tài)成員變量
或 靜態(tài)成員函數(shù)
靜態(tài)成員
為所有類對象所共享,不屬于某個具體的對象,存放在靜態(tài)區(qū)
靜態(tài)成員變量
必須在類外定義,定義時不添加static
關(guān)鍵字,類中只是聲明
- 類
靜態(tài)成員
可用類名::靜態(tài)成員
或者對象.靜態(tài)成員
來訪問 靜態(tài)成員函數(shù)
沒有隱藏的this
指針,不能訪問任何非靜態(tài)成員
靜態(tài)成員
也是類的成員,受public
、protected
、private
訪問限定符的限制
課代表簡單總結(jié):
靜態(tài)成員變量
必須在類外初始化(定義)靜態(tài)成員函數(shù)
失去了this
指針,但當(dāng)為public
時,可以通過類名::函數(shù)名
直接訪問
class Test
{
public://Test(int val = 0, static int sVal = 0)// :_val(val)// , _sVal(sVal) //非法的,初始化列表也無法初始化靜態(tài)成員變量//{}static void Print(){//cout << _val << endl; //非法的,沒有 this 指針,無法訪問對象成員cout << _sVal << endl;}
private:int _val;static int _sVal; //靜態(tài)成員變量
};int Test::_sVal = 0; //靜態(tài)成員變量必須在類外初始化(定義),需指定屬于哪個類
靜態(tài)成員變量只能初始化一次
靜態(tài)成員函數(shù)沒有 this 指針
靜態(tài)成員函數(shù)是為靜態(tài)成員變量而生
如此刁鉆的成員變量究竟有何妙用呢?
- 答: 有的,存在即合理
利用靜態(tài)成員變量只能初始化一次的特定,寫出函數(shù)統(tǒng)計(jì)程序運(yùn)行中調(diào)用了多少次構(gòu)造函數(shù)
class Test
{
public:Test(int val = 0):_val(val){_sVal++; //利用靜態(tài)成員變量進(jìn)行累加統(tǒng)計(jì)}static void Print(){cout << _sVal;}
private:int _val = 0;static int _sVal; //靜態(tài)成員變量
};int Test::_sVal = 0; //靜態(tài)成員變量必須在類外初始化(定義)int main()
{Test T[10]; //調(diào)用十次構(gòu)造函數(shù)//通過靜態(tài)成員變量驗(yàn)證cout << "程序共調(diào)用了";Test::Print();cout << "次成員函數(shù)" << endl;return 0;
}
輸出結(jié)果如下:
得益于 static 修飾的成員變量統(tǒng)計(jì)
注意:
靜態(tài)成員函數(shù)
不可以調(diào)用非靜態(tài)成員變量
,沒有this
指針非靜態(tài)成員函數(shù)
可以調(diào)用靜態(tài)成員變量
,具有全局屬性
📖匿名對象
C語言
結(jié)構(gòu)體支持創(chuàng)建匿名結(jié)構(gòu)體,C++
則支持創(chuàng)建匿名對象
匿名對象使用如下:
//假設(shè)存在日期類 Date
int main()
{Date(); //此處就是一個匿名對象return 0;
}
匿名對象擁有正常對象的所有功能,缺點(diǎn)就是生命周期極短,只有一行
//演示
Date(2023, 2, 10); //匿名對象1 初始化
Date().Print(); //匿名對象2 調(diào)用打印函數(shù)//注意:兩個匿名對象相互獨(dú)立,創(chuàng)建 匿名對象2 時, 匿名對象1 已被銷毀
🖋?使用場景
匿名對象適合用于某些一次性場景,也適合用于優(yōu)化性能
Date(2023, 2, 10).Print(); //單純打印日期 2023 2 10//函數(shù)返回時
Date d(2002, 1, 1);
return d;
//等價于
return Date(2002, 1, 1); //提高效率
📖友元
新增關(guān)鍵字 friend
,譯為朋友,常用于外部函數(shù)在類中的友好聲明
類中的成員變量為私有,類外函數(shù)無法隨意訪問,但可以在類中將類外函數(shù)聲明為友元函數(shù),此時函數(shù)可以正常訪問類中私有成員
友元函數(shù)會破壞類域的完整性,有利有弊
注意:
- 友元是單向關(guān)系
- 友元不具有傳遞性
- 友元不能繼承
- 友元聲明可以寫在類中的任意位置
🖋?友元函數(shù)
friend
修飾函數(shù)時,稱為友元函數(shù)
class Test
{
public://聲明外部函數(shù) Print 為友元函數(shù)friend void Print(const Test&d);Test(int val = 100):_val(val){}private:int _val;
};void Print(const Test& d)
{cout << "For Friend " << d._val << endl;
}int main()
{Test t;Print(t);
}
程序正常編譯,結(jié)果如下:
友元函數(shù)可以用來解決外部運(yùn)算符重載函數(shù)無法訪問類中成員的問題,但還是不推薦這種方法
🖋?友元類
friend
修飾類時,稱為友元類
class Time
{friend class Date; // 聲明日期類為時間類的友元類,則在日期類中就直接訪問Time類
public:Time(int hour = 0, int minute = 0, int second = 0): _hour(hour), _minute(minute), _second(second){}
private:int _hour;int _minute;int _second;
};class Date
{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}void SetTimeOfDate(int hour, int minute, int second){// 直接訪問時間類私有的成員變量_t._hour = hour;_t._minute = minute;_t._second = second;}
private:int _year;int _month;int _day;Time _t;
};
友元有種繼承的感覺,但它不是繼承,它也不支持繼承
📖內(nèi)部類
將類B
寫在類A
中,B
稱作 A
的內(nèi)部類
class A
{
public://B 稱作 A 的內(nèi)部類class B{private:int _b;}
private:int _a;
}
內(nèi)部類天生就是外類的友元類
也就是說,B
天生就能訪問 A
中的成員
🖋?特性
內(nèi)部類在C++
中比較少用,屬于了解型知識
內(nèi)部類的最大特性就是使得內(nèi)部類能受到外類的訪問限定符限制
內(nèi)部類特點(diǎn):
- 獨(dú)立存在
- 天生就是外類的友元
用途:
- 可以利用內(nèi)部類,將類隱藏,現(xiàn)實(shí)中比較少見
注意:
內(nèi)部類
跟其外類
是獨(dú)立存在的,計(jì)算外類
大小時,是不包括內(nèi)部類
大小的內(nèi)部類
受訪問限定符的限定,假設(shè)為私有,內(nèi)部類
無法被直接使用內(nèi)部類
天生就算外類
的友元,即可以訪問外類
中的成員,而外類
無法訪問內(nèi)部類
📖編譯器優(yōu)化
前面說過,編譯器存在優(yōu)化行為,這里就來深入探討一下
把上面的代碼搬下來用一下,方便觀察發(fā)生了什么事情
class A
{
public://默認(rèn)構(gòu)造函數(shù)A(int a = 0):_a(a){//表示默認(rèn)構(gòu)造函數(shù)被調(diào)用過cout << "A(int a = 0)" << endl;}//默認(rèn)析構(gòu)函數(shù)~A(){_a = 0;//表示默認(rèn)析構(gòu)函數(shù)已被調(diào)用cout << "~A" << endl;}//拷貝構(gòu)造函數(shù)A(const A& a){_a = a._a;cout << "A(const A& a)" << endl;}//賦值重載函數(shù)A& operator=(const A& a){if(this != &a){_a = a._a;}cout << "A& operator=(const A& a)" << endl;return *this;}private:int _a;
};
🖋?參數(shù)優(yōu)化
在類外存在下面這些函數(shù):
void func1(A aa)
{}int main()
{func1(100);return 0;
}
預(yù)計(jì)調(diào)用后發(fā)生了這些事情:
構(gòu)造(隱式轉(zhuǎn)換)
-> 拷貝構(gòu)造(傳參)
-> 構(gòu)造(創(chuàng)建aa接收參數(shù))
編譯器會出手優(yōu)化
實(shí)際只發(fā)生了這些事情:
構(gòu)造(直接把a(bǔ)a構(gòu)造為目標(biāo)值)
🖋?返回優(yōu)化
除了優(yōu)化傳參外,編譯器還會優(yōu)化返回值
A func2()
{return A(100);
}int main()
{//func1(100);A a = func2();return 0;
}
預(yù)計(jì)調(diào)用后發(fā)生了這些事情:
構(gòu)造(匿名對象的創(chuàng)建)
-> 構(gòu)造(臨時變量)
-> 拷貝構(gòu)造(將匿名對象拷貝給臨時變量)
-> 拷貝構(gòu)造(將臨時變量拷貝給 a)
編譯器會出手優(yōu)化
實(shí)際只發(fā)生了這些事情:
構(gòu)造(直接把函數(shù)匿名對象值看作目標(biāo)值,構(gòu)造除出 a)
現(xiàn)在可以證明:編譯器會將某些非必要的步驟省略點(diǎn),執(zhí)行關(guān)鍵步驟
優(yōu)化場景:
- 涉及
拷貝構(gòu)造
+構(gòu)造
時,編譯器多會出手 - 傳值返回時,涉及
多次拷貝構(gòu)造
,編譯器也會出手
注意:
- 引用傳參時,編譯器無需優(yōu)化,因?yàn)?code>不會涉及拷貝構(gòu)造
- 實(shí)際編碼時,如果能采用匿名構(gòu)造,就用匿名構(gòu)造,會
加速
編譯器的優(yōu)化 - 接收參數(shù)時,如果分成兩行(先定義、再接收),編譯器無法優(yōu)化,效率會降低
編譯器只能在一行語句內(nèi)進(jìn)行優(yōu)化,如果涉及多條語句,編譯器也不敢擅自主張
🖋?編碼技巧
下面是一些編碼小技巧,可以提高程序運(yùn)行效率
- 接收返回值對象時,盡量拷貝構(gòu)造方式接收,不要賦值接收
- 函數(shù)返回時,盡量返回匿名對象
- 函數(shù)參數(shù)盡量使用
const&
參數(shù)
📖再次理解類和對象
出自:比特教育科技
📘總結(jié)
以上就是 類和對象(下)的全部內(nèi)容了,我們在本文章學(xué)習(xí)了一些類和對象的小細(xì)節(jié),比如明白了善用初始化列表
的道理、懂得了友元函數(shù)
的用法、了解了編譯器的優(yōu)化
事實(shí)、最后還簡單理解了類和對象與現(xiàn)實(shí)的關(guān)系,相信在這些細(xì)節(jié)的加持之下,對類和對象的理解能更上一層樓!
如果你覺得本文寫的還不錯的話,可以留下一個小小的贊👍,你的支持是我分享的最大動力!
如果本文有不足或錯誤的地方,隨時歡迎指出,我會在第一時間改正
…
相關(guān)文章推薦
類和對象(上)
類和對象(中)
C++入門基礎(chǔ)
![]()