網(wǎng)站首頁 編程語言 正文
寫在前面
我們今天來分享一下關(guān)于引用的知識點,這里都是一些比較基礎(chǔ)的知識,后面在類里面就可以進一步的使用引用了,一起努力.??.
引用初階
引用是C++的特性的之一,不過C++沒有沒有給引用特意出一個關(guān)鍵字,使用了操作符的重載。引用在C++中很常見,常見就意味著它很重要。我們分兩個境界來談?wù)勔?初階是我們能在書上看到的.
什么是引用
引用不是新定義一個變量,而是給已存在變量取了一個別名,編譯器不會為引用變量開辟內(nèi)存空間,它和它引用的變量共用同一塊內(nèi)存空間 .所謂的取別名,就是取外號,張三在朋友面前可能叫三子,在長輩面前可能叫三兒,但是無論是叫三子還是三兒,他們叫的就是張三,這是無可否認(rèn)的.引用的符號&和我們?nèi)〉刂返?操作符一樣的,后面我們就會知道這是操作符構(gòu)成重載的原因.
我們可以理解所謂的引用.
#include
int main()
{
int a = 10;
int& b = a; // b 是 a的一個別名
printf("%p\n", &a);
printf("%p\n", &b);
return 0;
}
為何要有引用
引用有很多優(yōu)點,其中有一個就是可以簡化代碼,我們在C語言中寫過如何交換兩個整型數(shù)據(jù),需要借助一維指針,但是這有一點麻煩,使用引用就可以很好的解決這個問題.
#include
void swap(int& a, int& b)
{
int c = a;
a = b;
b = c;
}
int main()
{
int a = 10;
int b = 20;
printf("交換前 a = %d b = %d\n", a, b);
swap(a, b);
printf("交換后 a = %d b = %d\n", a, b);
return 0;
}
引用指向同一塊空間
我們無論給變量取多少外號,但是這些外號所指向的空間是一樣的,我們只要修改一個外號的空間,就會導(dǎo)致其他外號值得修改.
#include
using namespace std;
int main()
{
int a = 10;
int& b = a; //b 是 a 的別名
int& c = b; //還可以對c取別名
return 0;
}
int main()
{
int a = 10;
int& b = a; //b 是 a 的別名
int& c = b; //還可以對c取別名
c = 20;
cout << "a: " << a << endl;
cout << "b: " << a << endl;
cout << "c: " << a << endl;
return 0;
}
引用的特性
即使引用很好使用,但是我們還要關(guān)注他們的一些特性.
定義時必須初識化
這個要求很嚴(yán)格,我們必須在引用的時候給他初始化,否則就會報錯.
#include
using namespace std;
int main()
{
int a = 10;
int& b;
return 0;
}
一個變量可以多次引用
這個特性我們前面和大家分享過了,我們可以對一個變量多次取外號,每一個外號都是是這個變量的別名,我們甚至可以對外號取外號,不過我們一般不這么干.
引用一旦引用了一個實例,不能在再引用其他的實例
這個特性完美的闡釋了引用的專一性,一旦我們給某一個變量起了一個別名,這個別名就會跟著這個變量一輩子,絕對不會在成為其他的變量的別名.這個也是和指針很大的區(qū)別,指針比較花心.
這個我們就不通過代碼打印各個變量的地址了.,我們觀看另外一種現(xiàn)象,通過觀察我們看到a的值也被修改了,可以確定 b 仍舊a的別名.
int main()
{
int a = 10;
int& b = a;
int c = 20;
// 這個一定是賦值
b = c;
cout << "a: " << a << endl;
return 0;
}
引用進階
我們已經(jīng)看到引用的優(yōu)點,但是這是引用的基礎(chǔ)用法,要是到那里,我們一般看書都可以做到,現(xiàn)在要看看引用更加詳細(xì)的知識.
常引用
我們在C語言中,專門分享過const相關(guān)的知識,那么我們?nèi)绾螌onst修飾的變量取別名呢?這是一個小問題.
const int a = 10;
我們看看下面的方法.
int main()
{
const int a = 10;
int& b = a; //報錯
const int& c = a; // 不報錯
return 0;
}
到這里我們就可以發(fā)現(xiàn),我們?nèi)e名的時候也用const修飾就可以了,這是我們從現(xiàn)象中的得出的結(jié)論,但是這又是因為什么呢?我們需要知道他們的原理.
權(quán)限
不知道大家有沒有在學(xué)習(xí)中看到過這樣一種現(xiàn)象,對于一個文件,我們可能存在僅僅閱讀的權(quán)限,自己無法修改,但是其他的人有可能有資格修改,這就是權(quán)限的能力,const修飾變量后,使得變量的加密程度更加高了,我們?nèi)e名的時候,總不能把這個權(quán)限給過擴大了,編譯器這是不允許的,也就是說我們?nèi)e名的時候,權(quán)限只能往低了走,絕對不能比原來的高.
下面就是把權(quán)限往縮小了給
int main()
{
int a = 10;
const int& b = a;
return 0;
}
給常量取別名必須要是const修飾的,因為常量不能修改.
int main()
{
const int& a = 10;
return 0;
}
臨時變量具有常屬性
這個是語法的一個知識點,我們記住就可以了,現(xiàn)在我們就要看看為什么這個代碼會可以運行.
我們常引用 還有最后一個問題,如果我們是double類型的變量,如何給取一個int類型的別名?
需要用const修飾,而且會發(fā)生截斷,
int main()
{
double d = 1.2;
const int& a = d; //需要用const修飾
cout << a << endl;
return 0;
}
它的本質(zhì)不是取別名,而類似于給常量取別名,而且還會發(fā)生截斷.這個說截斷也不太合適,這會產(chǎn)生一個臨時變量,我把原理圖放在下面.
int main()
{
double d = 1.2;
const int& a = d;
cout << "&a :" <<&a << endl;
cout << "&d :" <<&d << endl;
return 0;
}
引用的場景
我們需要來看看引用的使用場景,它主要有兩大作用.
- 做參數(shù)
- 做返回值
做參數(shù)
我們可以使用引用來交換兩個整型數(shù)據(jù).
#include
void swap(int& a, int& b)
{
int c = a;
a = b;
b = c;
}
int main()
{
int a = 10;
int b = 20;
printf("交換前 a = %d b = %d\n", a, b);
swap(a, b);
printf("交換后 a = %d b = %d\n", a, b);
return 0;
}
但是使用引用作為參數(shù)可能會出現(xiàn)權(quán)限不太匹配的錯誤,所以說我們需要const修飾.
下面就是由于權(quán)限問題,我們沒有辦法來給常量卻別名,這就需要const修飾.
void func(int& a)
{
}
int main()
{
int a = 10;
double b = 10.2;
func(a);
func(10);
func(b);
return 0;
}
返回值
我們先看看返回值的原理,再說說引用做返回值.
返回值的原理
我們需要談?wù)劸幾g器是如何使用返回值的,以下面的代碼為例.
int func()
{
int n = 1;
n++;
return n;
}
int main()
{
int ret = func();
return 0;
}
編譯器會看這個返回值的空間大不大,如果不大,就把的數(shù)據(jù)放到一個寄存器中,如果很大,看編譯器的機制,有的編譯器甚至可能在main函數(shù)中開辟這個空間來臨時保存數(shù)據(jù).
這是由于當(dāng)函數(shù)結(jié)束后,函數(shù)棧幀會銷毀,n的空間也會被釋放,所以要有一個寄存器來保存數(shù)據(jù).
下面的代碼就可以表現(xiàn)出來,
int main()
{
int& ret = func();
return 0;
}
引用做返回值
引用做返回值會有很大的要求,這個和我們普通的返回值可不一樣.說實話,我不想和大家分享的那么深,但是已經(jīng)到這里了,只能這樣了.
下面的代碼,我們可以理解,就是給靜態(tài)變量n取一個別名,我們把它返回到了ret.
int& func()
{
static int n = 1;
n++;
return n;
}
int main()
{
int ret = func();
cout << ret << endl;
return 0;
}
也就是說我們n取一個別名,把這個別名作為返回值返回給函數(shù)調(diào)用者.
int& func()
{
static int n = 1;
n++;
cout << &n << endl;
return n;
}
int main()
{
int& ret = func(); // 注意 是 int&
cout << &ret << endl;
return 0;
}
注意事項
到這里我們就可以看看引用做返回值的注意事項了,我們一定要保證做返回值得引用得數(shù)據(jù)在函數(shù)棧幀銷毀后空間不被釋放,否則就會發(fā)生下面得事情.
我們得到的是一個隨機值,我們拿到了變量 n 的別名,但是在func結(jié)束后空間就被釋放了,下一次函數(shù)的調(diào)用函數(shù)棧幀會覆這篇空間,運氣好的話,我們有可能拿到準(zhǔn)確值,但是無論如何訪問都越界了.
int& func()
{
int n = 1;
n++;
return n;
}
int main()
{
int& ret = func();
printf("這是一條華麗的分割線\n");
cout << ret << endl;
cout << ret << endl;
return 0;
}
引用不會開辟空間
前面我們說了傳參需要有一定的要求,但是這不是說引用做參數(shù)不行,我們使用引用傳參不會發(fā)生拷貝,這極大的提高了代碼的效率.
我們定義一個大點的結(jié)構(gòu)體,來看看拷貝傳參和引用傳參的效率.一般情況下相差大概20倍左右.
typedef struct A
{
int arr[10000];
} A;
void func1(A a)
{
}
void func2(A& a)
{
}
void TestRefAndValue()
{
A a;
// 以值作為函數(shù)參數(shù)
size_t begin1 = clock();
for (size_t i = 0; i < 100000; ++i)
func1(a);
size_t end1 = clock();
// 以引用作為函數(shù)參數(shù)
size_t begin2 = clock();
for (size_t i = 0; i < 100000; ++i)
func2(a);
size_t end2 = clock();
// 分別計算兩個函數(shù)運行結(jié)束后的時間
cout << "func1(A)-time:" << end1 - begin1 << endl;
cout << "func2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
TestRefAndValue();
return 0;
}
引用和指針比較
它們有一個本質(zhì)的區(qū)別,在語法概念上引用就是一個別名,沒有獨立空間,和其引用實體共用同一塊空間 ,但是引用的底層還是開辟空間的,因為引用是按照指針方式來實現(xiàn).我們看看匯編代碼.
- 引用不會開辟空間,但是指針會開辟相應(yīng)的空間.
底層引用和指針是一樣的.
int main()
{
int a = 10;
//語法沒有開辟空間 底層開辟了
int& b = a;
b = 20;
//語法開辟空間 底層也開辟了
int* pa = &a;
*pa = 20;
return 0;
我們來看看它們其他的比較小的區(qū)別,我就不詳細(xì)舉例了.
- 引用在定義時必須初始化,指針沒有要求
- 引用在初始化時引用一個實體后,就不能再引用其他實體,而指針可以在任何時候指向任何一個同類型實體
- 沒有NULL引用,但有NULL指針
- 在sizeof中含義不同:引用結(jié)果為引用類型的大小,但指針始終是地址空間所占字節(jié)個數(shù)(32位平臺下占4個字節(jié))
- 引用自加即引用的實體增加1,指針自加即指針向后偏移一個類型的大小
- 有多級指針,但是沒有多級引用
- 訪問實體方式不同,指針需要顯式解引用,引用編譯器自己處理
- 引用比指針使用起來相對更安全
內(nèi)聯(lián)函數(shù)
以inline修飾的函數(shù)叫做內(nèi)聯(lián)函數(shù),編譯時C++編譯器會在調(diào)用內(nèi)聯(lián)函數(shù)的地方展開,沒有函數(shù)壓棧的開銷,內(nèi)聯(lián)函數(shù)提升程序運行的效率,本質(zhì)來說就和我們的宏比較相似.
記住我們不關(guān)心他們在那個過程中展開,只需要記住他會展開就可以了.我么看看內(nèi)聯(lián)函數(shù)的知識點.
為何存在 內(nèi)聯(lián)函數(shù)
對于一些比較小的函數(shù),我們總是調(diào)用函數(shù)開銷比較的,我們是不是存在一個方法減少這個開銷呢?C語言通過宏來實現(xiàn),C++支持C語言,所以我們可以通過行來實現(xiàn),那么你給我寫一個兩數(shù)現(xiàn)相加的宏,要是你寫的和下面的不一樣,就代表你忘記了一部分知識點.
#define ADD(x,y) ((x) + (y)) //不帶 ; 括號要帶
我們就可以理解了,宏很好,但是寫出一個正確的宏很困難,但是寫一個函數(shù)就不一樣了,所以一些大佬就發(fā)明了內(nèi)斂函數(shù),用來替代宏的部分功能.
展開短小的函數(shù)
函數(shù)內(nèi)聯(lián)不內(nèi)聯(lián)不是你說了算,我們用inline修飾就是告訴編譯器這個函數(shù)可以展開,至于是否展開還是看編譯器,一般之后展開比較短小的函數(shù),較大的函數(shù)不會展開,像遞歸的那些也不可以.
inline void swap(int& x, int& y)
{
int ret = x;
x = y;
y = ret;
}
int main()
{
int a = 1;
int b = 2;
swap(a,b);
cout << "a: " << a << endl;
cout << "b: " << b << endl;
return 0;
}
我們來看看內(nèi)聯(lián)函數(shù),如果函數(shù)不是內(nèi)聯(lián)了的,匯編語言會call
void swap(int& x, int& y)
{
int ret = x;
x = y;
y = ret;
}
如果在上述函數(shù)前增加inline關(guān)鍵字將其改成內(nèi)聯(lián)函數(shù),在編譯期間編譯器會用函數(shù)體替換函數(shù)的調(diào)用.
由于我們使用的是VS編譯器,這里需要看看內(nèi)聯(lián)函數(shù)的匯編語言.
在release模式下,查看編譯器生成的匯編代碼中是否存在call swap,但是編譯器會發(fā)生優(yōu)化,我們通過debug模式下,但是需要設(shè)置.
inline修飾的較短函數(shù)展開了,沒有call
inline void swap(int& x, int& y)
{
int ret = x;
x = y;
y = ret;
}
內(nèi)聯(lián)函數(shù)的特性
我們需要來看看函數(shù)的基本的特性
- inline是一種以空間換時間的做法,省去調(diào)用函數(shù)額開銷。所以代碼很長或者有循環(huán) / 遞歸的函數(shù)不適宜使用作為內(nèi)聯(lián)函數(shù)。
- inline 對于編譯器而言只是一個建議,編譯器會自動優(yōu)化,如果定義為inline的函數(shù)體內(nèi)有循環(huán)/遞歸等,編譯器優(yōu)化時會忽略內(nèi)聯(lián)。
- inline不建議聲明和定義分離,分離會導(dǎo)致鏈接錯誤。因為inline被展開,就沒有函數(shù)地址了,鏈接就會找不到。
較大的函數(shù)編譯器不會發(fā)生內(nèi)聯(lián)
編譯器會自動判別這個函數(shù)給不該內(nèi)聯(lián),要是一個函數(shù)比較大,里面存在遞歸,那么還不如不展開呢.一般是10行為依據(jù).
inline void f()
{
cout << "hello" << endl;
cout << "hello" << endl;
cout << "hello" << endl;
cout << "hello" << endl;
cout << "hello" << endl;
cout << "hello" << endl;
cout << "hello" << endl;
cout << "hello" << endl;
cout << "hello" << endl;
cout << "hello" << endl;
cout << "hello" << endl;
cout << "hello" << endl;
cout << "hello" << endl;
cout << "hello" << endl;
cout << "hello" << endl;
cout << "hello" << endl;
cout << "hello" << endl;
cout << "hello" << endl;
}
int main()
{
f();
return 0;
}
聲明定義一起
如果我們聲明和定義的分離,運行時編譯器會找不到這個函數(shù)的地址的.
這里我么建議直接把內(nèi)聯(lián)函數(shù)直接放到自定義的頭文件中
inline void f()
{
}
原文鏈接:https://blog.csdn.net/m0_61334618/article/details/124694293
相關(guān)推薦
- 2022-06-22 Python實現(xiàn)npy/mat文件的保存與讀取_python
- 2022-07-21 解決win10系統(tǒng)網(wǎng)絡(luò)連接正常,但是網(wǎng)頁打不開的問題
- 2022-10-08 Python使用plt.boxplot()函數(shù)繪制箱圖、常用方法以及含義詳解_python
- 2022-08-01 C#串口編程System.IO.Ports.SerialPort類_C#教程
- 2023-01-07 基于Go語言實現(xiàn)選擇排序算法及優(yōu)化_Golang
- 2022-08-20 C#使用對象序列化類庫MessasgePack_C#教程
- 2022-12-21 C語言中continue的用法詳解_C 語言
- 2022-04-22 Android ScrollView充滿屏幕
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支