數(shù)組指針與指針數(shù)組
數(shù)組指針:誰在后面就是定義為誰,即數(shù)組指針為指針
指針數(shù)組:先是數(shù)組;
在C語言中,我們將第 0 個元素的地址稱為數(shù)組的首地址
arr 本身就是一個指針,可以直接賦值給指針變量 p。arr 是數(shù)組第 0 個元素的地址,所以int *p = arr;
也可以寫作int *p = &arr[0];
。也就是說,arr、p、&arr[0] 這三種寫法都是等價的,它們都指向數(shù)組第 0 個元素,或者說指向數(shù)組的開頭.
如果一個指針指向了數(shù)組,我們就稱它為數(shù)組指針(Array Pointer)。
引入數(shù)組指針后,我們就有兩種方案來訪問數(shù)組元素了,一種是使用下標(biāo),另外一種是使用指針。
1) 使用下標(biāo)
也就是采用 arr[i] 的形式訪問數(shù)組元素。如果 p 是指向數(shù)組 arr 的指針,那么也可以使用 p[i] 來訪問數(shù)組元素,它等價于 arr[i]。
2) 使用指針
也就是使用 *(p+i) 的形式訪問數(shù)組元素。另外數(shù)組名本身也是指針,也可以使用 *(arr+i) 來訪問數(shù)組元素,它等價于 *(p+i)。
指針可變,數(shù)組名不可變
指針函數(shù):
一個函數(shù)總是占用一段連續(xù)的內(nèi)存區(qū)域,函數(shù)名在表達式中有時也會被轉(zhuǎn)換為該函數(shù)所在內(nèi)存區(qū)域的首地址,這和數(shù)組名非常類似。我們可以把函數(shù)的這個首地址(或稱入口地址)賦予一個指針變量,使指針變量指向函數(shù)所在的內(nèi)存區(qū)域,然后通過指針變量就可以找到并調(diào)用該函數(shù)。這種指針就是函數(shù)指針。
函數(shù)指針的定義形式為:
returnType (*pointerName)(param list);
returnType 為函數(shù)返回值類型,pointerName 為指針名稱,param list 為函數(shù)參數(shù)列表。參數(shù)列表中可以同時給出參數(shù)的類型和名稱,也可以只給出參數(shù)的類型,省略參數(shù)的名稱,這一點和函數(shù)原型非常類似。
注意( )
的優(yōu)先級高于*
,第一個括號不能省略,如果寫作returnType *pointerName(param list);
就成了函數(shù)原型,它表明函數(shù)的返回值類型為returnType *
。
運行結(jié)果:
Input two numbers:10 50↙
Max value: 50
pmax 是一個函數(shù)指針,在前面加 * 就表示對它指向的函數(shù)進行調(diào)用。注意( )
的優(yōu)先級高于*
,第一個括號不能省略。
指針作為參數(shù):
在C語言中,函數(shù)的參數(shù)不僅可以是整數(shù)、小數(shù)、字符等具體的數(shù)據(jù),還可以是指向它們的指針。用指針變量作函數(shù)參數(shù)可以將函數(shù)外部的地址傳遞到函數(shù)內(nèi)部,使得在函數(shù)內(nèi)部可以操作函數(shù)外部的數(shù)據(jù),并且這些數(shù)據(jù)不會隨著函數(shù)的結(jié)束而被銷毀。
像數(shù)組、字符串、動態(tài)分配的內(nèi)存等都是一系列數(shù)據(jù)的集合,沒有辦法通過一個參數(shù)全部傳入函數(shù)內(nèi)部,只能傳遞它們的指針,在函數(shù)內(nèi)部通過指針來影響這些數(shù)據(jù)集合。
有的時候,對于整數(shù)、小數(shù)、字符等基本類型數(shù)據(jù)的操作也必須要借助指針,一個典型的例子就是交換兩個變量的值。
從結(jié)果可以看出,a、b 的值并沒有發(fā)生改變,交換失敗。這是因為 swap() 函數(shù)內(nèi)部的 a、b 和 main() 函數(shù)內(nèi)部的 a、b 是不同的變量,占用不同的內(nèi)存,它們除了名字一樣,沒有其他任何關(guān)系,swap() 交換的是它內(nèi)部 a、b 的值,不會影響它外部(main() 內(nèi)部) a、b 的值。
運行結(jié)果:
a = 99, b = 66
調(diào)用 swap() 函數(shù)時,將變量 a、b 的地址分別賦值給 p1、p2,這樣 *p1、*p2 代表的就是變量 a、b 本身,交換 *p1、*p2 的值也就是交換 a、b 的值。函數(shù)運行結(jié)束后雖然會將 p1、p2 銷毀,但它對外部 a、b 造成的影響是“持久化”的,不會隨著函數(shù)的結(jié)束而“恢復(fù)原樣”。
需要注意的是臨時變量 temp,它的作用特別重要,因為執(zhí)行*p1 = *p2;
語句后 a 的值會被 b 的值覆蓋,如果不先將 a 的值保存起來以后就找不到了。
這就好比拿來一瓶可樂和一瓶雪碧,要想把可樂倒進雪碧瓶、把雪碧倒進可樂瓶里面,就必須先找一個杯子,將兩者之一先倒進杯子里面,再從杯子倒進瓶子里面。這里的杯子,就是一個“臨時變量”,雖然只是倒倒手,但是也不可或缺。
參數(shù)的傳遞本質(zhì)上是一次賦值的過程,賦值就是對內(nèi)存進行拷貝。所謂內(nèi)存拷貝,是指將一塊內(nèi)存上的數(shù)據(jù)復(fù)制到另一塊內(nèi)存上。
C語言允許函數(shù)的返回值是一個指針(地址),我們將這樣的函數(shù)稱為指針函數(shù)。下面的例子定義了一個函數(shù) strlong(),用來返回兩個字符串中較長的一個:
指針的總結(jié):
指針(Pointer)就是內(nèi)存的地址,C語言允許用一個變量來存放指針,這種變量稱為指針變量。指針變量可以存放基本類型數(shù)據(jù)的地址,也可以存放數(shù)組、函數(shù)以及其他指針變量的地址。
程序在運行過程中需要的是數(shù)據(jù)和指令的地址,變量名、函數(shù)名、字符串名和數(shù)組名在本質(zhì)上是一樣的,它們都是地址的助記符:在編寫代碼的過程中,我們認(rèn)為變量名表示的是數(shù)據(jù)本身,而函數(shù)名、字符串名和數(shù)組名表示的是代碼塊或數(shù)據(jù)塊的首地址;程序被編譯和鏈接后,這些名字都會消失,取而代之的是它們對應(yīng)的地址。

1) 指針變量可以進行加減運算,例如p++
、p+i
、p-=i
。指針變量的加減運算并不是簡單的加上或減去一個整數(shù),而是跟指針指向的數(shù)據(jù)類型有關(guān)。
2) 給指針變量賦值時,要將一份數(shù)據(jù)的地址賦給它,不能直接賦給一個整數(shù),例如int *p = 1000;
是沒有意義的,使用過程中一般會導(dǎo)致程序崩潰。
3) 使用指針變量之前一定要初始化,否則就不能確定指針指向哪里,如果它指向的內(nèi)存沒有使用權(quán)限,程序就崩潰了。對于暫時沒有指向的指針,建議賦值NULL
。
4) 兩個指針變量可以相減。如果兩個指針變量指向同一個數(shù)組中的某個元素,那么相減的結(jié)果就是兩個指針之間相差的元素個數(shù)。
5) 數(shù)組也是有類型的,數(shù)組名的本意是表示一組類型相同的數(shù)據(jù)。在定義數(shù)組時,或者和 sizeof、& 運算符一起使用時數(shù)組名才表示整個數(shù)組,表達式中的數(shù)組名會被轉(zhuǎn)換為一個指向數(shù)組的指針。
