對於C++中引用和指針的理解

引用:

  • 引用是C++提供的一種數據類型。定義引用類型變量的一般格式爲:

    <數據類型> & <引用變量名> = <變量名>;
    //變量名爲已定義的變量
  • 我們在程序中定義引用類型變量,實際上是給已定義的變量起一個別名而已,引用類型的變量沒有單獨的存儲空間,而是與其相關聯的變量使用同一空間。舉個生活中的例子:假如你叫張三,這是你父母給你取的名字。然而,你逗比的朋友李四很喜歡叫你“三哥”。這裏的張三就是變量名,三哥就是引用變量名。但無論是張三還是三哥,都是指你本人。

  • 使用:

#include <iostream>
using namespace std;
int main()
{
    int a = 9;
    int &refa = a;
    cout << "a = " << a << " , " << "refa = " << refa << endl;
    refa = 10;
    cout << "a = " << a << " , " << "refa = " << refa << endl;
}
  輸出如下:
  a = 9 , refa = 9
  a = 10 , refa = 10

  //從上述例子,我們可以看出,對引用變量的修改實質上是對其相關聯的變量的修改,這也進一步說明了這兩個變量是共用同一空間的。

指針:

  • 首先,我們來回顧一個簡單的例子:
//基本類型當做函數參數
  #include<iostream>
  using namespace std;

  void swap(int x, int y)
  {
    int t;
    t = x;
    x = y;
    y = t;
  }

  int main()
  {
    int x = 3, y = 9;
    swap(x, y);
    cout << "x = " << x << " , y = " << y << endl;
    return 0;
  }

 輸出如下:
 x = 3 , y = 9
//指針做函數參數
#include <iostream>
using namespace std;

void swap(int *px, int *py)
{
    int t;
    t = *px;
    *px = *py;
    *py = t;
}

int main()
{
    int x = 3, y = 9;
    swap(&x, &y);
    cout << "x = " << x << " , y = " << y << endl;
    return 0;
}
輸出如下:
x = 9, y = 3

對比這兩段,我們可以看出:用基本類型量做參數時,無法通過參數帶回多個結果。用指針做函數參數時,可以通過參數指針帶回多個運算結果,但是指針比較難理解,而且書寫通過間接訪問變量的語句也比較麻煩。

再來看下引用類型做函數參數的例子:

//引用類型做函數參數
#include <iostream>
using namespace std;

void swap(int &x, int &y)
{
    int t;
    t = x;
    x = y;
    y = t;
}

int main()
{
    int x = 3, y = 9;
    swap(x, y);
    cout << "x = " << x << " , " << "y = " << y << endl;
    return 0;
}
輸出如下:
x = 9, y = 3

可以看到,只是在swap()函數形參的定義時,寫成了變量引用形式(參數傳遞是相當於有int &x = x,;int &y = y;),執行效果卻和指針做函數參數時一樣。可以看到,使用引用參數類型做參數的優點是:

  • 避免了使用指針做參數時的程序書寫及理解的麻煩
  • 增強了程序的可讀性和編程的方便性,在被調函數中直接寫引用變量名,直觀且易理解
  • 提高了程序執行的效率,因爲系統在執行時不需要爲引用參數動態分配存儲空間

引用和指針的區別

  • 在聲明引用時必須將其初始化,而不能像指針那樣,先聲明,再賦值
int a;
int &refa;
refa = a;
//上面這種寫法是錯誤的
  • 引用更像是const指針,必須在創建時進行初始化,一旦與某個變量關聯起來,就至始至終都效忠於它。
 int a;
 int &refa = a;     //與下面一句等價
 int * const pa = &a;
 //其中,refa就與*pa等價。
  • 來看個例子:
#include <iostream>
using namespace std;
int main()
{
    int a = 10;
    int &refa = a;

    cout << "a = " << a
        << " , refa = " << refa << endl;

    cout << "a address = " << &a
        << " , refa address = " << &refa << endl;

    int b = 20;
    refa = b;       //嘗試更改引用變量refa,讓其成爲b的引用

    cout << "b = " << b
        << " , a = " << a
        << " , refa = " << refa << endl;

    cout << "b address = " << &b
        << " , refa address = " << &refa
        << " , a address = " << &a << endl;

    return 0;
}
輸出如下:
a = 10, refa = 10
a address = 0073F804, refa address = 0073F804
b = 20, a = 20, refa = 20
b address = 0073F7EC, refa address = 0073F804, a address = 0073F804

