C++:引用的簡單理解

2021-03-02 C語言與C++編程

作者:Tom文星

連結:https://www.cnblogs.com/duwenxing/p/7421100.html

前言:引用是C++一個很重要的特性,最近看了很多有關引用的資料和博客,故在此對引用的相關知識進行總結

一、什麼是引用

引用,顧名思義是某一個變量或對象的別名,對引用的操作與對其所綁定的變量或對象的操作完全等價

語法:類型 &引用名=目標變量名;


特別注意:

1、&不是求地址運算符,而是起標誌作用

2、引用的類型必須和其所綁定的變量的類型相同

#include<iostream>
using namespace std;
int main(){
    double a=10.3;
    int &b=a; 
    cout<<b<<endl;
}3.聲明引用的同時必須對其初始化,否則系統會報錯

#include<iostream>
using namespace std;
int main(){
    int &a; 
    return 0;
}

4、引用相當於變量或對象的別名,因此不能再將已有的引用名作為其他變量或對象的名字或別名

5、引用不是定義一個新的變量或對象,因此內存不會為引用開闢新的空間存儲這個引用


#include<iostream>
using namespace std;
int main(){
    int value=10;
    int &new_value=value;
    cout<<"value在內存中的地址為:"<<&value<<endl;
    cout<<"new_value在內存中的地址為:"<<&new_value<<endl;
    return 0;
}

6、對數組的引用

語法:類型 (&引用名)[數組中元素數量]=數組名;

#include<iostream>
using namespace std;
int main(){
    int a[3]={1,2,3};
    int (&b)[3]=a;
    cout<<&a[0]<<" "<<&b[0]<<endl;
    cout<<&a[1]<<" "<<&b[1]<<endl;
    cout<<&a[2]<<" "<<&b[2]<<endl;
    return 0;
}


7、對指針的引用

語法:類型 *&引用名=指針名;

#include<iostream>
using namespace std;
int main(){
    int a=10;
    int *ptr=&a;
    int *&new_ptr=ptr;
    cout<<&ptr<<" "<<&new_ptr<<endl;
    return 0; 
}

二、引用的應用A、引用作為函數的參數


#include<iostream>
using namespace std;
void swap(int &a,int &b){
    int temp=a;
    a=b;
    b=temp; 
}
int main(){
    int value1=10,value2=20;
    cout<<"--交換前---"<<endl;
    cout<<"value1的值為:"<<value1<<endl; 
    cout<<"value2的值為:"<<value2<<endl;
    swap(value1,value2); 
    cout<<"--交換後---"<<endl;
    cout<<"value1的值為:"<<value1<<endl; 
    cout<<"value2的值為:"<<value2<<endl;
    return 0;
}


特別注意:

1、當用引用作為函數的參數時,其效果和用指針作為函數參數的效果相當。當調用函數時,函數中的形參就會被當成實參變量或對象的一個別名來使用,也就是說此時函數中對形參的各種操作實際上是對實參本身進行操作,而非簡單的將實參變量或對象的值拷貝給形參。

2、通常函數調用時,系統採用值傳遞的方式將實參變量的值傳遞給函數的形參變量。此時,系統會在內存中開闢空間用來存儲形參變量,並將實參變量的值拷貝給形參變量,也就是說形參變量只是實參變量的副本而已;並且如果函數傳遞的是類的對象,系統還會調用類中的拷貝構造函數來構造形參對象。而使用引用作為函數的形參時,由於此時形參只是要傳遞給函數的實參變量或對象的別名而非副本,故系統不會耗費時間來在內存中開闢空間來存儲形參。因此如果參數傳遞的數據較大時,建議使用引用作為函數的形參,這樣會提高函數的時間效率,並節省內存空間。

3、使用指針作為函數的形參雖然達到的效果和使用引用一樣,但當調用函數時仍需要為形參指針變量在內存中分配空間,而引用則不需要這樣,故在C++中推薦使用引用而非指針作為函數的參數

4、如果在編程過程中既希望通過讓引用作為函數的參數來提高函數的編程效率,又希望保護傳遞的參數使其在函數中不被改變,則此時應當使用對常量的引用作為函數的參數。

5、數組的引用作為函數的參數:C++的數組類型是帶有長度信息的,引用傳遞時如果指明的是數組則必須指定數組的長度

#include<iostream>
using namespace std;
void func(int(&a)[5]){

}
int main(){
    int number[5]={0,1,2,3,4};
    func(number); 
    return 0; 
 }

B、常引用

語法:const 類型 &引用名=目標變量名;


常引用不允許通過該引用對其所綁定的變量或對象進行修改

