C語言指標基本知識
對程式進行編譯的時候,系統會把變數分配在記憶體單位中,根據不同的變數型別,分配不同的位元組大小。比如int整型變數分配4個位元組,char字元型變數分配1個位元組等等。被分配在記憶體的變數,可以通過地址去找到,記憶體區每一個位元組都有一個編號,地址也可以形象的理解成我們生活中的住址,通過住址找到每一個人所在的地方。指標作為一個變數用來存放地址,可以通過指標來改動變數。
上圖就是一個簡單的定義一個一級指標變數和利用指標改變變數數值的過程。int*表示整型指標,*p表示解引用操作,就是利用指標找到a的地址然後再改變a的值。
地址用%p列印,用十六進位制表示,在列印時候輸入指標變數p和取地址a得出的結果是相同的,證明了指標是用來存放地址的。
指標作為一個變數是有大小的,其大小在32位平臺是4個位元組,64位平臺上是8個位元組,大小與指標的型別無關。
上圖以32位平臺舉例子,可以看到無論指標是整型、字元型、浮點型也無論一級指標還是二級指標,其在記憶體空間所佔的大小都是4個位元組。
指標有多種類別,按照級數來分便可以分為一級指標,二級指標,三級指標等等
一級指標是最基礎的指標,指向的是建立的變數的地址。就類似於上圖的前三個sizeof後面所寫的。前文講到指標也是一個變數,是用來存放地址的。既然是一個變數,就也要在記憶體開闢空間,開闢了空間就也會產生屬於指標變數自己的地址。二級指標便是用來存放一級指標地址的。以此類推多級指標也是如此。
指標也可以根據指標指向的變數的資料型別來進行分類,有整型指標,字元指標,陣列指標,函式指標等等
整型指標和字元指標
這兩個是比較常見和容易理解的指標,依次用int*和char*表示,他們的區別在於指向變數型別不同,記憶體也不一樣,在進行解引用操作時訪問的位元組大小也因為變數型別的區別會有所差異。整型指標可以訪問4個位元組,而字元指標只能訪問1個位元組。也就是說對整型指標變數解引用,一次可以操作一個整型,而對字元變數解引用一次只能操作一個字元。
較為特殊的char*p="hello"這並不是將整個字串的地址傳個了p,而是傳了字元穿首元素‘h'的地址,可以通過’h‘的地址來找到整個字串。此時出現char*p2=“hello”,p2和p代表的是同一處地址,因為hello是常量字串,沒有必要開闢兩塊不同的空間的來儲存它。這是字元指標的一個特性。
void型指標
void型的指標可以接受任何型別的地址,但是不能對void型指標進行解引用操作。解引用操作要有特定的訪問位元組的數量,比如對整型指標解引用就是訪問4個位元組,字元型指標解引用就是訪問1個位元組,而void型指標無法確定訪問位元組個數,所以不能進行解引用操作。同時void*這種型別的指標也不能進行加減整數的操作,因為無法確定跳過的位元組個數。
此圖表示了void型指標可以接受任意型別的地址。
陣列指標
這是一種指向陣列的指標,例如int(*p)[10]這就是一個指向陣列的指標,它指向的陣列有10個元素,每個元素都是整型。給*p加上括號是因為p和[10]優先結合,這樣的話就變成了一個數組而不是指標了。這個陣列叫指標陣列,int*p[10]這樣的寫法意思是一個有10個元素的陣列,每一個元素都是整型指標,這和陣列指標是兩個不同的東西。
指向陣列的指標裡面存放的便是陣列的地址,而非陣列某個元素的地址,所以在定義陣列指標時要用 &+陣列名,而不是簡單使用 陣列名。
上圖顯示出&arr和arr的不同,雖然起始地址相同,但arr+1只讓指標向後移動了一個元素的空間,而&arr+1讓指標移動了一個數組的空間。
函式指標
函式指標顧名思義就是指向函式的指標,每個函式都有一個入口,這個入口的地址便是函式指標所指向的地址。函式地址的表示方法為 函式名或 &+函式名。例如一個函式叫Add,&Add和Add都是表示這個函式的地址沒有什麼差別。函式指標的寫法是 函式的返回型別(*)(函式的引數),例如函式Add,其函式指標的寫法就是int(*p)(int,int)=Add 。*p要加上括號來保證*和p的優先結合來形成一個指標變數,如果不加括號來優先結合,則會出現int* p(int,int)這樣的寫法,這就變成了函式的宣告,這個函式的返回型別是int*,函式的名字叫p,函式的引數是2個整型和原先的函式指標不是同一個意思。
用函式指標呼叫函式時可以不加*這個解引用符號,因為這個符號將不會在程式執行的時候起到作用。
上圖顯示了*這個解引用符號在函式指標呼叫函式時候不起作用,以上的寫法都可以用。
根據函式指標的相關知識,可以來看這兩段程式碼。
程式碼1中間的 void(*)()是一個函式指標型別,將這個函式指標型別放在括號中,是強制型別轉換的意思也就是把0強制轉換成一個函式指標,強制型別轉換這個部分簡單寫出來就是“(函式指標)0”是將0作為一個函式的地址,而最外層的括號(*函式的地址)()這個是解引用操作,也就是通過0這個地址,找到了0地址處所在的函式,並且進行呼叫。
程式碼2 內部的(int,void(*)(int))這一段表示的函式的引數,第一個引數是一個整型,第二個引數是一個函式指標型別,這個函式指標指向的函式的返回型別是void,引數型別是int。而這個函式的名字就是signal。解決了這個部分的內容,剩下的就是void(*)(int),去除裡面的signal函式可以很明顯地看出來這是一個函式指標。一個函式由三部分組成,返回型別,函式名,函式的引數。也就是說引數和函式名去掉之後,函式宣告中就只剩下一個返回型別。此時,函式名和引數已經在前一步分析中得出,剩下的void(*)(int)便就是函式的返回型別,這個函式返回型別是也是一個函式指標。
這兩個程式碼來自於書本《C陷阱和缺陷》。
函式指標和陣列的結合例項,簡易的計算器,這是函式指標陣列的應用
陣列傳參
陣列在傳參的時候傳的是首元素的地址,陣列名錶示首元素的地址。函式的形參可以用陣列形式表示也可以用指標形式表示。
一維陣列的傳參比較簡單,例如int arr[3]這個陣列,形參可以直接使用int arr[]或者int arr[3]用陣列形式表示形參,形參處的元素個數可以寫也可以不寫,因為元素個數在這裡不起作用。或者用一級指標表示,int* arr這樣就反映了指標傳參傳的是首元素地址。
二維陣列傳參相對比較複雜,由陣列的知識可以知道,二維陣列必須有規定的列數,所以要以陣列形式傳參的時候列數不能省略。
以指標形式傳參,陣列名仍然是首元素地址的意思,作為一個二維陣列,首元素便是第一行的陣列。比如int arr[3][5]這個二維陣列的首元素是一個含有5個整型元素的陣列,所以在傳參的時候傳的指標也應該是指向這個陣列的指標。所以此時形參應該表示為int (*arr)[5],這表示一個數組指標,指向一個含有5個整型元素的陣列,符合正確的傳參規則。
回撥函式
回撥函式是把函式指標作為引數傳給另一個函式,當這個指標被用來呼叫其所指向的函式時,我們就說這是回撥函式。回撥函式不是由函式實現方直接呼叫,而是用另外一方或者特定條件下來呼叫。
比較常見的例子就是C語言裡面的庫函式快速排序,這裡需要自己實現的比較函式,就用到了回撥函式,int_cmp作為函式的指標充當了qsort的引數。
模擬實現qsort快速排序函式,氣泡排序的推廣
&n