最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網 會員登陸 & 注冊

第 18 講:指針和函數

2021-09-19 21:39 作者:SunnieShine  | 我要投稿

前文我們提到了指針的很多用法,比如指針和普通變量的使用,利用指針來指向變量,以間接操作變量的方式來控制程序的行為。我們還講到了指針和數組的方式,以及把數組傳入函數的參數的時候,會退化為指針的形式,因此數組當參數傳遞到函數里的時候,是以指針形式存在的,所以我們需要單獨為其添加一個參數表示數組的長度。我們甚至還討論了字符串(字符數組)和普通數組的表現方式。

今天我們要談論一種新型語法,這種語法不多見,所以很少有人接觸到,所以比較難。但它依然很有用途。


數組有指針,那么函數呢?

很令人欣喜和疑惑的是,在 C 語言里,函數也具有指針一說。在內存里,如果我們不嘗試把代碼編譯后的二進制串放到內存里,我們就無法調用和執(zhí)行這些方法。那么唯一能找到它們執(zhí)行位置的就是通過這些函數的執(zhí)行地址來確定函數的位置了。既然是地址,就可以存在指針。所以 C 語言為函數也提供了指針的說法。

在前文里,我們已經提到了數組退化為指針作為返回值的一個說法。這種返回指針的函數叫做指針函數,它們并不是多余和難用的,因為需要堆內存這一知識點才能輔助我們更好使用它們,不過因為超過了講解范圍,所以我們沒有在上一節(jié)內容里提及到非常深入。

今天要提到的是,間接調用函數的機制。這種機制被稱為函數指針


函數指針的用法

還記得函數需要聲明吧?聲明也被稱為定義,所以這些寫在開頭的內容被稱為“函數變量”的定義。它們被廣義化稱為一個變量,它們滿足標識符命名規(guī)則,而且也需要先定義后使用。


基本用法的實現流程

比如上述一個簡單的執(zhí)行流程,我們在外面寫的 int Add(int, int) 就是函數的定義。當我們定義了它們,我們就可以使用 Add 的函數了,否則,你必須把 Add 放在 main 前面書寫。

不過,函數指針的寫法格式有點類似于數組指針,它需要把函數名括起來,前面加上指針符號:

這就是一個函數指針。這種指針僅用于指向一個函數:

那么,在使用的時候,使用取內容符號 *,取出 Adder 這個函數指針指向的函數,并執(zhí)行調用:

這樣就可以間接通過 Adder 函數指針,來調用指向的函數 Add,傳入兩個參數 ab 來得到結果,賦值給 result 了。所以總的寫法格式是這樣的:

這里特別要提及的是,調用和聲明函數指針的時候,都需要這個星號。第一個星號(第 8 行)指的是定義語句的星號,表示定義的這個變量是一個函數指針變量;而第二個星號(第 11 行)則指的是調用指向函數(即取出內容)。

在 C 語言里,由于為函數指針賦值操作(int (*Adder)(int, int) = &Add)和調用指向函數操作((*Adder)(a, b))兩個操作基本上都是這么書寫的,沒有其它的形式,所以指針賦值操作里的取地址符可以不需要寫,而且調用函數指針指向的函數的操作也可以不寫這個取內容符號,即上面第 8 行的代碼和第 11 行的代碼可以修改變?yōu)槿缦路绞剑?/span>

函數指針構成的數組?

下面我們?yōu)榱烁邮煜ふZ法,我們來一個難一點的。我們嘗試用一個數組來存放一組函數指針。我們之前學過,數組可以存放整數(整數數組)、小數(小數數組)和字符(字符串或字符數組)。

首先考慮如下情況,我們嘗試用四個函數來實現四則運算。

那么,我們發(fā)現,它們的實現都是傳入兩個 int 參數,返回 int 結果,所以我們嘗試把它們存到數組里。我們顯然可以發(fā)現,數組的類型是這個函數指針類型,而函數指針類型的格式是

那么,這個數組就是

這個類型了。

為什么函數指針數組這么書寫呢?這是因為,數組符號 [] 一般都挨在變量名稱后,所以這里也不例外。我們在原始的函數指針名后添加數組符號 [] 暗示它是數組,這個時候它就可以表示一個函數指針數組了。而且,我們不用考慮函數指針左側的這個星號 * 到底是作用在誰上的。因為這個運算符挨著誰,就是誰,它是單目運算符。

現在,我們?yōu)槠滟x初始值。

然后我們嘗試對這個數組進行 for 循環(huán)遍歷,然后挨個執(zhí)行和輸出。

這樣執(zhí)行下來,整體的輸出就是 18 -8 65 0 5 了。整體代碼如下:

函數指針作參數

在什么場合會用到如此雞肋和別扭的寫法呢?考慮一下。前文提到的 printfputs 等等函數都是系統(tǒng)為我們已經提供好了的,我們無需實現,而且無法修改它們的實現。這樣的函數既然自帶,肯定考慮到了擴展性,即考慮到我們可能會在后期更改或給予一些特殊情況。函數指針就可以解決一部分問題。

如果我們嘗試為代碼寫一個冒泡排序的算法代碼,如下所示:

這樣就實現了。不過,如果我們考慮擴展性,萬一用戶要寫一個降序執(zhí)行冒泡排序的算法呢?那豈不是照抄代碼,只改掉比較的代碼(第 8 行)?

