天天看點

C++中指針與引用詳解

在計算機存儲資料時必須要知道三個基本要素:資訊存儲在何處?存儲的值為多少?存儲的值是什麼類型?是以指針是表示資訊在記憶體中存儲位址的一類特殊變量,指針和其所指向的變量就像是一個硬币的兩面。指針一直都是學習C語言的難點,在C++中又多了一個引用的概念。初學時很容易把這兩個概念弄混,下面就來通過一些例子來說明二者之間的差别。

1、指針的聲明

上文中提到,指針和其所指向的變量就像硬币的兩面,是以通過取址符号

"&"

我們可以找到變量的位址,通過解引用符号

"*"

可以找到位址記憶體放的變量值。

int data = 10;	//聲明了一個變量data,并賦初始值10,存儲的值是int類型
int* p_data = &data;	//找到 data 在記憶體中存放的位置,即p_data
cout << "位址為:" << int(p_data) << "\t 存放的值為:" << data << endl;
           

輸出結果為:

位址為:8191436  存放的值為:10
           

位址預設是16進制,我們在輸出時将其轉換成了

int

類型,是以以十進制輸出。輸出結果翻譯過來就是:在位址編碼為8191436的位置存放了值為10的變量data,再進一步地說,

data

*p_data

表示同一個東西。為了更有助于了解,我們繪制了下圖:

C++中指針與引用詳解

是以從本質上看,指針與普通的變量并沒有什麼太大的差別,隻是指針變量可以通過解引用的方式找到指針所對應的位址中存放的數值。假如定義如下:

int data = 10;
int* p_data = &data;		//定義指向 int 類型的指針 p_data, 存儲的是 int 類型的變量 data的位址,其
int** p_p_data = &p_data;	//定義指向 int* 類型的指針 p_p_data, 存儲的是 int* 類型的變量 p_data的位址

cout << "p_data:" << p_data << "\t 存放的值為:" << *p_data << endl;
cout << "p_p_data:" << p_p_data << "\t 存放的值為:" << *p_p_data << endl;
           

輸出結果為:

p_data:00EFF96C         存放的值為:10
p_p_data:00EFF960       存放的值為:00EFF96C
           

從輸出結果可以看出,

p_p_data

中存儲的值就是

p_data

,而

p_data

中存儲的值就是

data

,很像”我愛她,她愛他“的這種橋段。下面我們就重點分析一下變量與指針之間的關系:我們在上述例子中把指針初始化為變量的位址,而變量是在編譯時配置設定的有名稱的記憶體,指針隻是為可以通過名稱直接通路的記憶體提供了一個别名。還拿上面這個例子:對程式員來說,變量10的名字就是data;而對于計算機來說,變量10就是存在 8191436 位址的資料;實作程式員與計算機溝通的方式就是指針,通過對data取址讓程式員能夠明白計算機的存儲結構,同樣,通過對位址解引用,也能輕松地找到該位址中存儲的資料。在上述情況下,指針的出現顯得有些多餘,然而指針的真正用武之地在于,在運作階段配置設定未命名的記憶體以存儲值,在這種情況下,隻能通過指針來通路記憶體。

最後關于指針聲明的一點建議:在聲明一個指針變量時,必須要指定一個确定的位址,否則聲明的指針變量不知道指向哪裡,是以容易造成系統崩潰。

2、使用

new

來配置設定記憶體

記憶體四區之代碼區,全局區,棧區和堆區 - ZhiboZhao - 部落格園 (cnblogs.com) 中提到過,

new

會在堆區建立一個記憶體空間,其傳回值就是該記憶體空間的位址,是以程式員的責任就是将該位址賦給一個指針。下面是一個示例:

int* p_data = new int;	//在堆區開辟一個空間,并傳回該記憶體空間的位址
*p_data = 10;	//将向該記憶體中存儲數值10
cout << "p_data:\t" << p_data << "\t *p_data: " << *p_data << endl;
           

通過比較會發現,

new

後面指定了資料類型

int

,同樣地,

p_data

也被聲明為指向

int

的指針。這是因為,計算機的記憶體是以位元組為存儲機關,不同類型的變量會占用不同的位元組,是以使用

new

時必須要告訴編譯器配置設定多少位元組的存儲空間,并且接收的指針也必須與聲明的類型一緻。輸出結果為:

p_data: 00D0D9A0         *p_data: 10
           

當處理大型資料,比如數組時,通常會使用的一種方法是定義一個數組類型的資料,在定義的時候配置設定足夠大的空間。但是這種做法太過于死闆,但是當使用

new

時,如果在運作階段需要數組,那麼則建立它,如果不需要則不建立,最重要的是可以在程式運作時選擇數組的長度。 下面就看一下如何使用

new

來建立動态數組。在C++中,數組名被解釋為數組位址,即數組第一個元素的位址。下面是一個執行個體:

int Arr[10];	// 定義一個包含10個int類型元素的數組
cout << "Arr:" << Arr << "\t&Arr[0]:" << &Arr[0] <<endl;
           

輸出結果為:

Arr:008FFAB4    &Arr[0]:008FFAB4
           

這種聲明方式隻能在剛開始就聲明固定的數組長度,在C++中建立動态數組時,隻需要将數組的元素類型和元素數目告訴給

new

即可,

new

的傳回值同樣是數組的首位址。