#include<iostream>
using namespace std;
int main(){
    int a=10;
    const int &new_a=a;
    new_a=11;
    return 0;
}

特別注意:

先看下面的例子

#include<iostream>
#include<string> 
using namespace std;
string func1(){
    string temp="This is func1";
    return temp;
}
void func2(string &str){
    cout<<str<<endl;
}
int main(){
    func2(func1());
    func2("Tomwenxing");
    return 0;
}

運行上面的程序編譯器會報錯

這是由於func1()和「Tomwenxing」都會在系統中產生一個臨時對象(string對象)來存儲它們,而在C++中所有的臨時對象都是const類型的,而上面的程序試圖將const對象賦值給非const對象,這必然會使程序報錯。如果在函數func2的參數前添加const,則程序便可正常運行了

#include<iostream>
#include<string> 
using namespace std;
string func1(){
    string temp="This is func1";
    return temp;
}
void func2(const string &str){
    cout<<str<<endl;
}
int main(){
    func2(func1());
    func2("Tomwenxing");
    return 0;
}

C、引用作為函數的返回值

語法:類型 &函數名(形參列表){ 函數體 }


特別注意:

1、引用作為函數的返回值時,必須在定義函數時在函數名前將&

2、用引用作函數的返回值的最大的好處是在內存中不產生返回值的副本


