- C++語言程序設計
- 千鋒教育高教產品研發部編著
- 3979字
- 2019-07-30 17:37:44
2.4 指針
2.4.1 內存和地址
前面提到過,變量其實就是用來放置數值等內容的“盒子”,每個盒子都可以容納數據,并通過一個編號來標識。盒子也有自己的地址,計算機要找到某個盒子,必須知道該盒子的地址。
計算機的內存是以字節為單位的一段連續的存儲空間,每個字節單元都有一個唯一的編號,這個編號就稱為內存的地址。接下來用一張圖來展示機器中的一些內存位置,如圖2.14所示。

圖2.14 機器中的內存位置
圖2.14中每個盒子的存儲類型為字節,每個字節都包含了存儲一個字符所需要的位數。在許多現代的機器上,每個字節包含8個位,可以存儲無符號值0~255,或有符號值-128~127。圖2.14中并沒有顯示這些位置的內容,但內存中的每個位置總是包含一些值。每個字節通過地址來標識,如圖2.14中方框上面的數字所示。
為了存儲更大的值,可以把兩個或更多個字節合在一起作為一個更大的內存單位。例如,許多機器以字為單位存儲整數,每個字一般由2個或4個字節組成。接下來用一張圖來表示4個字節的內存位置,如圖2.15所示。

圖2.15 機器中4字節的內存位置
舉一個例子,這次盒子里顯示了內存中4個整數的內容,如圖2.16所示。

圖2.16 內存中存放了4個整數
這里顯示了4個整數,每個整數都位于對應的盒子中。如果大家記住了一個值的存儲地址,那么以后可以根據這個地址取得這個值。
但是,要記住所有這些地址實在太煩瑣了,因此高級語言所提供的特性之一就是通過名字而不是地址來訪問內存的位置。接下來使用名字來代替地址,如圖2.17所示。

圖2.17 用變量來存儲整數
當然,這些名字就是變量。有一點非常重要,必須記住,名字與內存位置之間的關聯并不是硬件提供的,它是由編譯器為人們實現的。所有這些變量正是為了人們而提供的一種更方便用來記住地址的方法——硬件仍然通過地址訪問內存位置。在C++語言中可以通過取地址運算符&來獲取系統將某種數據存放在內存中的位置。
2.4.2 指針的定義與使用
在旅店住宿時,前臺服務員通常會提供給客戶一個房間號,然后客戶通過房間號就可以很方便地找到自己預定的房間。在C++中,指針就起到房間號的作用,它指向另一個內存地址,這個地址對應的內存空間就是編程者真正需要操作的數據。
1. 指針的定義與初始化
指針的定義是使用一個特殊的符號?來區別的,其語法格式如下:
數據類型 *指針變量名;
具體示例如下:

上面示例中,p、p1、p2、str都是指針類型的變量,int、float、char是指針所指向內存空間中的數據類型,它決定了指針所指向的內存空間的大小,int?、float?、char?是指針數據類型,用來定義p、p1、p2、str這樣的指針變量。指針的值是另外一個變量的內存地址,如定義一個整型變量,將它的地址賦值給一個整型指針變量,具體示例如下:
int a= 1; int *p=&a;
上述語句中,定義指針變量p時,初始化為&a,此時p的值就是a的地址,一般稱為指針變量p指向變量a。由于指針變量的值是某個內存空間的首地址,而地址的長度都是一樣的,因此指針變量所占的內存空間大小都是相同的。
2. 指針的賦值
由于指針也是一個變量,因此它的值是可以改變的,需要注意的是,在給指針賦值時,指針變量的值必須是與其類型相對應的內存地址,具體示例如下:

引入指針的目的是操作指針指向的內存空間,完成指針定義及初始化后,在指針變量前加?,表示對指針所指的內存空間的引用,這時就可以通過指針間接地操作這塊內存空間,需要注意此處?與定義時的?之間的區別。接下來演示指針的用法,如例2-7所示。
例2-7

運行結果如圖2.18所示。

