??前言
上篇文章中,對函數(shù)重載和內(nèi)聯(lián)函數(shù)的作用和特性使用,進(jìn)行了精細(xì)的詳解。
引用和指針混不清?引用的抽絲剝繭!還有不用寫類型可以自動推導(dǎo)的關(guān)鍵字?for遍歷竟然還有我們沒見過的面孔!新版空指針nullptr!本篇會對這些進(jìn)行細(xì)致的講解,以幫助大家更好的了解c++語法。
???引用
? 你有沒有被人起過外號?比如身邊的朋友,喊他的時(shí)候不會叫他的全名,像我很好的朋友,我一般都喜歡叫他"阿威",而不會去稱呼全名.我叫他"阿威",他還是他沒有什么問題.
? 這里新登場的引用不是新定義一個(gè)變量,而是給已存在變量取了一個(gè)別名,編譯器不會為引用變量開辟內(nèi)存空間,它和它引用的變量共用同一塊內(nèi)存空間.
??引用的概念
語法 : 類型& 引用變量名(對象名) = 引用實(shí)體;
從圖中我們可以看出b不僅和a的值相同,地址也是一模一樣!這是為什么?
這里要注意的一點(diǎn)是:引用類型必須和引用實(shí)體是同種類型的!
這里編譯器直接就報(bào)錯(cuò)了,這樣是不被允許的!
??引用的特性
?引用在定義時(shí)必須初始化
這里的引用a沒有初始化,編譯器直接報(bào)錯(cuò)了!
?一個(gè)變量可以有多個(gè)引用
此時(shí)這些引用都是n的別名,指向的就是n,和n共用一塊空間!
? 引用一旦引用一個(gè)實(shí)體,再不能引用其他實(shí)體
這里的n最開始是a別名,是指向a的引用,引用只能引用一個(gè)實(shí)體,一旦有了就不能再換了!所以這里的n拿到的是b的值,并不是引用了b,由于n是a的別名,二者共用一塊空間,所以改了n改成了20,因此a也會是20!
??常引用
在C++中,常引用是指在函數(shù)參數(shù)列表或變量聲明中使用const關(guān)鍵字來修飾的引用。常引用的作用是限制對被引用對象的修改。
語法: const 數(shù)據(jù)類型 &引用名 = 被引用對象;
常引用的特點(diǎn)如下:
- 常引用只能引用常量或臨時(shí)對象,不能引用非常量對象。
- 常引用不允許對被引用對象進(jìn)行修改,即不能通過常引用修改被引用對象的值。
- 常引用可以接受非常量對象、常量對象和臨時(shí)對象作為參數(shù)。
- 常引用可以提高程序的效率,因?yàn)槌R貌恍枰獎?chuàng)建臨時(shí)變量。
常引用只能引用常量或臨時(shí)對象,不能引用非常量對象。如果需要引用非常量對象并且不允許修改該對象的值,可以使用const修飾符來聲明常量對象。
??使用場景
?做參數(shù)
在沒有引用前,我們交換兩個(gè)變量的值需要使用指針來完成?,F(xiàn)在可以使用引用來完成了。
?做返回值
上面是一段有問題的代碼!
這里的結(jié)果是不確定的,因?yàn)锳dd函數(shù)返回是c的別名,也就是引用,ret接受到的就是c,此時(shí)ret就是c,第一次調(diào)用,如果函數(shù)棧幀結(jié)束后,這塊空間沒有被銷毀,那么ret就是3,如果空間被銷毀了,那么ret就是不確定的值.
? 第二次函數(shù)調(diào)用Add,雖然ret沒有接受,但是因?yàn)樵诘谝淮魏瘮?shù)調(diào)用后,ret就已經(jīng)是c的別名了,是指向c的引用,由于函數(shù)棧幀空間的復(fù)用性,第二次Add的調(diào)用還是在上一次的空間,此時(shí)c更改了值,那么ret也就會修改!
? 但是這是不確定的,因?yàn)樵诓煌木幾g器下,函數(shù)棧幀調(diào)用后,會不會立即清空空間,所以值是不確定的,如果銷毀了,那么ret引用的值就是個(gè)隨機(jī)值,如果沒銷毀,那就是函數(shù)正常的返回值.(vs2022下不會立即空間清除)
但是如果加上了static修飾就會不一樣,因?yàn)楸籹tatic修飾的變量是靜態(tài)變量,是放在靜態(tài)區(qū)上的,而不是棧上,并且由于static修飾的靜態(tài)的變量只能被初始化一次,所以可以在一定程度上保證安全.
??傳值與傳引用效率比較
以值作為參數(shù)或者返回值類型,在傳參和返回期間,函數(shù)不會直接傳遞實(shí)參或者將變量本身直接返回,而是傳遞實(shí)參或者返回變量的一份臨時(shí)的拷貝,因此用值作為參數(shù)或者返回值類型,效率是非常低下的,尤其是當(dāng)參數(shù)或者返回值類型非常大時(shí),效率就更低。
如圖所示,你可以看出傳引用的效率還是很高的!
-
傳值的優(yōu)點(diǎn)是簡單、直觀,不會對原始數(shù)據(jù)產(chǎn)生任何影響。但是,傳值會導(dǎo)致參數(shù)的副本被創(chuàng)建,如果參數(shù)較大,傳值的效率可能會比較低。
-
傳引用的優(yōu)點(diǎn)是效率高,因?yàn)椴恍枰獎?chuàng)建參數(shù)的副本。同時(shí),傳引用可以直接修改原始數(shù)據(jù),對原始數(shù)據(jù)產(chǎn)生影響。但是,需要注意的是,如果函數(shù)內(nèi)部不需要修改參數(shù)的值,傳引用可能會導(dǎo)致意外的修改,因此需要謹(jǐn)慎使用。
??值和引用作為返回值類型的性能比較
函數(shù)可以返回值或引用作為返回類型。返回值是將函數(shù)的結(jié)果復(fù)制一份返回,而返回引用是返回原始數(shù)據(jù)的引用。
通過上述代碼的比較,發(fā)現(xiàn)傳值和指針在作為傳參以及返回值類型上效率相差很大。
-
返回值的優(yōu)點(diǎn)是簡單、直觀,不會對原始數(shù)據(jù)產(chǎn)生任何影響。但是,返回值會導(dǎo)致結(jié)果的副本被創(chuàng)建,如果結(jié)果較大,返回值的效率可能會比較低。
-
返回引用的優(yōu)點(diǎn)是效率高,因?yàn)椴恍枰獎?chuàng)建結(jié)果的副本。同時(shí),返回引用可以直接修改原始數(shù)據(jù),對原始數(shù)據(jù)產(chǎn)生影響。但是,需要注意的是,返回引用時(shí)需要確保原始數(shù)據(jù)的生命周期足夠長,否則返回的引用可能會指向無效的數(shù)據(jù)。
??指針和引用的區(qū)別
引用在很多地方好像與指針類似,這里來區(qū)分他們二者的不同.
?語法上的區(qū)別
在語法概念上引用就是一個(gè)別名,沒有獨(dú)立空間,和其引用實(shí)體共用同一塊空間
?底層上的區(qū)別
在底層實(shí)現(xiàn)上實(shí)際是有空間的,因?yàn)橐檬前凑罩羔樂绞絹韺?shí)現(xiàn)的
?檢驗(yàn)
通過觀察匯編我們可以看出,引用的實(shí)現(xiàn)是與指針相同的。那既然如此,引用是不是就是指針呢?我們來驗(yàn)證一下,指針根據(jù)平臺的不同,分為4~8字節(jié),我們來看看引用的大小。
64位平臺下,指針大小8字節(jié),而引用ccc還是1個(gè)字節(jié)的大小,因?yàn)樵谡Z法概念上引用就是一個(gè)別名,沒有獨(dú)立空間,和其引用實(shí)體共用同一塊空間,編譯器是跟語法走的。
?引用與指針的不同點(diǎn)
- 引用概念上定義一個(gè)變量的別名,指針存儲一個(gè)變量地址。
- 引用在定義時(shí)必須初始化,指針沒有要求
- 引用在初始化時(shí)引用一個(gè)實(shí)體后,就不能再引用其他實(shí)體,而指針可以在任何時(shí)候指向任何一個(gè)同類型實(shí)體
- 沒有NULL引用,但有NULL指針
- 在sizeof中含義不同:引用結(jié)果為引用類型的大小,但指針始終是地址空間所占字節(jié)個(gè)數(shù)(32位平臺下占4個(gè)字節(jié))
- 引用自加即引用的實(shí)體增加1,指針自加即指針向后偏移一個(gè)類型的大小
- 有多級指針,但是沒有多級引用
- 訪問實(shí)體方式不同,指針需要顯式解引用,引用編譯器自己處理
- 引用比指針使用起來相對更安全
???auto關(guān)鍵字
??類型別名
隨著程序越來越復(fù)雜,程序中用到的類型也越來越復(fù)雜,經(jīng)常體現(xiàn)在:
- 類型難于拼寫
- 含義不明確導(dǎo)致容易出錯(cuò)
以前我們可能會用到typedef來給復(fù)雜的類型取別名.
使用typedef給類型取別名確實(shí)可以簡化代碼,但是typedef有會遇到新的難題
在編程時(shí),常常需要把表達(dá)式的值賦值給變量,這就要求在聲明變量的時(shí)候清楚地知道表達(dá)式的
類型。然而有時(shí)候要做到這點(diǎn)并非那么容易,因此C++11給auto賦予了新的含義.
??auto簡介
“auto” 關(guān)鍵字是C++11引入的,用于自動推導(dǎo)變量的類型。它可以根據(jù)變量的初始值來確定變量的類型,從而簡化代碼編寫和類型聲明的過程。
- 使用auto定義變量時(shí)必須對其進(jìn)行初始化,在編譯階段編譯器需要根據(jù)初始化表達(dá)式來推導(dǎo)auto的實(shí)際類型。
- auto并非是一種“類型”的聲明,而是一個(gè)類型聲明時(shí)的“占位符”,編譯器在編譯期會將auto替換為變量實(shí)際的類型。
??auto使用細(xì)節(jié)
?auto與指針和引用結(jié)合起來使用
用auto聲明指針類型時(shí),用auto和auto*沒有任何區(qū)別,但用auto聲明引用類型時(shí)則必須加&
?在同一行定義多個(gè)變量
當(dāng)在同一行聲明多個(gè)變量時(shí),這些變量必須是相同的類型,否則編譯器將會報(bào)錯(cuò),因?yàn)榫幾g器實(shí)際只對第一個(gè)類型進(jìn)行推導(dǎo),然后用推導(dǎo)出來的類型定義其他變量。
??auto不能推導(dǎo)的場景
?函數(shù)參數(shù)
由于函數(shù)參數(shù)的類型是在函數(shù)調(diào)用時(shí)確定的,編譯器無法在編譯時(shí)推導(dǎo)出參數(shù)的類型。
void foo(auto x); // 錯(cuò)誤,auto 不能用于函數(shù)參數(shù)的類型聲明
?模板參數(shù)
模板參數(shù)的類型是在實(shí)例化時(shí)確定的,編譯器無法在編譯時(shí)推導(dǎo)出模板參數(shù)的類型。
template <typename T>
void foo(auto x); // 錯(cuò)誤,auto 不能用于模板參數(shù)的類型聲明
?類成員變量
類成員變量的類型是在類定義時(shí)確定的,編譯器無法在編譯時(shí)推導(dǎo)出類成員變量的類型。
class MyClass {
auto x; // 錯(cuò)誤,auto 不能用于類成員變量的類型聲明
};
?靜態(tài)變量
靜態(tài)變量的類型是在編譯時(shí)確定的,編譯器無法在編譯時(shí)推導(dǎo)出靜態(tài)變量的類型。
static auto x = 10; // 錯(cuò)誤,auto 不能用于靜態(tài)變量的類型聲明
??auto不能直接用來聲明數(shù)組
void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6};
}
- 為了避免與C++98中的auto發(fā)生混淆,C++11只保留了auto作為類型指示符的用法.
- auto在實(shí)際中最常見的優(yōu)勢用法就是跟C++11提供的新式for循環(huán),還有l(wèi)ambda表達(dá)式等進(jìn)行配合使用.
???范圍for
??語法
正常我們?nèi)绻闅v一個(gè)數(shù)組的話,會是下面這樣的代碼:
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
array[i] *= 2;
for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)
cout << *p << endl;
}
對于一個(gè)有范圍的集合而言,由程序員來說明循環(huán)的范圍是多余的,有時(shí)候還會容易犯錯(cuò)誤。因此C++11中引入了基于范圍的for循環(huán)。
for循環(huán)后的括號由冒號“ :”分為兩部分:第一部分是范圍內(nèi)用于迭代的變量,第二部分則表示被迭代的范圍。
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)
e *= 2;
for(auto e : array)
cout << e << " ";
return 0;
}
注意:與普通循環(huán)類似,可以用continue來結(jié)束本次循環(huán),也可以用break來跳出整個(gè)循環(huán)。
??使用條件
?for循環(huán)迭代的范圍必須是確定的
-
對于數(shù)組而言,就是數(shù)組中第一個(gè)元素和最后一個(gè)元素的范圍;
-
對于類而言,應(yīng)該提供begin和end的方法,begin和end就是for循環(huán)迭代的范圍。
注意:以下代碼就有問題,因?yàn)閒or的范圍不確定,在函數(shù)參數(shù)中,使用數(shù)組作為參數(shù)時(shí),會自動轉(zhuǎn)換為指針類型。因此,int array[] 實(shí)際上是 int* array 的語法糖。
void TestFor(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}
?迭代的對象要實(shí)現(xiàn)++和==的操作
關(guān)于迭代器,我會在以后的文章中,給大家詳細(xì)講解
???指針空值(nullptr)
在良好的C/C++編程習(xí)慣中,聲明一個(gè)變量時(shí)最好給該變量一個(gè)合適的初始值,否則可能會出現(xiàn)
不可預(yù)料的錯(cuò)誤,比如未初始化的指針。
??NULL
NULL實(shí)際是一個(gè)宏,在傳統(tǒng)的C頭文件(stddef.h)中,可以看到如下代碼:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看到,NULL可能被定義為字面常量0,或者被定義為無類型指針(void*)的常量。不論采取何種定義,在使用空值的指針時(shí),都不可避免的會遇到一些麻煩,比如:
程序本意是想通過f(NULL)調(diào)用指針版本的f(int*)函數(shù),但是由于NULL被定義成0,因此與程序的初衷相悖。
在C++98中,字面常量0既可以是一個(gè)整形數(shù)字,也可以是無類型的指針(void*)常量,但是編譯器默認(rèn)情況下將其看成是一個(gè)整形常量,如果要將其按照指針方式來使用,必須對其進(jìn)行強(qiáng)轉(zhuǎn)(void*)0
?指針空值
在 C 語言中,通常使用宏定義 NULL
來表示空指針。NULL
被定義為一個(gè)整數(shù)常量 0。在 C++ 中,也可以使用 NULL
來表示空指針,但更推薦使用更加類型安全的 nullptr
。
int* ptr = NULL; // 使用 NULL 表示空指針
??nullptr
在 C++11 中引入了 nullptr
關(guān)鍵字,用于表示空指針。nullptr
是一個(gè)特殊的空指針常量,具有空指針類型。使用 nullptr
可以避免一些與整數(shù)常量 0 相關(guān)的問題,提供更好的類型安全性。
int* ptr = nullptr; // 使用 nullptr 表示空指針
??注意事項(xiàng)
-
nullptr
可以隱式轉(zhuǎn)換為任意指針類型,但不能隱式轉(zhuǎn)換為整數(shù)類型。 -
nullptr
和NULL
是不同的。nullptr
是一個(gè)空指針常量,而NULL
是一個(gè)整數(shù)常量。 - 在 C++11 中,推薦使用
nullptr
來表示空指針,以提供更好的類型安全性。 - 在使用nullptr表示指針空值時(shí),不需要包含頭文件,因?yàn)閚ullptr是C++11作為新關(guān)鍵字引入的。
- 在C++11中,sizeof(nullptr) 與 sizeof((void*)0)所占的字節(jié)數(shù)相同。
- 為了提高代碼的健壯性,表示指針空值時(shí)建議最好使用nullptr。
???全篇總結(jié)
? 本章我們詳細(xì)的了解到了c++中的引用,還有作為關(guān)鍵字引入的auto和nullptr,以及一種新的for遍歷方式.
??至此c++入門篇就已經(jīng)算是結(jié)束啦,如果你搞明白這些,c++就已經(jīng)算是入門啦!
看到這里了還不給博主留個(gè):
?? 點(diǎn)贊??收藏 ?? 關(guān)注!
?? ?? ?? ?? ???? ?? ?? ?? ?? ??
拜托拜托這個(gè)真的很重要!
這將對我提供巨大的鼓勵(lì)和支持。文章來源:http://www.zghlxwxcb.cn/news/detail-739825.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-739825.html
到了這里,關(guān)于【c++入門】引用詳解 | auto的類型推導(dǎo) | 范圍for循環(huán) | nullptr空指針的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!