#include<iostream>
using namespace std;
float temp;
float fn1(float r){
    temp=r*r*3.14;
    return temp;

float &fn2(float r){ 
    temp=r*r*3.14;
    return temp;
}
int main(){
    float a=fn1(5.0); 
    
                           
    float c=fn2(5.0);
    float &d=fn2(5.0);
    cout<<a<<endl;
    
    cout<<c<<endl;
    cout<<d<<endl;
    return 0;
}

上例中4個case的說明解釋:

case 1:用返回值方式調用函數(如下圖,圖片來源:伯樂在線):

返回全局變量temp的值時,C++會在內存中創建臨時變量並將temp的值拷貝給該臨時變量。當返回到主函數main後,賦值語句a=fn1(5.0)會把臨時變量的值再拷貝給變量a

case 2:用函數的返回值初始化引用的方式調用函數(如下圖,圖片來源:伯樂在線)

這種情況下,函數fn1()是以值方式返回到,返回時,首先拷貝temp的值給臨時變量。返回到主函數後,用臨時變量來初始化引用變量b,使得b成為該臨時變量到的別名。由於臨時變量的作用域短暫(在C++標準中,臨時變量或對象的生命周期在一個完整的語句表達式結束後便宣告結束,也就是在語句float &b=fn1(5.0);之後) ,所以b面臨無效的危險,很有可能以後的值是個無法確定的值。

 如果真的希望用函數的返回值來初始化一個引用,應當先創建一個變量,將函數的返回值賦給這個變量,然後再用該變量來初始化引用:

1 int x=fn1(5.0);
2 int &b=x;

case 3:用返回引用的方式調用函數(如下圖,圖片來源:伯樂在線)

這種情況下,函數fn2()的返回值不產生副本,而是直接將變量temp返回給主函數,即主函數的賦值語句中的左值是直接從變量temp中拷貝而來(也就是說c只是變量temp的一個拷貝而非別名) ,這樣就避免了臨時變量的產生。尤其當變量temp是一個用戶自定義的類的對象時,這樣還避免了調用類中的拷貝構造函數在內存中創建臨時對象的過程,提高了程序的時間和空間的使用效率。

case 4:用函數返回的引用作為新引用的初始化值的方式來調用函數(如下圖,圖片來源:伯樂在線)

這種情況下,函數fn2()的返回值不產生副本,而是直接將變量temp返回給主函數。在主函數中,一個引用聲明d用該返回值初始化,也就是說此時d成為變量temp的別名。由於temp是全局變量,所以在d的有效期內temp始終保持有效,故這種做法是安全的。

3、不能返回局部變量的引用。如上面的例子,如果temp是局部變量,那麼它會在函數返回後被銷毀,此時對temp的引用就會成為「無所指」的引用,程序會進入未知狀態。

4、不能返回函數內部通過new分配的內存的引用。雖然不存在局部變量的被動銷毀問題,但如果被返回的函數的引用只是作為一個臨時變量出現,而沒有將其賦值給一個實際的變量,那麼就可能造成這個引用所指向的空間(有new分配)無法釋放的情況(由於沒有具體的變量名,故無法用delete手動釋放該內存),從而造成內存洩漏。因此應當避免這種情況的發生

5、當返回類成員的引用時,最好是const引用。這樣可以避免在無意的情況下破壞該類的成員。

6、可以用函數返回的引用作為賦值表達式中的左值

#include<iostream>
using namespace std;
int value[10];
int error=-1;
int &func(int n){
    if(n>=0&&n<=9)
        return value[n];
    else
        return error;
}

int main(){
    func(0)=10;
    func(4)=12;
    cout<<value[0]<<endl;
    cout<<value[4]<<endl;
    return 0; 
}


D、用引用實現多態

在C++中,引用是除了指針外另一個可以產生多態效果的手段。也就是說一個基類的引用可以用來綁定其派生類的實例

class Father;//基類(父類)
class Son:public Father{}
Son son;
Father &ptr=son;


特別注意:

ptr只能用來訪問派生類對象中從基類繼承下來的成員。如果基類(類Father)中定義的有虛函數,那麼就可以通過在派生類(類Son)中重寫這個虛函數來實現類的多態。

三、總結

1、在引用的使用中,單純給某個變量去別名是毫無意義的,引用的目的主要用於在函數參數的傳遞中,解決大塊數據或對象的傳遞效率和空間不如意的問題

2、用引用傳遞函數的參數,能保證參數在傳遞的過程中不產生副本,從而提高傳遞效率,同時通過const的使用,還可以保證參數在傳遞過程中的安全性

3、引用本身是目標變量或對象的別名,對引用的操作本質上就是對目標變量或對象的操作。因此能使用引用時儘量使用引用而非指針

●編號552,輸入編號直達本文

●輸入m獲取文章目錄

分享C/C++技術文章

相關焦點

  • c++ 之布爾類型和引用的學習總結!
    2、c++中的三目運算符可以直接返回變量本身,既可以作為右值使用,也可以作為左值來使用。3、c++中的三目運算符可能返回的值中如果有一個是常量值,則不能作為左值進行使用,這點要切記和理解。>--引用的語法:Type &name = var;這裡舉個簡單的示例: int a =4; int& b =a; //b為a的別名 b = 5;//操作b就是操作a--注意普通引用在定義時必須用同類型的變量進行初始化。
  • C++11中的右值引用
    ,但不好理解的內容。右值引用,顧名思義,就是引用右值。那什麼是右值呢?書的給出的解釋是:可以取地址的,有名字的就是左值。反之,為右值。T a(1);T b(a);其中 a是左值,b也是右值。int a; // a左值int b = a + 1;  // (a + 1)是右值,b是左值那為什麼出來右值引用的概念呢?因為我們用左值引用的方式對訪問右值,是不能對其進行修改的。如:const T &d = ReturnRValue();  //!
  • C++、java 和 C 的區別
    一、基礎類型c++:** java:** C#:1.以java為準,c++裡面的int short long 像這樣的整型 一般都有unsigned 和signed的區分 ,這個跟java和c# 的區別比較大,但c#裡面有unit ulong ushort 這三種就相當於c++的修飾詞unsigned,當c++李明的變量類型定義unsigned,就默認是整數。
  • c++11新特性,所有知識點都在這了!
    純右值:運算表達式產生的臨時變量、不和對象關聯的原始字面量、非引用返回的臨時變量、lambda表達式等都是純右值。將亡值:可以理解為即將要銷毀的值。左值引用:對左值進行引用的類型。右值引用:對右值進行引用的類型。移動語義:轉移資源所有權,類似於轉讓或者資源竊取的意思,對於那塊資源,轉為自己所擁有,別人不再擁有也不會再使用。
  • 「最佳實踐」C++陷阱與套路
    本文結合號主的工作經驗和學習心得,對C++語言的一些高級特性,做了簡單介紹;對一些常見的誤解,做了解釋澄清;對比較容易犯錯的地方,做了歸納總結;希望藉此能增進大家對C++語言了解,減少編程出錯,提升工作效率。
  • C++ 的門門道道 | 技術頭條 - CSDN
    本文結合作者的工作經驗和學習心得,對C++語言的一些高級特性,做了簡單介紹;對一些常見的誤解,做了解釋澄清;對比較容易犯錯的地方,做了歸納總結;希望藉此能增進大家對C++語言了解,減少編程出錯,提升工作效率。一、我的程序裡用了全局變量,但為什麼進程正常停止的時候會莫名其妙的core掉?
  • 再論C++中的const和引用
    /a.outc = arc = atrc = c二、引用的總結:1、引用與指針有什麼關係,以及如何理解"引用的本質就是指針常量"?(1)指針是一個常量:值為一個內存地址,不需要初始化,可以保存不同的地址通過指針可以訪問對應內存地址中的值指針可以被const修飾成為常量或者只讀變量(2)引用只是一個變量的新名字:對引用的操作(賦值、取地址等)都會傳遞到代表的變量上const引用使其代表的變量具有隻讀屬性引用必須在定義時初始化
  • C++ vector詳解
    可以簡單的認為,vector是一個能夠存放任意類型的動態數組。接下來,請跟隨小編一起來複習一下吧。以下是正文前言本文mark了vector的一些接口,介紹了vector中的對內存和對象的管理詳解請見cppreference-vector。
  • Effective C++條款13 C++基本功之智能指針
    智能指針簡介C++語言標準庫STL提供了一套智能指針接口,且一直處於完善狀態,如下圖所示:最早的智能指針是std::auto_ptr,到c++11才開始廣泛使用,平時用得最多的是這三個:其中unique_ptr只能指向一個對象,shared_ptr是通用的智能指針,weak_ptr可以理解為只讀用途的指針,這種指針不改變引用計數。
  • 理解 Java 中的弱引用
    我開始困惑,為什 麼這塊的知識如此不被重視,畢竟弱引用是一個很有用途的特性,況且這個特性已經在7年前 Java 1.2發布時便引入了。好吧,這裡我不期待你看完本文之後成為一個弱引用方面的專家,但是我認為至少你應該了解什麼是弱引用,如何使用它們,並且什麼場景使用。既然它們是一些不知名的概念,我簡單就著前面的三個問題來說明一下。
  • python+C、C++混合編程的應用
    有的語言專注於簡單高效,比如python,內建的list,dict結構比c/c++易用太多,但同樣為了安全、易用,語言也犧牲了部分性能。在有些領域,比如通信,性能很關鍵,但並不意味這個領域的coder只能苦苦掙扎於c/c++的陷阱中,比如可以使用多種語言混合編程。
  • 左值、右值、左值引用、右值引用
    左值和右值左值(left-values),縮寫:lvalues右值(right-values),縮寫:rvalues直接上官網查,我一向倡導自己去懂得原理,而原理都是老外寫的,當然我只是針對c++程式語言這樣說。
  • 那些容易犯錯的c++保留字
    本文首發 | 公眾號:lunvey目前正在學習vc++6.0開發,而這裡面使用的是c++98標準。
  • 深入淺出 C++ 11 右值引用
    If-it-has-a-name Rule:因為不論 左值引用 還是 右值引用 的變量(或參數)在初始化後,都是左值(參考 § 2.1):所以,返回右值引用變量時,需要使用 std::move()/std::forward() 顯式的 § 5.4 移動轉發 或 § 5.3 完美轉發,將變量的類型 「還原」 為右值引用。
  • 跟我學C++中級篇——STL的學習
    一、c++標準庫C++的標準庫主要包含兩大類,首先是包含C的標準庫的,當然,為了適應c++對一些C庫進行了少許的修改和增加。最重要的當然是面向對象的c++庫;而c++庫又可以分成兩大類,即面向對象的c++庫和標準模板庫,也就是題目中的STL。
  • json for modern c++的使用
    然而C++對json沒有很好的內置支持,因此往往要引用一些第三方庫。而網上很多json的包,形形色色種類繁多,但是要麼功能不夠強大,要麼難以使用。json for modern c++是一款非常好用的json庫,具有語法直觀和使用簡單的特點,並且是用C++11標準編寫的,此外還支持STL和json容器之間的轉換,可謂集方便又強大。
  • c++之內存分配、命名空間、強制類型轉換學習總結
    一、C++動態內存分配:在學習c語言的時候,我們一般都是使用庫函數malloc()來進行內存的申請分配,然後使用庫函數free()來進行釋放申請到的內存;現在在c++裡面採用了另外一種內存申請的方法:c++中通過
  • 濤哥講事-新手教程5-C++語句學習之賦值和輸入輸出語句
    賦值語句所謂賦值,很顯然就是把一個值賦予給一個變量,在c++中最基本的賦值就是用等號「=」來完成。比如a=a+10,意思就是把等號右邊的值賦給左邊的變量,右邊可以是具體的值,也可以是一個表達式。a=5;a=3+5;這個很容易懂吧。2.
  • C++的轉換手段並與explicit關鍵詞配合使用
    >++作者:良知猶存轉載授權以及圍觀:歡迎添加微信公眾號:羽林君隱式轉化c++const_cast,字面上理解就是去const屬性。dynamic_cast,命名上理解是動態類型轉換。如子類和父類之間的多態類型轉換。
  • C 2 C++進階篇(1)
    之前一直是對於面向過程的編程,python有過那種對象風格的編程,但是對於oop的實際開發還停留在表面,沒有獨立的開發c++經驗,也有好幾年沒有碰過c了。由於接手Qt的相關項目,所以對c to c++的進階希望能進行個自我總結。