关于c++中的临时变量
為什么寫這樣一篇文章?
本人是c++的初學(xué)者, 剛接觸類這個(gè)概念沒多久, 但是遇到了許多問題困擾我, 其中有一個(gè)問題尤為致命, 我問了許多前輩, 他們?cè)S多都沒能如愿幫我徹底解決這個(gè)問題, 而寫這篇文章, 一是為了幫助自己再次梳理一遍近期的困惑, 二也是為了幫助后來者理解這方面的一系列問題.
當(dāng)然, 由于是初學(xué)者, 我并不保證我下面的話不會(huì)有很多錯(cuò)誤出現(xiàn), 甚至有些在您看來有些滑稽之談. 請(qǐng)您諒解
首先: 什么是臨時(shí)變量?
如果你學(xué)過C語言, 那我這里提到的臨時(shí)變量對(duì)你來說應(yīng)該是一個(gè)新的概念, 他不像你記憶中想象的那樣: 例如 你要交換a, b兩個(gè)整數(shù)時(shí)聲明的第三個(gè)變量temp就是臨時(shí)變量, 這個(gè)觀點(diǎn)是十分錯(cuò)誤的! 在C++中, 臨時(shí)變量的產(chǎn)生是你不能直接看出來的. 至于怎么產(chǎn)生我們下面會(huì)聊到.
// 交換例子int a = 5, b = 10, temp; //temp并不是本文所指的臨時(shí)變量temp = a;a = b;b = temp;例子:
在c++用, 我們有了一個(gè)新的概念一一引用. 就像一個(gè)不能改變指向?qū)ο蟮闹羔樢粯?
那如果我們現(xiàn)在有如下代碼:
int a = 5;int &b = a;這顯然是沒有問題的, 因?yàn)槲覀冇幸粋€(gè)int類型的變量, 我們用了int類型的引用去指向它.
可是如果我們玩點(diǎn)花樣, 不選擇使用一個(gè)同類型的引用呢?
比如我們這樣:
這個(gè)時(shí)候編譯器會(huì)報(bào)錯(cuò), 他告訴我不能用int&去指向一個(gè)double. 那如果這時(shí)候我把a(bǔ)強(qiáng)制類型轉(zhuǎn)換會(huì)如何?
像這樣:
很倒霉的是, 編譯器又報(bào)錯(cuò)了, 他告訴我不是常引用的變量b不能作為左值. 這時(shí)候就很神奇, 這跟常引用又有什么關(guān)系呢? 不過我們還是遵循編譯器所期待的那樣,修改為如下的代碼:
double a = 10.8;const int &b = (int)a; //也可不寫(int), 它會(huì)自動(dòng)轉(zhuǎn)換 const int &b = a;經(jīng)歷過上述稍微有些繁瑣的操作后, 我們終于實(shí)現(xiàn)了我們的目的, 與此同時(shí)我們也拋出了一個(gè)問題, 就是為什么我們要在引用前加const呢?
經(jīng)過幾番折騰, 我了解到, 我們?cè)诎岩粋€(gè)double類型變量(強(qiáng)制)轉(zhuǎn)換為int類型時(shí), 它會(huì)產(chǎn)生臨時(shí)變量. 我們相當(dāng)于(int)a后, 得到了一個(gè)沒有名字的變量, 它的類型為int, 值為10, 我們把這個(gè)臨時(shí)變量(10)作為右值給了b變量. 并在執(zhí)行完該語句后臨時(shí)變量會(huì)被釋放.
而臨時(shí)變量要賦給一個(gè)引用類型時(shí), 我們必須要用const修飾, 原因我會(huì)在后文中給出.
我們現(xiàn)在來深入理解一下上述操作:
其實(shí)我們是可以接受在寫 const int &b = a; (這里省寫了類型轉(zhuǎn)換, 方便理解) 時(shí), 我們并不是把a(bǔ)賦值給了b, 因?yàn)閎得到的是10, 而a在進(jìn)行類型強(qiáng)制轉(zhuǎn)換時(shí), 是不會(huì)影響到a原本的值的, 他該是10.8就還是10.8 并不會(huì)變成10. 所以這個(gè)10一定不是從a那里得來的, 而是從別的地方一一臨時(shí)變量.
既然如此,我們可以得出一個(gè)結(jié)論: b引用的變量并不是a, 而是一個(gè)int類型的臨時(shí)變量.
那么如果我們通過修改a的值, 那b的值理論上就不應(yīng)該會(huì)改變.
上述代碼就驗(yàn)證了我們的說法, b的值沒有改變.
當(dāng)然我們?cè)谄綍r(shí)寫代碼時(shí)雖然也不會(huì)照上面代碼那樣寫, 但當(dāng)你把其中的double或者int換成自定義的用戶類型時(shí), 在這一個(gè)過程中產(chǎn)生臨時(shí)變量的事情我們就不能再忽略了, 因?yàn)樗麜?huì)體現(xiàn)在調(diào)用構(gòu)造函數(shù)上.
我上面的一系列操作也僅僅是為了讓你理解你在C語言中沒能理解到的臨時(shí)變量這一新概念, 而發(fā)生類型強(qiáng)制轉(zhuǎn)換則是產(chǎn)生臨時(shí)變量的方法之一.
臨時(shí)變量:
通過上述實(shí)例我引入了一個(gè)非常簡單的產(chǎn)生臨時(shí)變量的方法, 那么這里我們就來剖析一下臨時(shí)變量有什么樣的特性.
首先, 我們可以認(rèn)為臨時(shí)變量都被const修飾:
但是我也在網(wǎng)上的某篇文上了解到, 有某本書上說過臨時(shí)變量是可以作為左值的, 但是絕大多數(shù)的文章也都告訴我臨時(shí)變量是一個(gè)const類型, 我選擇從眾, 在后文中提到的臨時(shí)變量也都認(rèn)為被const所修飾.
既然他本身是一個(gè)const類型, 那么我們就可以理解前文為什么需要一個(gè)const int&類型的變量去接收這個(gè)臨時(shí)變量了. 我們可以這樣理解, 你在定義某個(gè)變量時(shí), 如果你沒有說明他是一個(gè)const類型, 那么編譯器完全有理由去相信你是會(huì)修改這個(gè)變量的. 你修改一個(gè)臨時(shí)變量, 臨時(shí)變量本身被const修飾, 無法更改. 況且即使沒有const修飾, 這也將會(huì)是毫無意義的, 因?yàn)榕R時(shí)變量他可能隨時(shí)就會(huì)消散, 而你去修改它, 編譯器耗費(fèi)大量資源后, 又把它釋放了, 如果編譯器會(huì)說話, 你猜他會(huì)不會(huì)來句: 您逗我玩呢?
但是在這里我要說明一下: 我們不是說這個(gè)臨時(shí)變量是不能傳遞給一個(gè)變量(特指變量被沒有const修飾), 如下述代碼顯然是成立的, 畢竟我們之前也都是這么做的:
double a = 10.8;int b = (int)a;b = 20;我們是說臨時(shí)變量不能賦給非!常引用的變量. 但在這里我要特別強(qiáng)調(diào): 我們需要用一個(gè)常引用作為左值的主要原因并不是因?yàn)榕R時(shí)變量被const修飾過, 而是因?yàn)槲覀內(nèi)绻挥胏onst來限制我們程序員自己, 我們可能會(huì)無意中修改了臨時(shí)變量, 這些操作都是毫無意義的.
據(jù)此,c++編譯器加入了臨時(shí)變量不能作為非const引用的這個(gè)語義限制,其意在于限制這個(gè)非!常規(guī)用法的潛在錯(cuò)誤
臨時(shí)變量的產(chǎn)生:
其實(shí)看到這里, 你也應(yīng)該會(huì)好奇了, 那么我們除了在類型轉(zhuǎn)換時(shí)會(huì)產(chǎn)生臨時(shí)變量, 那么還有嗎?
答案是肯定的, 大體上歸結(jié)為以下3點(diǎn):
由于提到本人是個(gè)初學(xué)者, 我只遇到過上述三種情況, 并不一定全面, 而后兩種的產(chǎn)生途徑解釋我會(huì)在后文中說明.
注: 如果考慮到非編譯器自動(dòng)產(chǎn)生臨時(shí)變量的方法, 其實(shí)還有一種用戶生成臨時(shí)變量的方法, 后文會(huì)提到.
正文(一級(jí)加粗):
呃, 你沒有看錯(cuò), 這里才算到正文, 前面都是鋪墊, 但是這里我們也要經(jīng)過點(diǎn)鋪墊再到核心問題.
常引用與引用:
首先我想特殊說明一下常引用與引用在函數(shù)之間的傳遞, 其實(shí)這和常量與非常量在函數(shù)間傳遞是一樣的.
1. 當(dāng)某個(gè)函數(shù)我們以 普通的引用 作為形參時(shí): 不能接受常量作為實(shí)參傳遞
因?yàn)槲覀円云胀ㄒ米鳛樾螀? 在函數(shù)里編譯器有理由認(rèn)為我們會(huì)對(duì)這個(gè)形參進(jìn)行修改, 那么如果你傳遞的是一個(gè)常量, 把常量給了這個(gè)引用, 說白了不就是告訴編譯器您要修改一個(gè)常量嗎? 這當(dāng)然是不成立的
2. 當(dāng)某個(gè)函數(shù)我們以常引用作為形參時(shí): 可以接受常量或變量作為實(shí)參傳遞
當(dāng)以常引用作為形參的時(shí)候, 我們向編譯器說明我們不希望修改形參, 所以這里實(shí)參是可以接受變量傳遞的, 我不修改它而已. 而常量作為實(shí)參傳遞也當(dāng)然可以.
特殊說明: const所修飾的變量并不一定完全無法修改, 只是為了限制程序員自身. 在此不做過多的贅述.
特別強(qiáng)調(diào): 當(dāng)以引用作為形參時(shí)我們也是傳遞的地址, 從而不會(huì)產(chǎn)生臨時(shí)變量
臨時(shí)變量的產(chǎn)生途徑2和3:
首先我們引入一段代碼, 并且我們來熟悉一下這段代碼, 便于我們后續(xù)理解.
#include <bits/stdc++.h> typedef long long ll; using namespace std; class node { //這個(gè)類名隨便起了, 我習(xí)慣了node private:int x, y; public:node() { cout << "默認(rèn)構(gòu)造函數(shù)" << endl; } //默認(rèn)構(gòu)造函數(shù)node(int a, int b) :x(a), y(b) { cout << "構(gòu)造函數(shù)1" << endl; } //構(gòu)造函數(shù)node(const node& a) { cout << "復(fù)制構(gòu)造函數(shù)" << endl; *this = a; } //復(fù)制構(gòu)造函數(shù)~node() { cout << "析構(gòu)函數(shù)END" << endl; } //析構(gòu)函數(shù) }; /* 下面的可以先不用看了 */ node fact(node t) { return t; } int main(void) {node a;fact(a);cout << "END" << endl;return 0; }OK, 上述代碼熟悉完類體就可以了, 那么我們接下來可以來解釋臨時(shí)變量的產(chǎn)生途徑2和3:
首先, 我們?cè)趍ain函數(shù)里聲明了一個(gè)對(duì)象node a;, 然后我們把a(bǔ)這個(gè)對(duì)象作為參數(shù)傳遞給了fact()函數(shù), 注意: 此處我們是傳遞的值.
隨后我們?cè)趂act函數(shù)中return了這個(gè)形參. (雖然在main函數(shù)中忽略了fact的返回值, 但是不影響代碼的正確性)
執(zhí)行結(jié)果如下:
我們?yōu)榱四芨玫恼f明某些問題, 我們可以添加一些輸出地址的操作, 例如:
#include <bits/stdc++.h> typedef long long ll; using namespace std; class node { private:int x, y; public:node() { cout << "默認(rèn)構(gòu)造函數(shù)" << endl; } node(int a, int b) :x(a), y(b) { cout << "構(gòu)造函數(shù)1" << endl; } node(const node& a) { cout << "復(fù)制構(gòu)造函數(shù)" << endl; *this = a; cout << this << endl; } //增加了輸出當(dāng)前被生成對(duì)象的地址~node() { cout << "析構(gòu)函數(shù)END" << endl; } }; node fact(node t) { cout << &t << endl; return t; } //增加了輸出形參的地址 int main(void) {node a;cout << &a << endl; //增加了輸出對(duì)象a的地址fact(a);cout << "END" << endl;return 0; }經(jīng)過操作后, 我們?cè)俅芜\(yùn)行得到以下結(jié)果:
默認(rèn)構(gòu)造函數(shù)003AF7A4 //a的地址(我們的值不相同很正常) 只是以這個(gè)為例復(fù)制構(gòu)造函數(shù)003AF69C //這是復(fù)制實(shí)參a后產(chǎn)生的臨時(shí)變量的地址003AF69C //這是形參t的地址復(fù)制構(gòu)造函數(shù)003AF6C8 //這是返回值臨時(shí)變量的地址析構(gòu)函數(shù)END析構(gòu)函數(shù)ENDEND析構(gòu)函數(shù)ENDOK, 到此我們就能理解后兩種說明產(chǎn)生臨時(shí)變量的方法了.
問題:
現(xiàn)在我們開始說一個(gè)很重要的問題, 其實(shí)除了上述三種途徑, 我們還可以自己通過構(gòu)造函數(shù)來構(gòu)造一個(gè)臨時(shí)變量, 對(duì)于我這個(gè)題而言, 我可以用 node(1, 2); 這條語句通過構(gòu)造函數(shù)1來創(chuàng)造一個(gè)臨時(shí)變量.(這樣寫是成立的)
那么如果我這時(shí)想用這個(gè)臨時(shí)變量來初始化一個(gè)新的對(duì)象, 那我可以通過 node a = node(1, 2);
代碼如下:
注: 之后的類體內(nèi)容也都以上述代碼為標(biāo)準(zhǔn), 不再顯示在后文中.
運(yùn)行結(jié)果如下:
這時(shí)候, 如果我把復(fù)制構(gòu)造函數(shù)的參數(shù)中的const給刪除, 變?yōu)?/p> node(node& a) { cout << "復(fù)制構(gòu)造函數(shù)" << endl; *this = a; }
此時(shí)main函數(shù)中的語句就會(huì)報(bào)錯(cuò), 由于編譯器不同, 大體有兩種錯(cuò)誤提示:
這里特殊說明: 如果你改寫了我的代碼, 如下:
node a;a = node(1, 2);這樣是對(duì)的, 但是更改了問題的本質(zhì), 語句 node a = node(1, 2); 中表示聲明a的同時(shí)在給a初始化, 而 node a; a = node(1, 2); 則表示在給a賦值, 等號(hào)表示賦值符號(hào)了, 是有本質(zhì)區(qū)別的.
或者我這樣給你說明, 構(gòu)造函數(shù), 他只能在初始化對(duì)象時(shí)調(diào)用, 而初始化, 指的是在定義某個(gè)變量時(shí)對(duì)他賦初值的操作. 第二種寫法先是聲明了a對(duì)象, 然后沒有給他進(jìn)行賦值, 這時(shí)候里面的值會(huì)是隨機(jī)的. 而在==a = node(1, 2);==操作時(shí), 此時(shí)便是賦值操作了, 因此他也不會(huì)去調(diào)用構(gòu)造函數(shù)(對(duì)于a而言, 并不是指 node(1, 2) 這個(gè)臨時(shí)變量產(chǎn)生不會(huì)調(diào)用構(gòu)造函數(shù)). 要實(shí)在還不明白可以直接將賦值號(hào)重載來理解.
總結(jié)下來就是 : 第一種相當(dāng)于初始化, 而第二種相當(dāng)于賦值.
OK, 那么到此我們就拋出一個(gè)大問號(hào)了, 為什么會(huì)有這種東西出現(xiàn)呢? 我又沒調(diào)用這個(gè)復(fù)制構(gòu)造函數(shù), 為啥我改了他我的代碼會(huì)錯(cuò)?
這里我不多賣關(guān)子了, 根據(jù)剛才形參與臨時(shí)變量地址的問題, 我們同樣可以輸出這個(gè)臨時(shí)變量與我創(chuàng)建的這個(gè)a的地址, 在復(fù)制構(gòu)造函數(shù)中加回const后, 在構(gòu)造函數(shù)1里輸出臨時(shí)變量的地址, 在主函數(shù)里輸出a的地址, 我們會(huì)發(fā)現(xiàn)這兩個(gè)地址是相同的.
這時(shí)你是不是會(huì)稍微有點(diǎn)頭緒, 我們可以大膽猜測, 編譯器這時(shí)候偷了個(gè)懶, 他直接把臨時(shí)變量的地址給了a, 完成了初始化, 省略了再把臨時(shí)變量復(fù)制給a對(duì)象這步操作, 所以這也能解釋為什么我們運(yùn)行時(shí)明明沒有調(diào)用復(fù)制構(gòu)造函數(shù), 但是修改它卻報(bào)了錯(cuò)的原因一一我們?cè)揪褪切枰脧?fù)制構(gòu)造函數(shù)把臨時(shí)變量賦值給a的, 只是編譯器在執(zhí)行的時(shí)候略去了這一步, 而在檢查時(shí)沒有略去.
所以在我們寫復(fù)制構(gòu)造函數(shù)的時(shí)候, 請(qǐng)務(wù)必遵循規(guī)范, 加上const..
下面我們引入一段代碼, 來稍加進(jìn)行分析:
node fact() { return node(1, 2); } int main(void) {node a = fact();return 0; }這段代碼我們讓fact()函數(shù)返回了一個(gè)臨時(shí)變量, 用他來給a進(jìn)行初始化, 這時(shí)候由于本身我們返回值就是一個(gè)臨時(shí)變量 所以會(huì)省去一次復(fù)制構(gòu)造函數(shù)的調(diào)用. 又根據(jù)我們剛才的猜想, 此時(shí)他會(huì)用臨時(shí)變量直接初始化a, 而不再進(jìn)行復(fù)制構(gòu)造函數(shù)的調(diào)用.
輸出結(jié)果如下:
構(gòu)造函數(shù)1 析構(gòu)函數(shù)END同樣我們可以通過輸出臨時(shí)變量與a的地址確定我的上述說法.
END
本文到此結(jié)束, 希望對(duì)您有所幫助
總結(jié)
以上是生活随笔為你收集整理的关于c++中的临时变量的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 海奥华预言--第七章 慕大陆和远东岛
- 下一篇: 【期末复习】转眼到了C++的复习时间(更