圖2.18 例2-7運行結果
在例2-7中,定義了3個變量,其中a、b是整型變量,p是指向整型的指針。從運行結果可以看出,3個變量的內存地址是連在一起的,首先p的值是a的地址,?p的值是a的值;接著使p指向b,p的值是b的地址,?p就是b的值;最后給?p賦值為4,b的值變為4。由此可見,對?p操作,與對p指向的變量b做操作是一樣的。
3. void指針
有一種特殊類型的指針,這種指針指向的數據類型是void。在C++中,void是空的意思,但void指針的含義是不確定類型的指針,簡單理解為通用類型指針,即它可以指向任何數據類型的內存空間,具體示例如下:
void *p;
上述語句僅表示p是一個指針,可以存放一個內存地址,但是這個內存地址所指的空間中存放的數據類型還不能確定,當需要使用這個指針時,可以通過對指針進行強制類型轉換的方法確定其具體表示的類型。接下來演示void指針的使用,如例2-8所示。
例2-8

運行結果如圖2.19所示。

圖2.19 例2-8運行結果
在例2-8中,第7行通過強制類型轉換將void?類型指針轉換為int?型,再通過?p輸出指針指向的內存空間的值。同理,第8行通過強制類型轉換將void?類型指針轉換為char?型,再通過?p輸出指針指向的內存空間的值。
void指針經常用在能支持多種數據類型的數據操作函數中,讀者在以后深入學習C++時需要理解這種指針的用法。
4. NULL指針值
當一個指針定義時沒有為其初始化,且使用前也沒有為其賦值,那么這個指針的值是隨機的,它可以指向一個隨機的內存,通常把這種指針稱為野指針。在程序中如果不小心使用了野指針,很容易造成程序混亂。為避免這種情況發生,當一個指針不指向任何內存地址時,必須用NULL作為指針的值。在使用指針前,有時也需要判斷一下指針是否為NULL,如果不是NULL,則該指針指向一個內存空間;如果是NULL,則該指針沒有具體指向。具體示例如下:

2.4.3 指針與數組
一維數組名本質上說是一個地址常量,這個地址是一維數組中第一個元素的內存地址,因此可以將一維數組名賦值給一個指針變量,當指針指向一個一維數組時,指針也可以當成數組名使用。具體示例如下:

有了上面的定義,a和p就可以互換,如果要訪問數組的第一個元素,a[0]、?a、p[0]、?p這4種方式都是正確的,p可以看成數組的名字a,訪問數組的第i+1個元素,可以用a[i],也可以用p[i]。但p與a是有本質的區別的:a是地址常量,不能被賦予其他地址值;而p是變量,可以被重新賦予地址值。
C++中使用行指針來訪問二維數組,設有如下定義:
int a[3][4];
在形式上,可以將a[3]看成一個一維數組,在定義指針變量時可以將a[3]用?p來替換,具體示例如下:
int(*p)[4];
由于二維數組中定義的最高維(a[3])用來確定二維數組的行,因此把p稱為行指針。接下來p的初值就可以用a來設定,具體示例如下:
p=a;
這樣就可以通過p來訪問數組a中的元素了,如訪問a[2][3],就可以通過p[2][3]、?(p[i]+j)、?(?(p+i)+j)這3種方式訪問。
使用行指針也可以訪問多維數組的元素,讀者可以根據二維數組的方式類似推出多維數組,此處不再贅述。
2.4.4 指針運算
指針的運算主要是指指針的移動,即通過指針遞增、遞減、加上或者減去某個整數值來移動指針指向的內存位置。此外,兩個指針在有意義的情況下,還可以做關系運算,如比較運算。
指針變量的值實際上是內存中的地址,因此,一個指針加減整數相當于對內存地址進行加減,其結果依然是一個指針。然而,盡管內存地址是以字節為單位增長的,指針加減整數的單位卻不是字節,而是指針指向數據類型的大小。具體示例如下:
int a=0; int *p=&a; p++;
第3行將指針變量p存儲的內存地址自加,由于p指向的是int型變量,因此執行自加操作會將原來的內存地址增加4個字節(此處是int型占用4個字節的系統)。接下來演示指針的加減運算,如例2-9所示。
例2-9