int ele_num = 10;	//臨時指定數組内元素的個數
int* p_arr = new int [ele_num];	//根據臨時指定的元素個數建立數組
           

通過

new

在堆區開辟空間,由程式員管理釋放,是以當

new

的記憶體不用後,需要通過

delete

進行變量,使用

delete []

來釋放開辟的數組空間。代碼如下:

int* p_data = new int;
*p_data = 10;
cout << "p_data: " << p_data << "\t*p_data:" << *p_data << endl;

int ele_num = 10;
int* p_arr = new int [ele_num];

for(int i = 0; i<9; i++)
	*(p_arr+i) = i+2;

cout << "p_arr:" << p_arr << "\t\t*(p_array):";
for(int i = 0; i<9; i++)
	cout << *(p_arr + i) << " ";
cout << endl;

delete p_data;
delete [] p_arr;

cout << "\n******使用delete釋放記憶體後......*******" << endl;
cout << "p_data: " << p_data << "\t*p_data:" << *p_data << endl;
cout << "p_arr:" << p_arr << "\t\t*(p_array):";
for(int i = 0; i<9; i++)
cout << *(p_arr + i) << " ";
cout << endl;
           

輸出結果如下:

p_data: 0082B1C8        *p_data:10
p_arr:0082BB58          *(p_array):2 3 4 5 6 7 8 9 10

******使用delete釋放記憶體後......*******
p_data: 0082B1C8        *p_data:-572662307
p_arr:0082BB58          *(p_array):-572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -572662307 -572662307
           

3、malloc 與 new 的差別

學過C語言的朋友都知道,在C語言中通過

malloc

函數開辟一塊記憶體空間,

malloc

的函數原型如下:

void* malloc(unsigned int numbytes);
           

從函數原型的參數可以看出,

malloc

函數以位元組數為參數,開辟固定位元組的記憶體空間。這與

new

就有了第一點不同:

new

不需要自己計算位元組數,隻需要給定記憶體中存儲的資料類型與元數個數即可。

從函數原型的傳回類型可以看出,

malloc

函數傳回

void*

類型,需要我們在使用時自己指定指針類型。比如:

int* p_malloc = nullptr; // 建立一個指向int的指針
p_malloc = (int*) malloc(10);	//将 malloc 的傳回值強制轉換為 int* 類型
           

new

在使用時則不需要。總結看來,

malloc

在使用時需要自己根據記憶體中的資料類型以及記憶體長度計算處所需要的位元組數,然後傳回

void*

類型,需要使用對應類型的指針進行接收。而

new

在使用時隻需要給定記憶體的長度與記憶體中資料的類型,編譯器會自動計算所需要的位元組數。

4、引用的聲明與本質

C++中新增了引用作為已定義的變量的别名。引用的最主要用途是作為函數形參,這樣函數就可以使用原始資料而不是資料副本,這樣聽起來似乎與指針沒什麼差別,我們還是從引用的聲明說起。

int data = 10;
int& p_data = data;	//建立一個引用變量 p_data
cout << "data:" << data << "\tp_data:" << p_data << endl; //p_data 與 data 相當于一個變量的兩個名字
           

輸出結果為:

data:10 	p_data:10
           

從輸出結果來看,

p_data

data

就是一個變量的兩個不同叫法而已。引用必須在聲明時就為其指定初始值,而不能像指針一樣可以先聲明,再指派。下面将引用作為函數的參數來進一步說明引用與指針的差別:

template <typename T> //定義一個模闆函數
void swap(T a, T b){
	int temp;
	temp = a;
	a = b;
	b = temp;
}
int main(void){
	int a = 10, b = 20;	
	swap_value<int>(a,b);	//首先進行值傳遞
	cout << "a:" << a << "\t\tb:" << b << endl;
	swap_value<int&>(a,b);	//然後進行引用傳遞
	cout << "\na:" << a << "\t\tb:" << b << endl;
}
           

從上述代碼中可以看到,值傳遞和引用傳遞的形參都是一樣的,不同的是引用傳遞時,實參被聲明為引用,引用的用法與使用值一模一樣,輸出結果如下:

a:10            b:20
a:20            b:10
           

驚奇的發現,引用傳遞改變了原始資料的值,這點與指針的用法一緻,但是指針在書寫

swap

函數時應該這樣寫:

void swap(int* a, int* b){
	int temp;
	temp = *a;
	*a = *b;
	*b = temp;
}
swap(&a, &b);	//調用格式
           

綜上發現,引用其實就是變量的另一個名稱,它的用法與變量一模一樣,但是能在作為形參傳遞時,改變原始資料的值。除了這些用法上的差別,引用的本質其實就是一個指針常量,意味着指針指向的位置不可變,但是指針指向位置的值可變。即:

// 這兩者的語句是等效的,是以引用被當作指針常量來處理
int& p_a = a;  
int* const p_a=&a;
           

再補充一點小知識,關于

const

修飾符的問題,有些新手朋友來說很容易弄不清楚

const

修飾下什麼是可變的,什麼是不可變的。具體執行個體如下:

int data = 10, data2 = 20;
const int* p_data = &data;	//修飾的是int,即 p_data 所指向的值不可變,而p_data可變
p_data = &data2;

int* const p_data2 = &data;	//修飾的是int*,即 p_data 所指向的值可變,而p_data不可變
*p_data2 = data2;
           

引用即是第二種用法。