第二章為程序設計技術,本文為2.2.3 內置函數指針和2.2.4 嵌套結構體。
我們知道,數組和指針是相同類型有序數據的集合,但很多時候需要將不同類型的數據捆綁在一起作為一個整體來對待,使程序設計更方便。在C語言中,這樣的一組數據被稱為結構體。
>>>2.2.3內置函數指針
面對一系列數據,真正重要的不是如何存儲數據,而是如何使用數據。實際上,一個結構體的成員可以是數據,還可以是包含操作數據的函數指針。為了支持這種風格,在這里不妨引入一個新的概念——方法是作為某個結構體的一部分聲明的,有了方法就可以操作存儲在結構體中的數據。
1.類型與變量
當函數指針作為結構體的成員時,即將校驗參數和調用校驗器的函數指針封裝在一起,形成了一個新的結構體類型。有了類型就可以定義一個該類型的變量,然后就可以用這個變量引用校驗參數和調用校驗器函數。
為了支持這種風格,C允許將方法作為某個結構體的一部分來聲明,那么操作存儲在結構體中的數據就很容易了,詳見程序清單2.18。
程序清單 2.18 范圍值校驗器接口
接下來需要設計一個判斷value值是否符合范圍值要求的validateRange()接口函數,其具體的實現詳見程序清單2.19。
程序清單 2.19 范圍值校驗器接口函數的實現
同理,偶校驗器OddEvenValidator和變量oddEvenValidator的定義詳見程序清單2.20。
程序清單 2.20 偶校驗器接口
接下來同樣需要設計一個判斷value值是否符合偶校驗要求的validateOddEven()接口函數,其具體的實現詳見程序清單2.21。
程序清單 2.21 偶校驗器接口函數的實現
顯然,無論是什么校驗器,其共性是value值合法性判斷,因此可以共用一個函數指針,即特殊的函數指針類型RangeValidate和OddEvenValidate被泛化成了一般的函數指針類型Validate。其次,由于每個函數都有一個指向當前對象的pThis指針,因此特殊的結構體類型struct _RangeValidator*和struct _OddEvenValidator *被泛化成了void *類型,即可接受任何類型數據的實參。比如:
這就是范型編程,校驗器泛化接口的實現詳見程序清單 2.22。由于pRangeValidator與pThis的類型不同,因此必須對pThis指針強制類型轉換才能引用相應結構體的成員。
程序清單 2.22 通用校驗器接口的實現(validator.c)
由此可見,當將方法作為結構體的一部分聲明時,就直接將方法和數據打包成為了一個新的數據類型RangeValidator。有了RangeValidator類型,就可以創建一個該類型的變量rangeValidator,即可通過rangeValidator引用該結構體的數據,并調用相應的處理函數。真正想強化的是由方法定義結構體的思想,而不是實現結構體時碰巧用到的那些數據。
2.初始化
使用名為newRangeValidator的宏將結構體初始化:
其中,validateRange為范圍值校驗器的函數名,使用方法如下:
宏展開后如下:
其相當于:
如果有以下定義:
即可通過pValidator引用RangeValidator的min和max。校驗函數的調用方式如下:
以上調用形式的前提是已知pValidator指向了確定的結構體類型,如果pValidator將指向未知的校驗器,顯然以上調用形式無法做到通用,那么將如何調用?
雖然pValidator與&rangeValidator.validate的類型不一樣,但它們的值相等,因此可以利用這一特性獲取validateRange()函數的地址。比如:
其調用形式如下:
3.接口與實現
為了便于閱讀,如程序清單2.23所示詳細地展示了通用校驗器的接口。
程序清單 2.23 通用校驗器接口(validator.h)
以范圍值校驗器為例,調用validateRange()的rangeCheck()函數的實現如下:
rangeCheck()函數的調用形式如下:
由此可見,rangeCheck()函數的實現不依賴任何具體校驗器。 注意,在這里,作者并沒有提供完整的代碼,請讀者補充完善。
>>>2.2.4嵌套結構體
1.重構
隨著添加一個又一個功能,處理一個又一個錯誤,代碼的結構會逐漸退化。如果對此置之不理,這種退化最終會導致糾結不清,難以維護的混亂代碼,因此需要經常性地重構代碼扭轉這種退化。
重構就是在不改變代碼行為的前提下,對其進行一系列小的改進,旨在改進系統結構的實踐活動。雖然每個改進都是微不足道的,甚至幾乎不值得去做,但如果將所有的改造疊加在一起時,對系統設計和架構的改進效果是十分明顯的。
在每次細微改進后,通過運行單元測試以確保改進沒有造成任何破壞,然后才去做下一次改進。如此往復周而復始,每次改進后都要運行,通過這種方式保證在改進系統設計的同時系統能夠正常工作。
重構是持續進行的,而不是在項目結束時、發布版本時、迭代結束時、甚至每天下班時才進行。重構是每隔一個小時或半個小時就要去做的事情,通過重構可以持續地保持盡可能干凈、簡單且有表現力的代碼。
大量的實踐證明,重復可能是軟件中一切邪惡的根源,許多原則和實踐規則都是為了控制與消除重復而創建的。消除重復最好的方法就是抽象,即將所有公共的函數指針移到一個單獨的結構體中,創建一個通用的Validator類型校驗器。也就是說,如果兩種事物相似的話,必定存在某種抽象能夠統一它們,因此消除重復的行為會迫使團隊提煉出許多的抽象,進一步減少代碼之間的耦合。
自從發明子程序以來,軟件開發領域的所有創新都是在不斷嘗試從源代碼中消滅重復,即DRY(Don't Repeat Yourself)原則——別重復自己,因為重復黏貼會帶來很多的問題,所以無論在哪里發現重復的代碼,都必須消除它們。
2.類型與變量
實際上,不管是范圍值校驗器還是奇偶校驗器,其本質上都是校驗器,其相同的屬性是校驗參數和待校驗的值,其相同的行為可以共用一個函數指針調用不同的校驗器。根據依賴倒置原則,將它們相同的屬性和行為抽象為一個結構體類型Validator。比如:
在這里,還是以范圍值校驗為例,在RangeValidatro結構體中嵌套一個Validator類型的結構體,即將Validator類型的變量isa作為RangeValidator結構體的成員。比如:
由于&rangeValidator與&rangeValidator.isa的值相等,因此以下關系恒成立。比如:
即可將validateRange()函數原型:
中的“void *pThis”轉換為“Validator *pThis”,validatrRange()函數原型進化為:
3.初始化
當將Validator類型的isa作為RangeValidator結構體成員時,顯然rangeValidator.isa是一個結構體變量名,可以象任何普通結構體變量一樣使用。使用Validator類型表達式:
即可引用rangeValidator變量的結構體成員isa的成員validate,即將rangeValidator.isa作為另一個點操作符的左操作符。比如:
由于點操作符的結合性是從左向右的,因此可以省略括號。其等價于:
只要將rangeValidator.isa看作一個Validator類型的變量即可。
使用名為newRangeValidator的宏將結構體初始化:
其中,validateRange為范圍值校驗器函數名,使用方法如下:
宏展開后如下:
其中,外面的{}為RangeValidator結構體賦值,內部的{}為RangeValidator結構體的成員變量isa賦值。即:
如果有以下定義:
即可用pValidator引用RangeValidator的min和max。
由于pValidator與&rangeValidator.isa不僅類型相同且值相等,則以下關系同樣成立:
因此可以利用這一特性獲取validateRange()函數的地址,即pValidator->validate指向validateRange()。其調用形式如下:
4.接口與實現
以范圍值校驗器為例,validatorCheck()函數的調用形式如下:
當然,也可以采取以下調用形式:
其效果是一樣的。
為了便于閱讀,如程序清單 2.24所示詳細地展示了通用校驗器的接口。
程序清單 2.24通用校驗器接口(validator.h)
以范圍值校驗器為例,調用validateRange()的validatorCheck()函數的實現如下:
由此可見,validatorCheck()函數的實現不依賴任何具體校驗器,通用校驗器接口的實現詳見程序清單 2.25。
程序清單 2.25 通用校驗器接口的實現(validator.c)
在這里,作者并沒有提供完整的代碼,請讀者補充完善。
-
指針
+關注
關注
1文章
480瀏覽量
70510 -
C語言編程
+關注
關注
6文章
90瀏覽量
21093 -
周立功
+關注
關注
38文章
130瀏覽量
37584 -
結構體
+關注
關注
1文章
130瀏覽量
10833
原文標題:周立功:結構體,使程序設計更方便——內置函數指針和嵌套結構體
文章出處:【微信號:ZLG_zhiyuan,微信公眾號:ZLG致遠電子】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論