運行結果如圖2.20所示。

圖2.20 例2-9運行結果
在例2-9中,指針p中存儲的是變量a的地址0X0018FF44,然后讓p自減,此時p的值為0X0018FF40,從0X0018FF44到0X0018FF40需要移動4個字節,而這正好是int型變量所占用的內存字節數。同理,指針q中存儲的是變量b的地址0X0018FF38,然后讓q自加2,此時q的值為0X0018FF48,從0X0018FF38到0X0018FF48需要移動16個字節,而這正好是兩個double型變量所占用的內存字節數。
2.4.5 動態內存管理
在C語言中,用于管理動態內存的方法主要是malloc()和free()函數,使用時需要注意很多細節,一不小心就會造成內存泄漏。在C++中,更常用的動態內存管理是new和delete操作符,它們一般配套使用,new表示從堆內存中申請一塊空間,delete表示將申請的空間釋放。
1. new申請內存
用new運算符申請堆內存空間有3種格式,其語法格式如下:
new數據類型; new數據類型(初始值); new數據類型[常量表達式];
第一種格式是申請一個指定數據類型的內存空間,可以將該操作結果賦值給一個相應數據類型的指針變量,具體示例如下:

第二種格式是在申請空間的同時,為空間賦初值,具體示例如下:

第三種格式是動態申請數組空間,中括號內的常量表達式代表申請空間的大小,這個空間大小是以數組類型大小為單位,具體示例如下:
int *p3=new int[10]; //申請一個可以存放10個整型數的數組,然后賦值給整型指針變量
因為系統資源是有限的,所以不是在任何情況下都能申請到足夠的空間,如果申請失敗,將返回NULL指針。由于內存操作失敗是非常危險的,因此在申請內存時,一般需要在程序中判斷是否申請成功,如果成功就繼續執行程序;如果失敗,立即拋出異常或直接結束程序。
2. delete釋放內存
運算符delete用來釋放new申請的內存,其語法格式如下:
delete指針名; delete[]指針名;
當new使用前兩種形式時,即申請的是一個數據類型空間時,對應的delete為第一種格式,即只釋放指針所指的空間,具體示例如下:

當new使用第三種形式時,即申請的是動態數組,對應的delete為第二種格式,即釋放數組的全部空間,具體示例如下:
int *p3=new int[10]; delete[]p3;
程序中一旦執行delete后,指針指向的可能是原來的值,也可能是其他的值,這取決于編譯器對其處理的結果,因此出于對程序健壯性的考慮,一定要在使用delete后,將指針置為NULL。
接下來演示new與delete的用法,如例2-10所示。
例2-10

運行結果如圖2.21所示。
在例2-10中,第3~7行定義了一個結構體Student,第14行通過new運算符申請內存,第15~19行申請內存失敗返回-1,第32行釋放申請的內存。
此外,使用new與delete時,還需注意以下幾點:

圖2.21 例2-10運行結果
- new與delete必須配對使用,即用new為指針分配內存,使用完后,一定要用delete來釋放已分配的內存空間。
- NULL指針是不能釋放的,即用new申請內存失敗時,就不能再釋放了。
- 用new給指針變量分配一個有效指針后,必須用delete先釋放,然后再用new重新分配或改變指向,否則先前分配的內存空間因無法被程序引用而造成內存泄漏。
- 現代C++編程:從入門到實踐
- Java語言程序設計
- AngularJS Testing Cookbook
- 從零開始:數字圖像處理的編程基礎與應用
- 零基礎學Scratch少兒編程:小學課本中的Scratch創意編程
- Machine Learning with R Cookbook(Second Edition)
- 數據結構習題精解(C語言實現+微課視頻)
- Java程序員面試算法寶典
- 網絡爬蟲原理與實踐:基于C#語言
- Python貝葉斯分析(第2版)
- Android Native Development Kit Cookbook
- 編程與類型系統
- 深度學習:Java語言實現
- R語言:邁向大數據之路(加強版)
- Mastering OAuth 2.0