李長河
(中國地質(zhì)大學(xué)(武漢)自動化學(xué)院,武漢430074)
C++11新標(biāo)準(zhǔn)下引用的使用和教學(xué)方法研究
李長河
(中國地質(zhì)大學(xué)(武漢)自動化學(xué)院,武漢430074)
引用是提高程序運行效率的重要工具。C++11新標(biāo)準(zhǔn)引入的右值引用拓寬了引用的使用范圍,進(jìn)一步提升了程序的性能。引用是C++教學(xué)的重要內(nèi)容,它是后續(xù)教學(xué)內(nèi)容的基礎(chǔ)。但是對于如何深入理解而又有效地教授學(xué)生使用引用,還很少有教材專門論述。在C++教學(xué)中,深入而又清晰地分析引用的特點和使用方法,能夠幫助學(xué)生靈活而又正確地使用引用。
在C++11新標(biāo)準(zhǔn)下,引用分為左值引用和右值引用[1]。左值引用指的是綁定到左值的引用,新標(biāo)準(zhǔn)頒布之前的引用都是指左值引用。右值引用指的是綁定到右值的引用[2]。引用主要在以下四種場合使用:第一種是在函數(shù)的形參列表中使用左值引用,將其與對應(yīng)的實參進(jìn)行綁定,用來高效地讀取或修改實參的內(nèi)容;第二種是函數(shù)返回值以左值引用的方式返回,避免臨時對象的產(chǎn)生;第三種是為觸發(fā)移動語義而使用右值引用形參,目的是“竊取”將亡對象的資源,避免資源的拷貝[3-4],從而提高運行效率;第四種是為觸發(fā)動態(tài)綁定而使用左值引用,使用左值引用或指針是觸發(fā)動態(tài)綁定的前提。
在新標(biāo)準(zhǔn)下,引用分為左值引用和右值引用。因此,掌握左值和右值的概念對于學(xué)生理解引用是非常重要的。
任何一個表達(dá)式,要么是左值,要么是右值。對于程序員來說,左值所在的內(nèi)存空間的地址是可以獲取的(可用取址符&獲?。抑档牡刂肥菬o法得到的(無法用取址符&獲?。?。因此,既可以讀取左值對象的內(nèi)容又可以向其寫入數(shù)據(jù),而右值對象只能執(zhí)行讀操作,不能對它執(zhí)行寫操作。顯然常量,如‘a(chǎn)’,3.14,10等都是右值,而由程序員定義的用來存放并能夠改變值的對象是左值。一般來說,右值只能在=符號右邊,左值沒有限制,如:
int i=0;//正確:用右值常量0初始化左值對象i
10=i;//錯誤:賦值運算符左側(cè)必須為左值
int j=i;//左值對象i可以當(dāng)成右值,只是對其內(nèi)容進(jìn)行讀操作
引用是一種復(fù)合類型,我們可以把它理解為一個對象的別名。也就是說,當(dāng)我們創(chuàng)建一個對象的引用的時候,編譯器將引用的對象的內(nèi)容與這個別名綁定,不會把對象的內(nèi)容拷貝給引用。例如:
int sum=0;
int&lr=sum;//定義一個int類型的左值引用,引用左值對象sum的內(nèi)容
lr+=1;//相當(dāng)于sum+=1
int&&rr1=sum+5;//定義一個右值引用,引用右值表達(dá)式
int&&rr2=10;//引用字面值常量
通過引用訪問與其綁定的對象與直接訪問引用的對象效果是一樣的。左值引用只能綁定到左值對象,而右值引用必須綁定到右值對象。例如:
int&&rr3=rr1;//錯誤:rr1為左值
雖然rr1為右值引用,但rr1本身是左值對象,因此rr3不能引用左值對象。
定義引用時,需要注意以下幾點:1)定義一個引用時必須要初始化,使其與一個對象綁定;2)使用引用時,不能改變綁定的對象,也就是說,一旦定義一個引用,它始終與初始化的對象綁定在一起;3)引用必須綁定到相同數(shù)據(jù)類型的對象上。
在教學(xué)方法上,正反舉例法可以有效地加深學(xué)生對概念的理解,這對于C++等實踐性比較強(qiáng)的課程是非常適用的教學(xué)方法。通過正反舉例,學(xué)生可以清晰地理解左值、右值、左值引用和右值引用等概念并掌握易錯點。另外,對于前后有關(guān)聯(lián)的知識點,采用遞進(jìn)式教學(xué)方法有助于學(xué)生理解新知識。例如,在講解引用之前,學(xué)生需要了解左值和右值的概念。只有正確把握了左值和右值的概念,才能掌握引用的概念。
從以上所述可以看出,引用的行為類似于指針常量(注意不是指向常量的指針)的行為。一個指針常量在定義時與一個對象綁定,使用期間不允許改變其指向。
int i=0;
int*const p=&i;//不允許指針p指向其他對象
(*p)++;//等價于i++
cout<
雖然定義引用時編譯器不會根據(jù)引用對象的類型分配存儲空間,但和指針一樣,引用本身需要占用存儲空間。
cout< 引用和普通指針的主要區(qū)別在于:1)定義引用時必須初始化,定義指針時不需要初始化;2)賦值行為不同:對引用賦值修改綁定對象的值,對指針賦值改變其指向的對象。 把這一點講清楚,學(xué)生就不難理解引用了,而且對于后續(xù)知識的講解也十分重要。例如,C++的多態(tài)行為既可以作用于指針也可以作用于引用,如果我們沒有講清楚這一點,學(xué)生很難理解引用的多態(tài)行為是怎樣實現(xiàn)的。 通過右值引用可以訪問無名的臨時對象,相當(dāng)于給無名的臨時對象的取個名字,本質(zhì)上將一個短暫的右值轉(zhuǎn)化為持久的左值。也就是說,該右值又“重獲新生”,其生命期與被綁定的右值引用的生命期一致。 double&&rr=sqrt(3.14); 函數(shù)sqrt的返回值保存在一個臨時對象里,rr就是這個臨時對象的引用,通過rr可以直接訪問它。因此,原來臨時的右值實際上變成了一個持久的左值。 右值引用的這個特性非常重要,是移動語義的基礎(chǔ),教學(xué)過程中應(yīng)重點講解。在教學(xué)方法上,使用上面的舉例分析法可使學(xué)生能夠透過現(xiàn)象看到右值引用的本質(zhì)。 如果右值引用聲明&&與類型推導(dǎo)結(jié)合起來,那么&&并非總意味著右值引用類型,此時&&將成為一種通用引用類型[5]: int i=0; auto&&rr1=10;//rr1被推導(dǎo)為右值引用 auto&&rr2=i;//rr2被推導(dǎo)為左值引用 上面第二條語句中auto&&根據(jù)字面值常量推導(dǎo)出rr1為右值引用,而第三條語句中rr2被推導(dǎo)為一個左值引用。右值聲明為auto&&的對象都是通用引用。類似的,如果一個模板函數(shù)形參為模板類型參數(shù)(T)的右值引用(T&&),那么形參類型也有同樣的行為: template f(10);//par被推導(dǎo)為右值引用 f(i);//par被推導(dǎo)為左值引用,假設(shè)i為左值對象 在教學(xué)方法上,采用類比法可以加深學(xué)生對左值引用的理解和避免對右值引用錯誤的認(rèn)識。通過左值引用和指針類比,學(xué)生可以清晰地認(rèn)識到左值引用的本質(zhì)。當(dāng)右值引用與類型推導(dǎo)結(jié)合時,便成了一種通用引用。通過類比,學(xué)生可以避免遇見&&即為右值引用的錯誤認(rèn)識,進(jìn)一步提高學(xué)生對引用的認(rèn)識。 使用左值引用作為函數(shù)形參有兩個考慮:1)通過形參可以改變實參的值;2)可以避免對實參的拷貝,提高程序運行效率??梢酝ㄟ^下面的例子對上面的知識點進(jìn)行講解。 假設(shè)有如下函數(shù)調(diào)用: Foo a; passByValue(a);//調(diào)用Foo的復(fù)制構(gòu)造函數(shù),打印輸出copied cout< passByRef(a);//形參x為a的引用,a的值被修改 cout< 在教學(xué)方法中,通過案例對比法,講解引用形參和非引用形參的區(qū)別。如上面的例子中,函數(shù)passByRef的形參為實參的引用,在調(diào)用的過程中不會發(fā)生復(fù)制構(gòu)造。對形參進(jìn)行修改等價于修改實參的值。如果采用非引用形參,實參向形參傳遞的是值,因此會發(fā)生復(fù)制構(gòu)造才能將值傳遞給形參。通過對比講解,學(xué)生可以清楚了解到值傳遞和引用傳遞的區(qū)別。 通過這個例子的講解,可以提示學(xué)生引用形參有利無弊,并歸納出引用形參的通用性。因此,建議學(xué)生盡量使用引用形參,引起學(xué)生的重視。進(jìn)一步考慮到程序的安全性,如果對實參只執(zhí)行讀操作,可以告訴學(xué)生使用const引用,保證實參的安全性。 與引用形參類似,函數(shù)值以引用的方式返回可以避免復(fù)制構(gòu)造,提高程序執(zhí)行效率。例如,F(xiàn)oo類的成員函數(shù)get返回數(shù)據(jù)成員m_x的引用,與指針類似,返回的值與m_x是同一個內(nèi)存空間。如果,成員函數(shù)get被改為: string Foo::get(){return m_x;} 在返回時,將以復(fù)制構(gòu)造的方式構(gòu)造一個臨時對象,這個臨時對象是m_x的一個副本,構(gòu)造的時候需要分配存儲空間并進(jìn)行數(shù)據(jù)復(fù)制。因此,普通值返回的方式會大大降低程序的執(zhí)行效率。 在教學(xué)方法上,采用與引用形參相類比的方法,可以很容易把引用返回的優(yōu)點講清楚,學(xué)生也比較容易接受。在此基礎(chǔ)之上,接下來可采用互動式與引導(dǎo)式教學(xué)方法把引用對象的存儲類型的要求講清楚。例如,可以向?qū)W生提問:可以返回局部對象的引用嗎?互動之后,給出答案:函數(shù)不能返回一個局部對象的引用。這是因為當(dāng)一個函數(shù)返回時,函數(shù)體中的局部對象包括非引用形參都會消亡。因此,引用已經(jīng)消亡的對象是沒有意義的。例如,下面fun函數(shù)返回的局部對象i: int&fun(int i){return i;}//錯誤:不能返回局部對象的引用引導(dǎo)和互動式的教學(xué)方式不但能夠加深學(xué)生對所學(xué)知識的理解,準(zhǔn)確地把握事物的本質(zhì),而且還能自然地引入新的知識或強(qiáng)化對已有知識的認(rèn)識。 移動語義是C++11引入的新的語言特性,可以說移動語義就是為了性能而生。臨時對象是影響程序運行效率的一個重要因素。一個程序在運行期間,不可避免地會產(chǎn)生大量的臨時對象,這些臨時對象的生命期是短暫的,幾乎只被使用一次就會消亡。程序員無法控制這些臨時對象,它們不可以訪問。因此,它們往往也被稱為幽靈對象。為了解決這個問題,基于右值引用的移動語義便由此而生。 一般情況下,一個類要啟用移動語義,需要定義移動成員。改造的Foo類如下: 第二個構(gòu)造函數(shù)為移動構(gòu)造函數(shù),其形參為Foo類型的右值引用,用來接受一個右值。當(dāng)執(zhí)行完此移動構(gòu)造函數(shù)之后,實參對象的資源會被“竊取”。例如: Foo a(“test”); Foo b(std::move(a)); //庫函數(shù)move將左值a轉(zhuǎn)化為右值 cout<<“b:”< if(!a.get())cout<<“a is empty”< 為了方便測試,上述代碼利用庫函數(shù)move將左值對象a轉(zhuǎn)化為右值,用來觸發(fā)移動構(gòu)造函數(shù)。在構(gòu)造對象b的過程中,程序并沒有為b分配存儲空間和復(fù)制數(shù)據(jù)的操作,而是直接把a的內(nèi)容“竊取”出來。構(gòu)造完畢之后,a已經(jīng)沒有任何資源了。 在教學(xué)方法上,舉例法可以清晰地把移動語義講清楚。通過上面的例子,學(xué)生可以非常清楚地看到對象a的資源是如何被對象b“竊取”的,而且學(xué)生也會體會到在執(zhí)行的過程中,不需要分配存儲空間和復(fù)制數(shù)據(jù),程序的性能得到了明顯的改善。 在學(xué)生掌握了移動構(gòu)造函數(shù)之后,可采用任務(wù)驅(qū)動的授課方式講解移動賦值運算符。即,先提問如何“竊取”=符號右側(cè)對象的資源,然后分析任務(wù)需求,最后給出最終的實現(xiàn)。 Foo&operator=(Foo&&rhs){m_x=rhs.m_x;rhs.m_x=nullptr;return*this;} 多態(tài)性是面向?qū)ο蟪绦蛟O(shè)計的核心技術(shù)之一,它是通過虛函數(shù)的動態(tài)綁定來實現(xiàn)的,即在運行期間,根據(jù)基類指針或引用所綁定的對象來確定具體的行為。因此,觸發(fā)動態(tài)綁定的前提是使用指針或引用。下面構(gòu)造一個抽象基類和兩個公有派生類: struct B{virtual void fun()=0;}; struct D1:public B{void fun(){cout<<“fun of D1”< struct D2:public B{void fun(){cout<<“fun of D2”< 其中,基類B定義了一個虛接口,兩個派生類分別定義了各自版本的實現(xiàn)。為了測試基于引用的動態(tài)綁定,設(shè)計如下測試函數(shù): void test(const B&b){b.fun();} test函數(shù)形參為基類B的引用,函數(shù)體為一個成員fun函數(shù)的調(diào)用。測試代碼如下: D1 d1;D2 d2; test(d1);//打印輸出:fun of D1 test(d2);//打印輸出:fun of D2 上面代碼定義了兩個派生類對象d1和d2。在調(diào)用test函數(shù)時,當(dāng)基類引用形參b與一個派生類對象綁定時,便會調(diào)用相對應(yīng)版本的fun函數(shù),從而實現(xiàn)動態(tài)綁定。 在教學(xué)方法上,建議采用案例分析的方法把抽象的概念具體化,從而使學(xué)生能夠深入地體會和理解動態(tài)綁定與多態(tài)性的概念。設(shè)計的案例要重點突出,精簡扼要,抓住講解內(nèi)容的本質(zhì)。 筆者運用上述教學(xué)方法,通過對比分析學(xué)生每學(xué)期進(jìn)行的四次上機(jī)考核結(jié)果和課程設(shè)計的實訓(xùn)效果發(fā)現(xiàn),學(xué)生逐漸加強(qiáng)了引用的使用,程序的運行效率得到了很大的改進(jìn)。 圍繞C++11新標(biāo)準(zhǔn)下引用知識點在教學(xué)過程中的難點和重點問題,研究和討論了引用的本質(zhì)及其四種不同的應(yīng)用場合,針對具體教授知識點的不同特點,推薦了相應(yīng)的教學(xué)方法。筆者運用上述教學(xué)方法,精心設(shè)計了相應(yīng)的教學(xué)案例,通過近5年內(nèi)的教學(xué)效果分析,上述工作對學(xué)生理解和使用引用具有重要作用。 [1]國際標(biāo)準(zhǔn)組織和國際電工委員會.ISO/IEC 14882:2011,Information Technology-Programming Languages-C++[S].ISO.2 2011-09-1(3):187-189. [2]Stanley B.Lippman,Josée Lajoie,Barbara E.Moo.C++Primer[M](中文版第5版).王剛,楊巨峰,譯.北京:電子工業(yè)出版社,2013-09-01:471-472. [3]Michael Wong,IBM XL編譯器中國開發(fā)團(tuán)隊.深入理解C++11:C++11新特性解析與應(yīng)用[M].北京:機(jī)械工業(yè)出版社,2013-06-07:68-85. [4]祁宇.深入應(yīng)用C++11:代碼優(yōu)化與工程級應(yīng)用[M].北京:機(jī)械工業(yè)出版社,2015-05-01:64-78. [5]Scott Meyers.Effective Modern C++[M].O'Reilly Media.2014-11-7:164-168. Research on the Usages of Reference and Teaching Methods in C++11 New Standard LI Chang-he (China University of Geosciences,School of Automation,Wuhan 430074) Reference is an important feature of C++,and it has been extended in the new standard C++11,where introduces the r-value reference.However,there is seldom analysis of teaching methods for reference under the new standard.Analyzes the characteristics of reference and its applications comprehensively,discusses the effective teaching methods.By using these teaching methods,students are able to fully un?derstand reference and its usages. 1007-1423(2017)27-0003-05 10.3969/j.issn.1007-1423.2017.27.001 是C++語言的一個重要特性,C++11新標(biāo)準(zhǔn)對引用進(jìn)行拓展,引入右值引用。目前針對新標(biāo)準(zhǔn)下引用的教學(xué)方法還很少有專門論述。對C++新標(biāo)準(zhǔn)下引用進(jìn)行深入分析,闡述使用引用的場合和方法,探討有效的教學(xué)方法。教學(xué)效果表明,這些方法能夠幫助學(xué)生深入理解和正確使用引用。 C++11;左值引用;右值引用;教學(xué)方法 國家自然科學(xué)基金面上項目(No.61673355)、湖北省“楚天學(xué)子”人才項目(No.162301132807)、中國地質(zhì)大學(xué)(武漢)“騰飛計劃”項目(No.G1323531750)、中國地質(zhì)大學(xué)(武漢)研究生教育教學(xué)改革研究項目(No.YJG2017101) 李長河(1983-),男,河北秦皇島人,副教授,博士生導(dǎo)師,英國萊斯特大學(xué)博士研究生學(xué)位,研究方向為智能優(yōu)化與學(xué)習(xí) 2017-07-18 2017-09-14 C++11;L-value Reference;R-value Reference;Teaching Methods2.2 右值轉(zhuǎn)化為左值
2.3 通用引用
3 使用引用
3.1 左值引用形參
3.2 返回左值引用
3.3 觸發(fā)移動語義
3.4 觸發(fā)動態(tài)綁定
4 結(jié)語