是的,實際上,我們實現僅需要修改第 8 行的比較,把 > 改為 < 就好了。不過很顯然的是,這種東西如果非得讓用戶自行實現一遍,還不如提供一個參數,讓它自己去實現比較操作。所以我們可以嘗試為這個函數添加一個函數指針作為一個參數,專門讓用戶自己實現這個比較算法,然后用函數指針來調用自己實現的這個比較就可以了:

僅需要這么修改它,用戶層面就可以自己實現和修改比較算法的代碼,來實現比較操作了。整體代碼如下:

函數指針傳參時的函數的聲明

最頭疼的是,這種函數的聲明語句應該如何寫。實際上,照著前文數組指針的格式簡寫就好,把參數名稱全去掉,符號該有的還保留就可以了:

你答對了嗎?

那么,再來一個麻煩的。我們嘗試把剛才的函數指針變量構成的數組作為函數參數傳遞進去。

答案就是

為什么可以寫作 int (*[])(int, int)int (**)(int, int) 呢?這是因為,去掉函數指針的部分 int (*)(int, int) 外,這兩種寫法就剩下一個 [] 和一個指針定義符 * 了,這不就恰好是一個數組的意思嗎?所以,這種寫法恰好就表示了一個函數指針變量的數組。


快速排序 qsort 函數

在 C 語言里甚至為我們貼心地提供了為數值序列排序的函數 qsort,不過這個函數用起來非常復雜,需要函數指針。


先來看看使用

這樣調用它就可以立馬得到排序后的數組,和上面自己實現的冒泡排序的操作基本上一樣,不過差別是需要添加的函數指針,有一些奇怪。

這個函數第一個參數傳入的是數組名(即數組的首地址),而第二個參數傳入的是數組的長度 9。

第三個參數和第四個參數比較麻煩。第三個參數傳入的是數組的每一個元素的內存大小,顯然這個數組是 int 類型的,所以需要指定這個數組的元素大小是 sizeof(int),而第四個參數傳入的是比較函數。這里是在上面實現的 cmp 函數,所以這里寫 &cmp 或直接寫 cmp。

下面來分析一下自己實現的函數 cmp 到底為什么參數這么奇怪。


void * 類型

const 是一個關鍵字,表示指向不可修改,所以這里我們先可以忽略掉。那么,void * 是什么類型呢?void * 往往被稱為無類型指針,這種類型允許你在傳入的時候傳入任何指針類型(int *、char * 等都可以)。不過在使用取值的時候,由于指針無法確定指向的元素類型,所以需要你自己給出強制轉換,它就會把這個類型轉換為指定的類型的指針。例如

第 3 行將會輸出這個 p 指針的地址數值,而第 4 行將在取出地址的前提下,使用 * 間接取出數值,得到 3 這個結果。注意,*(int *)p*((int *)p) 是等價的,你無需為后面這一坨內容添加括號。

那么,我們轉回 qsort 函數就可以看到,整個函數的聲明是這樣的:

從注釋里就可以看到,第四個參數里為什么傳入的兩個參數是 const void * 類型了:因為兩個參數都指向元素,而這兩個元素的類型我們是無法從第一個參數 void * 類型而確定下來的,所以我們不得不推后到調用時候才能確定,所以這個時候,我們不得不為其設置 void * 類型來表示這個指向的元素是無類型(未知類型)的。

那么,從實現角度上說,cmp 函數里給定的代碼是:

由此可以看出,我們如果需要強制得到元素的類型,需要把指針 ab 轉換為 int * 類型才可以去使用和取值。然后,我們轉換后,把地址數值再使用 * 運算符取出元素數值,將兩者相減就可以得到它們倆的大小關系了。當然,如果需要降序排序,只需要把被減數和減數換一下位置,即改為 *(int *)b - *(int *)a 即可。

總的來說,void * 類型是一個可以指向任何類型變量的指針,但需要確定指向元素的數值時,需要自己給定指針的類型,并通過間接取值的運算符 * 來取值:

即強制轉換 void * 為其它類型的指針 T *。


那么 NULL 常量?

前文提到了一個 NULL 常量,這個常量表示為所有指針類型的默認數值,即指向 0 號內存的指針。當我們現在已經學會了 void * 類型后,就可以知道 NULL 的本面目:(void *)0,它直接把 0 強制轉換為一個地址,而這個地址還是無類型的。所以這允許了所有的類型都可以用。所以,NULL(void *)0 等價。


總結

那么至此,我們就把所有關于指針的內容就介紹完了?,F在我們作出總結。

這些寫法都在函數聲明時去掉變量名 p 即可。

第 18 講:指針和函數的評論 (共 條)

分享到微博請遵守國家法律
含山县| 济南市| 凌海市| 祁阳县| 樟树市| 益阳市| 海林市| 赞皇县| 佛山市| 莱西市| 河西区| 望城县| 阳江市| 安平县| 康平县| 沅江市| 泾源县| 昌乐县| 丘北县| 玉屏| 沁水县| 方城县| 沙田区| 清徐县| 合山市| 白河县| 宁津县| 九江县| 开原市| 铁岭市| 青浦区| 汶川县| 泉州市| 广元市| 固阳县| 水富县| 华容县| 九台市| 敖汉旗| 高邑县| 普格县|