C++之指針和引用詳解

指針和引用的區別:

      1.指針是一個實體,而引用僅是個別名;

      2.引用必須被初始化,指針不必;

      3.引用只能在定義時被初始化一次,之後不可變;指針可以改變所指的對象;

      4.可以有const指針(如char * const ptr = &value),但是沒有const引用(其實引用也算是有的,引用只能在定義時被初始化一次,之後不可變。);

      5.不存在指向空值的引用,但是存在指向空值的指針,即引用不能爲空,指針可以爲空;

      6.“sizeof 引用”得到的是所指向的變量(對象)的大小,而“sizeof 指針”得到的是指針本身(所指向的變量或對象的地址)的大小;

      7.指針和引用的自增(++)運算意義不一樣; 指針自增指向下一個地址,而引用是對變量本身的值的增加。

      8.程序爲指針變量分配內存區域,而引用不需要分配內存區域;

      9.指針可以有多級,但是引用只能是一級,例如int **p是合法的,而 int &&a是不合法的; 

      10.指針和引用作爲函數參數進行傳遞時也不同。用指針傳遞參數,可以實現對實參進行改變的目的;在將引用作爲函數參數進行傳遞時,實質上傳遞的是實參本身,而不是實參的一個拷貝,因此對形參的修改其實是對實參的修改。

       總而言之指針與引用的一個重要的不同是指針可以被重新賦值以指向另一個不同的對象。但是引用則總是指向在初始化時被指定的對象,以後不能改變。

指針和引用的相同點:

      兩者都是地址的概念,指針指向一塊兒內存,其內容爲所指內存的地址;引用是某塊兒內存的別名。

 區分用指針和引用的不同情況:

使用引用的情況:

        1.常引用,用這種方式聲明的引用,不能通過引用對目標變量的值進行修改,從而使引用的目標成爲const,達到了引用的安全性,函數體不能對引用型參數修改目標變量。聲明方式:const  類型標識符  &引用名 = 目標變量名

        2.作爲函數的返回值,當類A的數據成員包含B類的對象,可以在類A中定義一個方法,返回B類的對象的引用,返回的是引用就不會有副本產生,可以直接修改類對應類B的對象。

函數定義時要按以下格式:

    類型標識符  &函數名 (形參列表及類型說明)

    {  函數體  }

引用作爲返回值,必須遵守以下規則:

  1.不能返回局部變量的引用,主要原因是局部變量會在函數返回後被銷燬,因此被返回的引用就成爲了"無所指"的引用,程序會進入未知狀態。

  2.不能返回函數內部new分配的內存的引用。雖然不存在局部變量的被動銷燬問題,可對於這種情況(返回函數內部new分配內存的引用),又面臨其它局面。例如,被函數返回的引用只是作爲一個臨時變量出現,而沒有被賦予一個實際的變量,那麼這個引用所指向的空間(由new分配)就無法釋放,造成內存泄漏。

使用指針和引用的情況對比:

        1.是考慮到存在不指向任何對象的可能(在這種情況下,能夠設置指針爲空)

        2.需要能夠在不同的時刻指向不同的對象(在這種情況下,能改變指針的指向)。如果總是指向一個對象並且一旦指向一個對象後就不會改變指向,那麼就應該使用引用。

        3.重載某個操作符時,應該使用引用。當你知道你必須指向一個對象並且不想改變其指向時,或者在重載操作符併爲防止不必要的語義誤解時,你不應該使用指針。而在除此之外的其他情況下,則應使用指針。

 

C++ 引用指針和指針引用

C++不允許定義引用的指針,因爲引用本身只是與另一個對象綁定在一起的該對象的別名,而並非一個對象,所以標準規定不能定義指向引用的指針報錯

int a = 20;
int &*ptr = &a;// error

參考<< C++ Premier 第五版>>,想要看懂聲明符ptr的具體類型是什麼,最簡單的辦法就是從右往左讀,離變量名最近的符號對其類型有最直接的影響(此處是*,表示其首先是個指針,指針的類型是一個int型引用)。
但是由於指針是個對象,所以定義一個指針的引用是可以的:

int a = 20;
int *&b = &a;// ok

 

 

數組引用

1. 數組引用既可以作爲參數傳遞,又可以當做 函數返回值。

在這先舉個例子,通過模板傳入的類型和數組大小可以變的。其他一些關注數組引用的介紹後續再補。

例1:

#include <iostream>
 
template<typename T, int N>
//auto getArrayRef(T(&arr)[N])->T(&)[N] //C++11
decltype(auto) getArrayRef(T(&arr)[N])  //C++14
{
	return arr;
}
 
int main()
{
	const int arr1[] = { 1, 2, 3 };
	auto& r1 = getArrayRef(arr1);
	auto r2 = getArrayRef(arr1);
	auto size1 = sizeof(r1);
	auto size2 = sizeof(r2);
 
	const int arr2[] = { 1,2,3,4,5,6,7,8,9,0 };
	auto& rr3 = getArrayRef(arr2);
	auto size3 = sizeof(rr3);
 
	return 0;
}