分析:
剛開始,refa引用的是a。接着,我們使用refa = b;這一句,嘗試將refa更改爲b的引用。但是從輸出結果來看,這一句並沒有達到我們想要的目的。雖然,refa的值變成了20,但與此同時,a的值也變成了20。而且,refa的地址仍然與a的地址相同,與b的地址不同。這進一步說明,引用只在創建時初始化有效,一旦與某個變量關聯起來,就至始至終都效忠於它,即使通過賦值的方式來設置也是無效的。此外,通過賦值來設置的話,只是能改變與引用相關聯的變量的內容而已。

  • 再看個例子:
  #include<iostream>
  using namespace std;
  int main()
  {
    int a = 10;
    int *pa = &a;
    int &refa = *pa;
    int b = 20;
    pa = &b;
    cout << "a = " << a
        << " , b = " << b
        << " , refa = " << refa<<endl;

    cout << "a address = " << &a
        << " , pa address = " << pa
        << " , b address = " << &b
        << " , refa address = " << &refa << endl;

    return 0;
  }
 輸出結果如下:
 a = 10 , b = 20 , refa = 10
 a address = 00DEFA08 , pa address = 00DEF9E4 , b address = 00DEF9E4 , refa address = 00DEFA08

分析:
這裏將refa初始化爲*pa,*pa即爲a。接下來,更改pa的指向,讓其指向變量b。最後,我們再輸出各個變量的值,發現refa的值與地址依舊與a相同。這更加證明了上面例子的說法與結論。引用一旦設置成功,就無法再更改指向。這是與普通的指針使用上最大的不同。

  • 最後,看個綜合的例子:
#include <iostream>
using namespace std;

void modify_value10(int n);         //參數:值
void modify_value20(int *pn);       //參數:指針
void modify_value30(int &n);        //參數:引用

int main()
{
    int a = 5;
    int *pa = &a;
    int &refa = a;

    cout << "a address = " << &a << endl << endl;
    modify_value10(a);
    cout << "modify_value10(): a = " << a << endl << endl;
    modify_value20(pa);
    cout << "modify_value20(): a = " << a << endl << endl;
    modify_value30(refa);
    cout << "modify_value30(): a = " << a << endl << endl;
}

void modify_value10(int n)
{
    n = 10;
    cout << "n address : " << &n << endl;
}

void modify_value20(int *pn)
{
    *pn = 20;
    cout << "*pn address : " << pn << endl;
}

void modify_value30(int &n)
{
    n = 30;
    cout << "n address : " << &n << endl;
}
輸出結果如下:
a address = 010FF9AC

n address : 010FF8C0
modify_value10(): a = 5

*pn address : 010FF9AC
modify_value20(): a = 20

n address : 010FF9AC
modify_value30(): a = 30

分析:這段程序我們分別創建三個函數來更改a變量的值,三個函數分別使用普通變量,指針變量以及引用變量來當做參數。其中,modify_value10()函數在調用時,要爲形參另外開闢一個整型大小的空間,該空間首地址爲010FF8C0。
modify_value20()函數在調用時,通過間接訪問運算符*來訪問同變量a同一地址的內存空間,它與modify_value10()雖然同爲傳值調用,但是它傳的是變量的地址值,可以直接操作變量的內容,另外,它沒有額外開闢內存空間,效率比較高,但可讀性不是很好。
modify_value30()中,n就是變量a的別名,在函數中的操作其實就等價於直接操作變量a了,效率也高,可讀性也好。

總結:

在C++中, 增設引用變量其實是爲了提高程序的效率以及代碼的可閱讀性。引用能做的事情,指針都能做。但是在某些場景下,使用指針會給程序帶來不安全性。例如:指針爲NULL的話(指針的爲空性,這裏不討論const),可能會引起程序的一些異常。而這時,你用引用的話,可能能避免這種情況的發生,因爲引用在定義時就必須同時進行初始化,不具備爲空性。或許,你在定義一個函數時,只想使用傳遞給它的信息,而不對信息進行修改,同時又想使用引用,則應使用常量引用。可以這樣用,double ref_no_modify(const double &ra)。對於指針與引用在函數參數中的使用孰優孰劣,這個就見仁見智了。

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