本文將討論如下的內容:
1.指標的概念及其本質
2.指標的用法
3.容易混淆的概念
4.善用指標
1.指標的本質
在資訊工程中指標是一個用來指示一個記憶體位址的電腦語言的變數或中央處理器(CPU)中寄存器.指標一般出現在比較近機器語言的語言,如組合語言或C語言。純物件導向的語言如Java一般避免用指標。指標一般指向一個變數或者函數。在使用一個指標時,一個程式既可以直接使用這個指標所儲存的記憶體位址,又可以使用這個地址裡儲存的變數或函數的值。
c語言之所以強大,以及其自由性,很大部分體現在其靈活的指標運用上。因此,說指標是c語言的靈魂,一點都不為過。C++作為從C語言發展而來的一門進階語言,他並沒有摒棄指標,而是積極的採納他.同時又注入了物件導向的進階思想,這使得C++成為了全世界迄今為止最為優秀的語言.
C++中的指標一直被初學者視為很神秘的東西.其實指標的概念很簡單.我們可以把他看作一種資料類型,定義一個指標變數就像定義一個int,char型一樣的變數.只不過我們往int裡面存整數,往char型裡面存字元,而往指標裡面呢,存放的是地址,記憶體位址.我們只要記住一點,在進階語言裡
2.指標的用法
既然我們說指標是一個變數類型,那麼在繼續深入瞭解指標之前我們先來瞭解一下什麼叫做變數類型.
我們知道,所有的資料最終都是放到記憶體中才能被使用,也即是說,所有的資料,不管你是如何定義的,最終都將反映為一段記憶體空間.那麼我們聲明為某種變數類型之後有什麼用呢?其實有兩個用途.一個是限定記憶體佔用大小.一般來說程式設計語言總要為資料設定不同的記憶體佔用空間以滿足不同應用的需求.尤其是在記憶體吃緊的嵌入式等應用,經常可是要用到bit型的,如果都用4個位元組那是省事了,但是帶來的空間消耗可是很大的.類型定義的第二個用途是約束操作.例如我int型的資料可以相乘,但是char型不可以不同的資料類型應該具有不同的操作.所以我們用類型定義來實現這種約束.
所以我們可以看出,指標無非存放的東西有點不同,其餘的與其他資料類型沒有什麼不一樣.指標變數本身佔用了4個位元組的空間來存放記憶體位址,即可以定址到2的32次方即4G的記憶體位址空間.他有兩個基本操作符,一個是*號(取地址中存放的值),一個是&(取變數地址).
我們先來看下面這段簡單的代碼
int aint = 3;
int* pint;
pint = &aint;
當定義了一個int型變數之後,C++會該變數分配一塊記憶體空間,然後把3轉換成4個位元組初始化這片記憶體空間.在這裡,我們要知道,其實變數名就相當於某個寄存器的別名,也就是地址空間的名字.我們使用這個變數名.就像使用地址空間上面存放的值一樣.如果我們要得到aint這個變數的地址,我們可以用&aint來獲得.程式下面又定義了一個指標變數pint,他是用來存放aint變數的記憶體位址的.那麼我們如何使用指標呢?一般來說我們不用到變數的地址,我們更關心的變數的值,即地址中所存的值.我們用*號操作符來擷取aint的值.然後輸出.
指標和指向的變數之間的關係如所示.
那麼既然指標是一類資料類型,為什麼我們不用一種統一的資料類型的名字,例如pointer來定義一個指標變數呢?這是由於指標存放東西的特殊性造成的,指標存放的是記憶體位址,需要的時候我們可以取出記憶體位址中的資料來.問題是,我們如何去取這塊記憶體中的資料呢??取兩個位元組?取四個位元組?還有,取出來的資料他有什麼操作呢??他可以相乘嗎?我們要知道機器是很笨的,他什麼都不知道,他只知道我們給他設定的規則.
還記得我們上面說過的資料類型的兩大用途嗎?用”基礎資料型別 (Elementary Data Type) *”這樣一種方式來定義一個指標變數的意義正在於此.機器將按照我們設定的類型(int*)取出aint地址上的四個位元組的資料,然後將資料格式化成int型資料,這樣資料的操作就受到約束了.C++中還甚至允許我們定義指標類型為void*型,表明我們暫時只需要指標的首地址,不需要立即對其資料進行格式化.
*函數指標*
指標作為存放地址的變數,那麼他自然可以存放任何東西的地址,例如函數.這就是函數指標了.函數指標的聲明格式如下:
函數傳回值類型(*指標變數名)(參數類型列表) = &函數
或者是
typedef 函數傳回值類型 (*函數類型名)(參數類型列表)
函數類型名 函數指標 = &函數;
我們來看一個典型的應用
int getmax(int a,int b){return a > b ? a : b;}typedef int (*Func)(int,int);int main(){Func pFuncFormat1 = &getmax;int (*pFuncFormat2)(int,int) = &getmax;cout<<(*pFuncFormat1)(3,5);cout<<(*pFuncFormat2)(3,5);return 0;}
上面這段代碼首先定義了一個普通的比較大小的函數,然後在主函數中以兩種風格定義了兩個同樣的函數指標,指向該函數.這樣我們就在這個函數指標中存放了GetMax函數的入口地址了,注意這裡跟普通的變數有些不一樣.對GetMax函數用不用&號都是可以的,在使用時用不用*號也是可以的,效果是一樣的.但是習慣上我們會採用上面這種風格來寫程式碼比較好懂一點.還有要注意的是,函數指標一定要與函式宣告的簽名(也就是參數表)一致,還有傳回值也要一致.
3.容易混淆的概念
**指標數組和數組指標的區別
這個也是C++裡面比較容易混淆的一個定義.
你能看出下面的定義有什麼區別嗎?
1.int *PointerArray[3];
2.int (*ArrayPointer)[3];
1中定義的是一個指標數組.首先讓我們先來瞭解一下什麼叫做數組.數組就是同一類型元素的彙總.例如int array[3];定義了3個int型元素,並將其彙總在array這個資料結構中.數組本質上也是指標.即array變數本身存放的是上面這個資料結構的首地址.當我們用[]索引符號去訪問數組對象時,其實就相當於調用指標操作符*取地址上的資料(注意,指標也可以應用[]索引符,或者數組"名"也可以應用*操作符,這也再一次說明了數組和指標具有一定的關係).那數組和指標有什麼不同呢?我們說數組是一種常指標(關於常指標和指標常量我們待會再解釋).瞭解到這一點之後我們再來看下指標數組是什麼東西就很好理解了.指標數組說白了就是存放指標變數的數組.我們可以把int* 看作一種新類型,和int,char等基本類型等價的一種類型,然後再去看上面這個例子就很清楚了.
字元型的指標數組一般用於字串操作較多.例如我們可以這樣定義
char *PointerArray[3] = {"Hello","World","!"}
三個指標分別指向了三個字串,長度不相等.但要注意,這裡定義之後每個指標就變成了指標常量了.即通過該指標對這些字串進行修改是非法的.一般的用法是
PointerArray[i] = new char[N]; (N為常數)
然後再進行賦值操作.
再來解釋數組指標.數組指標其實就是指向數組的指標,定義中的下標號3表示的是該指標所指向的一維數組的長度(注意,任意維數組都可以看成是由一維數組構成的).我們來看下面的例子
int array[3][5];
int (*ArrayPointer)[5];
ArrayPointer = array;
然後我們就可以像使用array數組一樣使用ArrayPointer了.如果記不住的話我們可以將上面的定義分開來看.把他看成(*ArrayPointer)和int [5]兩部分,如所示:
(數組指標) (指標數組)
注意,是(*ArrayPointer)才相當於int [5],而不是ArrayPointer,所以我們可以用(*ArrayPointer)[3]來訪問array[0][3].或者用(*ArrayPointer+1)[3]來訪問array[1][3].當然也可以像二維數組那樣直接用兩個索引號來訪問.
數組指標一般用於動態分配二維數組,如下
char (*CharArray)[5] = new char[N][5]; (N為常數)
注意,二維數組動態分配時必須指定他的一維長度.
**常指標和指標常量的區別
常指標就像我們平時說常整數一樣,意思是指標的內容不能再被修改了.而我們知道指標記憶體放的是變數的地址,也就是說一旦將某變數的地址賦值給了該指標,那麼以後就不能將其指向其他的變數了.他的定義如下:
type * const PointerName = &Variable;
對於常量必須在聲明時即初始化,除非是類的資料成員,那麼可以在建構函式的時候才進行初始化,如下面
class Test{const int c;public:Test(int x):c(x){}};
指標常量比較難從字面上理解其意思.我們只好記住他了,他表示的是指標所指向的內容是不能通過指標來修改的(雖然通過一些小伎倆是可以進行"不合法"修改的).
他的定義方式有兩種
type const * PointerName = &Variable;const type * PointerName = &Variable;
下面的例子中試圖對指標常量進行修改將編譯失敗
char Variable = '3';char const * PointerName;PointerName = &Variable;//*PointerName = '4'; //編譯不通過
與常指標不同的是指標常量允許聲明和賦值分開.
**指標與引用的區別
引用就像是變數的別名一樣.他的定義如下:
int Variable;int &Ref = Variable;
我們可以看到,引用使用指標裡的取地址符作為標識,他表示引用與變數使用的是同一塊記憶體的資料.引用和指標有幾個不同的地方,一個就是引用必須聲明即賦初值,而且不能再引用其他的變數了.而指標是可以隨時指向其他變數的(常指標除外).既然引用就像別名一樣,那麼為什麼我們還需要這個東西呢?引用一般是用於傳遞參數的比較多,這樣可以節省記憶體和拷貝建構函式等的開銷.
4.善用指標
指標是操作底層必備的東西,是C++裡面最為靈活的東西.但也帶來安全性的問題.不正確的使用指標將會導致記憶體泄露,記憶體懸掛,野指標(不安全指向)等問題的出現,嚴重威脅到軟體系統的穩定性和安全性.現在有很多人在致力於編寫智能指標的庫,使用智能指標,我們將可以在很大程度上避免上述的問題.如boost庫中就設計了智能指標,具體請參考相關的內容.