在這裏通過函數返回數組引用的時候,給r1和r3加上&引用符,給r2不加引用符,結果是不一樣的。在getArrayRef函數中sizeof(arr)的值就是數組本身的大小,而不是一個int指針的大小。關於auto的介紹可以看<<Moedern effective c++>>前三章的介紹,很詳細也很經典。

結果如下(sizeof(int)大小爲4):

 

C++中指針*與指針引用*&的區別說明

C++中*&(指針引用)與*(指針)的區別
*指針是一個存放地址的變量,指針引用指的是這個存放地址的變量的引用。
C++中如果參數不是引用的話,會調用參數對象的拷貝構造函數,
所以如果有需求想改變指針所指的對象即想要改變指針變量裏存放的地址,就要使用指針引用。
下面用一個測試例子和過程圖結合進行說明:

#include <iostream>
using namespace std;
struct Node {
	int data;
};

void ChangeNode1(Node*& pnode) {
	pnode = new Node;
	pnode->data = 5;
}

void ChangeNode2(Node *pnode) {
	pnode = new Node;
	pnode->data = 5;
}


void Test1() {
	Node *node = new Node;
	node->data = 10;
	ChangeNode1(node);
	std::cout << "指針引用" << node->data << endl;
}

void Test2() {
	Node *node = new Node;
	node->data = 10;
	ChangeNode2(node);
	std::cout << "指針" << node->data << endl;
}

int main() {
	Test1();
	Test2();
	return 0;
}

 結果如下:

分析:在Test1中,第一步先創建了一個對象,假設該對象的首地址是1231,則將地址1231存放在node指針變量中,並賦值該對象data屬性值爲10,當調用ChangeNode1(Node*& pnode)時,如圖步驟二,pnode此時可以理解爲node的別名,即pnode指針指向的就是node中的地址。pnode=new Node;表示創建一個新對象(假設新對象的首地址爲1233),則將該新對象的首地址存放到指針pnode中,也就是指針node中。如圖步驟三,node指針和pnode指針中存放的地址均爲1233了,即新對象的首地址。

 分析:在Test2中,第一步先創建了一個對象,假設該對象的首地址是1231,則將地址1231存放在node指針變量中,並賦值該對象data屬性值爲10,當調用ChangeNode1(Node* pnode)時,如圖步驟二,將node指針中存放的地址拷貝給了pnode,即pnode指針存放的也是1231。pnode=new Node;表示創建一個新對象(假設新對象的首地址爲1233),則將該新對象的首地址存放到指針pnode中。如圖步驟三,node指針存放的地址還是之前舊對象的首地址1231,pnode指針存放的是新對象的首地址1233。


 

 

指針的指針和指針的引用

當指針作爲函數的參數進行傳遞時,實際上本質上是安置傳遞,即將指針進行了一份拷貝,在函數的內部對這個指針的修改實際上就是對一個在函數內部的那個局部變量的修改。這點事和引用不同的,引用實際上是在參數傳遞時,將實際變量的地址傳了進去,在函數內部訪問這個變量時,實際上是使用間接訪問的方式來進行了的,所以實際上就是訪問了元變量。但是由於只是將地址進行了拷貝,所以對這個指針所指向地址的修改不會對原有的指針產生影響。若果要實現對指針的修改,需要使用指針的指針或者指針的應用進行傳遞。

一、指針的指針

 

int value=3;

void func(int **p)
{
    *p = &value;
}

int main(int argc, char *argv[])

{

    int n = 1;
    int *pn = &n;

    cout << *pn << endl;

    func(&pn);
    cout << *pn <<endl;

    return 0;

}

int *p 本質爲地址的地址,也就是說,p指向了一個內存空間,裏面放了一個地址。如果我們通過值傳遞,將直接傳遞給函數,那麼內部的副本不會改變p本身。類似於在函數外部是int *p,在內部是int * tmpp,兩者裏面放的內容是一樣的,也就是真是的地址,但是兩者本身的地址是不一樣的,對tmpp的修改不會作用到p上。 使用指針的指針,可以做到這點。int **p;在函數內部首先解引用,實際上就得到了p的真是地址,從而可以對p本身進行修改。

 

二、指針的引用

int value=3;

void func(int *&p)

{

    p = &value;

}

 

int main(int argc, char *argv[])

{

    int n = 2;

    int *pn = &n;

    cout << *pn << endl;

    func(pn);

    cout << *pn <<endl;

    return 0;

}

看一下int *&p 實際上,本質p是一個引用,對一個指針的引用,所以對p的修改實際上就是對指針的修改。這裏在從新認識指針 int *p,本質爲地址的地址,也就是說p指針中放的是一個地址,本身p也有一個地址。所以當int *& p初始化之後那麼p指向的地址就不會改變,也就是存放地址的內存空間。但是這裏面放的內容可以改變,這就是引用的特點,在這裏實際上就是裏面放的地址可以改變。

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