C/C++ 引用作爲函數的返回值

(1)什麼是引用?

引用就是變量的別名,操作一個變量的引用也就相當於操作變量本身,這一點跟指針很類似,但是操作引用不用像操作指針一樣,利用取地址符號,很不方便。而操作引用的話,則跟操作普通變量一樣,所以C++之中更加鼓勵使用引用。

(2)C++函數爲什麼要使用引用?

C語言之中大量利用指針作爲形參或者函數返回值,這是由於值拷貝會有很大的消耗(比如傳入傳出一個大的結構體)。所以在C++之中使用引用作爲函數參數和返回值的目的和使用指針是一樣的。而且形式上更加直觀,所以C++提倡使用引用。

(3)C++函數返回引用

C++之中函數的返回分爲以下幾種情況:

1)返回值方式:函數的返回值用於初始化在跳出函數時候創建的臨時對象。用函數返回值來初始化臨時對象與用實參初始化形參的方法是一樣的。如果返回類型不是引用的話,在函數返回的地方,會將返回值複製給臨時對象。且其返回值既可以是局部對象,也可以是表達式的結果。

2)返回引用:當函數返回引用類型的時候,沒有複製返回值,而是返回對象的引用(即對象本身)。

語法:

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

特別注意:

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

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

前面提到利用引用作爲函數形參和返回值的好處。

函數返回引用:實際上是一個變量的內存地址,既然是內存地址的話,那麼肯定可以讀寫該地址所對應的內存區域的值,即就是“左值”,可以出現在賦值語句的左邊。

1)函數返回引用的時候,可以利用全局變量(作爲函數返回),或者在函數的形參表中有引用或者指針(作爲函數返回),這兩者有一個共同點,就是返回執行完畢以後,變量依然存在,那麼返回的引用纔有意義。

#include <iotream>
int global_variable; //global variable
int &f(int t1, int t2) //return the reference of the global variable "global_variable"

{
    global_variable =  t1 + t2;
    return global_variable; 
}

int main(void)
{
    int f = f(1, 3);
    std::cout << global_variable << std::endl; //L1
    f(2, 8)++;
    std::cout << global_variable << std::endl; //l2
    f(2, 3) = 9;
    std::cout << global_variable << std::endl; //L3

    return 0;        
}

L1:執行過f(1,3),則global_variable變爲4。

L2:執行過f(2,8)++,f(2,8)爲10,可以執行++(表示其爲左值),變爲11。

L3:執行f(2,3),global_variable變爲5,由於其是左值,修改了global_variable的值,爲9。

2)  不能返回局部變量的引用如下面的例子,如果temp是局部變量,那麼它會在函數返回後被銷燬,此時對temp的引用就會成爲“無所指”的引用,程序會進入未知狀態。

//代碼來源:RUNOOB
#include<iostream>
using namespace std;
float temp;
float fn1(float r){
    temp=r*r*3.14;
    return temp;
} 
float &fn2(float r){ //&說明返回的是temp的引用,換句話說就是返回temp本身
    temp=r*r*3.14;
    return temp;
}
int main(){
    float a=fn1(5.0); //case 1:返回值
    //float &b=fn1(5.0); //case 2:用函數的返回值作爲引用的初始化值 [Error] invalid initialization of non-const reference of type 'float&' from an rvalue of type 'float'
                           //(有些編譯器可以成功編譯該語句,但會給出一個warning) 
    float c=fn2(5.0);//case 3:返回引用
    float &d=fn2(5.0);//case 4:用函數返回的引用作爲新引用的初始化值
    cout<<a<<endl;//78.5
    //cout<<b<<endl;//78.5
    cout<<c<<endl;//78.5
    cout<<d<<endl;//78.5
    return 0;
}

case 1:用返回值方式調用函數

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

case 2:用函數的返回值初始化引用的方式調用函數

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

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

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

 case 3:用返回引用的方式調用函數

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

case 4:用函數返回的引用作爲新引用的初始化值的方式來調用函數

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

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

#include <iostream>

int &t(int a, int b, int &result)
{
    result = a + b;
    return result;
}
int &f(int a, int b, int *result)
{
    *result = a + b;
    return *result;
}
int main(void)
{
    int t_variable;
    int *f_variable = new int;
    t(1, 2, t_variable)++;
    std::cout << t_variable << std::endl; //t_variable = 4
    f(1, 2, f_variable)--;
    std::cout << *f_variable << std::endl; //t_variable = 2
    delete f_variable;
    return 0;
}

exp3:

int &t(int a, int b)
{
    return a+b; //now allowed
}

4)當返回類成員的引用時,最好是const引用。這樣可以避免在無意的情況下破壞該類的成員。由於返回的是生命期在函數退出的時候還存在的變量地址,所以函數返回值做的任何操作就相當於對這個變量地址指向的變量做的操作。如果在返回值前面加const那麼這個變量就不允許被修改。

#include <iostream>

const int &t(int a, int b, int &result)
{
    result = a + b;
    return result;
}
int main()
{
    int t_variable;
    t(1, 2, t_variable);
    std::cout << t_variable << std::endl; //t_variable = 3
    t(1, 2, t_variable)++;
    std::cout << t_variable << std::endl; //not allowed
}

5) 可以用函數返回的引用作爲賦值表達式中的左值

#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; 
}

總結:

(1)使用引用當作函數參數和返回值,效率更高。

(2)函數返回的對象引用,必須在調用函數前就已經存在,不允許返回局部變量的引用!

(3)當不希望返回的對象被修改的時候,可以添加const。

參考:

https://blog.csdn.net/weixin_40539125/article/details/81410008

https://www.cnblogs.com/floatedclouds/archive/2011/10/13/2209917.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章