91欧美超碰AV自拍|国产成年人性爱视频免费看|亚洲 日韩 欧美一厂二区入|人人看人人爽人人操aV|丝袜美腿视频一区二区在线看|人人操人人爽人人爱|婷婷五月天超碰|97色色欧美亚州A√|另类A√无码精品一级av|欧美特级日韩特级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

for竟然有那么多種用法!

工程師 ? 來(lái)源:嵌入式資訊精選 ? 作者:嵌入式資訊精選 ? 2020-09-11 13:58 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

【說(shuō)在前面的話】

通過(guò)本系列前面兩篇文章的學(xué)習(xí),我們掌握了宏的基本語(yǔ)法和使用規(guī)則,諷刺的是這些所謂的“基本語(yǔ)法和規(guī)則”卻恰恰是正規(guī)C語(yǔ)言教育中所缺失的。本文的內(nèi)容將建立在前面構(gòu)筑的基礎(chǔ)之上,以for功能的挖掘和封裝為契機(jī),手把手的教會(huì)你如何正確使用宏來(lái)簡(jiǎn)化日常開(kāi)發(fā),增強(qiáng)C語(yǔ)言的可讀性、降低應(yīng)用開(kāi)發(fā)的難度、同時(shí)還盡可能避免宏對(duì)日常代碼調(diào)試帶來(lái)的負(fù)面影響。

【被低估的價(jià)值】

想必大家對(duì)C語(yǔ)言中的 for 循環(huán)結(jié)構(gòu)并不陌生。根據(jù)C/C++語(yǔ)法網(wǎng)站cppreference.com 的介紹,for 的語(yǔ)法結(jié)構(gòu)如下:

for ( init_clause ; cond_expression ; iteration_expression ) loop_statement

這里,我并不想假設(shè)大家對(duì) for 結(jié)構(gòu)一無(wú)所知,并介紹一堆教科書上已有的內(nèi)容。然而,在 for 的語(yǔ)法結(jié)構(gòu)中有幾個(gè)大家容易忽視的地方,而它們恰恰是本文后續(xù)各種“展開(kāi)”的基礎(chǔ):

for 循環(huán)中的 cond_expression 和 interation_expression 都必須是表達(dá)式,而不能是直接的語(yǔ)句。

for 循環(huán)中第一個(gè)部分 init_clause 一開(kāi)始是用來(lái)放置給變量賦值的表達(dá)式;但從ANSI-C99開(kāi)始,init_clause 可以被用來(lái)建立局部變量;而局部變量的生命周期覆蓋且僅覆蓋整個(gè)for循環(huán)——這一點(diǎn)非常有利用價(jià)值,也是大家容易忽略的地方。

為了說(shuō)明這一點(diǎn),我們不妨舉幾個(gè)例子。首先在C99標(biāo)準(zhǔn)之前,如果你要在 for 循環(huán)中使用一個(gè)循環(huán)變量,你只能在進(jìn)入 for 之前將其定義好:

int i = 0;。..for (i = 0; i 《 100; i++) { 。..}

如你所見(jiàn),雖然我們可以在 init_clause 的位置對(duì)變量賦值,但它并不是必須的——多少一點(diǎn)雞肋是不是?也許更雞肋的是,你可以在 init_clause 這里完成更多的賦值操作,比如:

int i = 0, j,k;。..for (i = 0, j = 100, k = 1; i 《 100; i++) { 。..}

實(shí)際上,明眼人都可以看出,init_clause 中所作的事情完全可以放置到 for 循環(huán)之前去完成,還可以避免“使用逗號(hào)進(jìn)行分隔” 這樣讓人不那么習(xí)慣的使用方式。也許是意識(shí)到這一點(diǎn),C99允許在 init_clause 里定義局部變量,而正是這一點(diǎn),完全改變了 for 的命運(yùn)(關(guān)于這一點(diǎn),我們將在隨后的內(nèi)容中詳細(xì)介紹)。現(xiàn)在,上述代碼可以等效的改寫為:

for (int i = 0, j = 100, k = 1; i 《 100; i++) { 。..}

需要強(qiáng)調(diào)的是,這里仍然有一個(gè)小小的限制,即:init_clause 里雖然可以定義局部變量,但這些變量只能是同一類型的,或者是指向這一類型的指針。因此下面的寫法是非法的:

for (int i = 0, short j = 100; i 《 100; i++) { 。..}

而這樣的寫法是合法的:

for (int i = 0, *p = NULL; i 《 100; i++) { 。..}

請(qǐng)大家務(wù)必留意這里的語(yǔ)法細(xì)節(jié),我們將在后面的封裝中大規(guī)模使用。

另外一個(gè)值得注意的是 for 的執(zhí)行順序,它可以用下面的流程圖來(lái)表示:

容易發(fā)現(xiàn),經(jīng)過(guò)必要的“構(gòu)造”,我們可以恰好實(shí)現(xiàn)一個(gè)如同 do { } while(0) 一樣的效果:

圖中灰色的部分為原本實(shí)際的執(zhí)行流程,而純黑色的線條以及最下方的虛線箭頭則為等效的運(yùn)行流程。與do {} while(0) 相比,在我們眼中 for 循環(huán)的幾個(gè)關(guān)鍵部分就有了新的意義:

在執(zhí)行用戶代碼之前(灰色部分),有能力進(jìn)行一定的“準(zhǔn)備工作”(Before部分);

在執(zhí)行用戶代碼之后,有能力執(zhí)行一定的“收尾工作”(After部分)

在init_clause階段有能力定義一個(gè)“僅僅只覆蓋” for 循環(huán)的,并且只對(duì) User Code可見(jiàn)的局部變量——換句話說(shuō),這些局部變量是不會(huì)污染 for 循環(huán)以外的地方的。

【構(gòu)造using結(jié)構(gòu)】

上面所提到的結(jié)構(gòu),在C#中有一個(gè)類似的語(yǔ)法,叫做 using(),其典型的用法如下:

using (StreamReader tReader = File.OpenText(m_InputTextFilePath)){ while (!tReader.EndOfStream) { 。.. }}

以上述代碼為例進(jìn)行講解:

在 using 圓括號(hào)內(nèi)定義的變量,其生命周期僅覆蓋 using 緊隨其后的花括號(hào)內(nèi)部;

當(dāng)用于代碼離開(kāi) using 結(jié)構(gòu)的時(shí)候,using 會(huì)自動(dòng)執(zhí)行一個(gè)“掃尾工作”,而這個(gè)掃尾工作是對(duì)應(yīng)的類事先定義好的。在上述例子中,所謂的掃尾工作就是關(guān)閉 與 類StreamReader的實(shí)例tReader 所關(guān)聯(lián)的文件——簡(jiǎn)單說(shuō)就是using會(huì)自動(dòng)把文件關(guān)閉,而不必用戶親自動(dòng)手。

是不是聞到了熟悉的味道?不要搞錯(cuò)因果關(guān)系——我們正是對(duì)C#中的using結(jié)構(gòu)“甚是眼饞”才決定自己動(dòng)手,用 for 來(lái)創(chuàng)造一個(gè)——現(xiàn)有C#的using結(jié)構(gòu)才有我們后面的嘗試。下圖是using所等校流程圖,可以看到他比我們此前的結(jié)構(gòu)還少了一個(gè)“Before”部分:

要實(shí)現(xiàn)類似using的結(jié)構(gòu),首先要考慮如何構(gòu)造一個(gè)“至執(zhí)行一次”的for循環(huán)結(jié)構(gòu)。要做到這一點(diǎn),毫無(wú)難度:

for (int i = 1; i 》 0; i++) { 。..}

以此為起點(diǎn),對(duì)比我們的“藍(lán)圖”,發(fā)現(xiàn)至少有以下幾個(gè)問(wèn)題:

如何實(shí)現(xiàn) before和after的部分?

現(xiàn)在用的變量 i 固定是 int 類型的,如何允許用戶在 init_clause 定義自己的局部變量,并允許使用自己的類型?

問(wèn)題一:如何實(shí)現(xiàn) before 和 after 部分

對(duì)比前面的圖例,我們知道 before 和 after 的部分實(shí)際上分別對(duì)應(yīng) for 循環(huán)的 cond_expression 和 iteration_expression;同時(shí),這兩個(gè)部分都必須是表達(dá)式——由于表達(dá)式的限制,能插入在 before 和 after 部分的內(nèi)容實(shí)際上就只能是“普通表達(dá)式”或者是“函數(shù)”。

由于我們還必須至少借助 cond_expression 來(lái)實(shí)現(xiàn) “只運(yùn)行一次” 的功能,如何見(jiàn)縫插針的實(shí)現(xiàn) before 的功能呢?不繞彎子,看代碼:

//! 假設(shè)用戶要插入的內(nèi)容我們都放在叫做 before 和after的函數(shù)里extern void before(void);extern void after(void);for (int i = 1; //!《 init_clause i--?(before(),1):0; //!《 cond_expression after()) //!《 iteration_expression{ 。..}

我們知道,cond_expression 只在乎用戶表達(dá)式的返回值是0還是非0,因此,這里其實(shí)真正起作用的本體是 “i--”——第一次判斷的時(shí)候返回值是1,由于自減操作,第二次判斷的時(shí)候就是0了——這就完成了讓 for 運(yùn)行且只運(yùn)行一次的功能。

接下來(lái),我們借助一個(gè)問(wèn)好表達(dá)式,嘗試給 i-- 的結(jié)果做一個(gè)等效“解釋”,即:

(i--) ? 1 : 0

用人話說(shuō)就是,如果 (i--)值是非0的,我們就返回1,反之返回0。這么做的意義是為了進(jìn)一步通過(guò)逗號(hào)表達(dá)式對(duì) “1” 所在的部分進(jìn)行擴(kuò)展:

(i--) ? (before(), 1) //!《 使用逗哈表達(dá)式進(jìn)行擴(kuò)展: 0

由于逗號(hào)表達(dá)式只管 最右邊的結(jié)果,忽略所有左邊的返回值,因此,哪怕before()函數(shù)沒(méi)有實(shí)際返回值對(duì)C編譯器來(lái)說(shuō)都是無(wú)所謂的。同理,由于我們?cè)赾ond_expression部分已經(jīng)完成了所有功能,因此 iteration_expression 就任由我們?cè)赘盍恕幾g器原本就對(duì)此處表達(dá)式所產(chǎn)生的數(shù)值并不感興——我們直接放下 after() 函數(shù)即可。

至此,插入 before() 和 after() 的問(wèn)題圓滿解決。

問(wèn)題二:如何允許用戶定義自己的局部變量,并且擁有自己的類型

要解決這個(gè)問(wèn)題,首先必須打破定勢(shì)思維,即:for循環(huán)只能用整型變量。實(shí)際并非如此,對(duì)for來(lái)說(shuō)真正起作用的只有 cond_expression 的返回值,而它只關(guān)心用戶的表達(dá)式返回的 布爾量 是什么——換句話說(shuō),有無(wú)數(shù)種方法來(lái)產(chǎn)生 cond_expression,而使用普通的整形計(jì)數(shù)器,并對(duì)其進(jìn)行判斷只是眾多方法中的一種。

打破了這一定勢(shì)思維后,我們就從問(wèn)題本身出發(fā)考慮:允許用戶用自己的類型定義自己的變量——雖然看似我們并不能知道用戶會(huì)用什么類型來(lái)定義變量,因而就無(wú)法寫出通用的 cond_expression 來(lái)實(shí)現(xiàn)“讓for執(zhí)行且執(zhí)行一次”的功能,然而,你們也許忘記了 init_clause 的一個(gè)特點(diǎn):它還可以定義指針——換句話說(shuō),無(wú)論用戶定義了什么類型,我們都可以在最后定義一個(gè)指向該類型的指針:

#define using(__declare, __on_enter_expr, __on_leave_expr) \ for (__declare, *_ptr = NULL; \ _ptr++ == NULL ? \ ((__on_enter_expr),1) : 0; \ __on_leave_expr \ )

為了驗(yàn)證我們的結(jié)果,不妨寫一個(gè)簡(jiǎn)單的代碼:

using(int a = 0,printf(“========= On Enter =======\r\n”), printf(“========= On Leave =======\r\n”)) { printf(“\t In Body a=%d \r\n”, ++a);}

這是對(duì)應(yīng)的執(zhí)行效果:

我們不妨將上述的宏進(jìn)行展開(kāi),一個(gè)可能的結(jié)果是:

for (int a = 0, *_ptr = NULL; _ptr++ == NULL ? ((printf(“========= On Enter =======\r\n”)),1) : 0; printf(“========= On Leave =======\r\n”) ) { printf(“\t In Body a=%d \r\n”, ++a);}

從 init_clause 的展開(kāi)結(jié)果來(lái)看,完全符合要求:

int a = 0, *_ptr = NULL;

接下來(lái),為了提高宏的魯棒性,我們可以繼續(xù)做一些改良,比如給指針一個(gè)唯一的名字:

#define using(__declare, __on_enter_expr, __on_leave_expr) \ for (__declare, *CONNECT3(__using_, __LINE__,_ptr) = NULL; \ CONNECT3(__using_, __LINE__,_ptr)++ == NULL ? \ ((__on_enter_expr),1) : 0; \ __on_leave_expr \ )

這里,實(shí)際上是使用了前面文章中介紹的宏 CONNECT3() 將 “__using_”,__LINE__所表示的當(dāng)前行號(hào),以及 “_ptr” 粘連在一起,形成一個(gè)唯一的局部變量名:

CONNECT3(__using_, __LINE__,_ptr)

如果你對(duì) CONNECT() 宏的來(lái)龍去脈感興趣,可以單擊這里。

更進(jìn)一步,如果用戶有不同的需求:比如想定義兩個(gè)以上的局部變量,或是想省確 __on_enter_expr 或者是 __on_leave_expr ——我們完全可以定義多個(gè)不同版本的 using:

#define __using1(__declare) \ for (__declare, *CONNECT3(__using_, __LINE__,_ptr) = NULL; \ CONNECT3(__using_, __LINE__,_ptr)++ == NULL; \ )#define __using2(__declare, __on_leave_expr) \ for (__declare, *CONNECT3(__using_, __LINE__,_ptr) = NULL; \ CONNECT3(__using_, __LINE__,_ptr)++ == NULL; \ __on_leave_expr \ )#define __using3(__declare, __on_enter_expr, __on_leave_expr) \ for (__declare, *CONNECT3(__using_, __LINE__,_ptr) = NULL; \ CONNECT3(__using_, __LINE__,_ptr)++ == NULL ? \ ((__on_enter_expr),1) : 0; \ __on_leave_expr \ )#define __using4(__dcl1, __dcl2, __on_enter_expr, __on_leave_expr) \ for (__dcl1, __dcl2, *CONNECT3(__using_, __LINE__,_ptr) = NULL; \ CONNECT3(__using_, __LINE__,_ptr)++ == NULL ? \ ((__on_enter_expr),1) : 0; \ __on_leave_expr \ )

借助宏的重載技術(shù),我們可以根據(jù)用戶輸入的參數(shù)數(shù)量自動(dòng)選擇正確的版本:

#define using(。..) \ CONNECT2(__using, VA_NUM_ARGS(__VA_ARGS__))(__VA_ARGS__)

至此,我們完成了對(duì) for 的改造,并提出了__using1, __using2, __using3 和 __using4 四個(gè)版本變體。那么問(wèn)題來(lái)了,他們分別有什么用處呢?

【提供不阻礙調(diào)試的代碼封裝】

前面的文章中,我們?cè)幸鉄o(wú)意的提供過(guò)一個(gè)實(shí)現(xiàn)原子操作的封裝:即在代碼的開(kāi)始階段關(guān)閉全局中斷并記錄此前的中斷狀態(tài);執(zhí)行用戶代碼后,恢復(fù)關(guān)閉中斷前的狀態(tài)。其代碼如下:

#define SAFE_ATOM_CODE(。..) \{ \ uint32_t CONNECT2(temp, __LINE__) = __disable_irq(); \ __VA_ARGS__ \ __set_PRIMASK((CONNECT2(temp, __LINE__))); \}

因此可以很容易的通過(guò)如下的代碼來(lái)保護(hù)關(guān)鍵的寄存器操作:

/** \fn void wr_dat (uint16_t dat) \brief Write data to the LCD controller \param[in] dat Data to write*/static __inline void wr_dat (uint_fast16_t dat) { SAFE_ATOM_CODE ( LCD_CS(0); GLCD_PORT-》DAT = (dat 》》 8); /* Write D8..D15 */ GLCD_PORT-》DAT = (dat & 0xFF); /* Write D0..D7 */ LCD_CS(1); )}

唯一的問(wèn)題是,這樣的寫法,在調(diào)試時(shí)完全沒(méi)法在用戶代碼處添加斷點(diǎn)(編譯器會(huì)認(rèn)為宏內(nèi)所有的內(nèi)容都寫在了同一行),這是大多數(shù)人不喜歡使用宏來(lái)封裝代碼結(jié)構(gòu)的最大原因。借助 __using2,我們可以輕松的解決這個(gè)問(wèn)題:

#define SAFE_ATOM_CODE() \ __using2( uint32_t CONNECT2(temp,__LINE__) = __disable_irq(), \ __set_PRIMASK(CONNECT2(temp,__LINE__)))

修改上述的代碼為:

static __inline void wr_dat (uint_fast16_t dat) { SAFE_ATOM_CODE() { LCD_CS(0); GLCD_PORT-》DAT = (dat 》》 8); /* Write D8..D15 */ GLCD_PORT-》DAT = (dat & 0xFF); /* Write D0..D7 */ LCD_CS(1); }}

由于using的本質(zhì)是 for 循環(huán),因?yàn)槲覀兛梢酝ㄟ^(guò)花括號(hào)的形式來(lái)包裹用戶代碼,因此,可以很方便的在用戶代碼中添加斷點(diǎn),單步執(zhí)行。至于原子保護(hù)的功能,我們不妨將上述代碼進(jìn)行宏展開(kāi):

static __inline void wr_dat (uint_fast16_t dat){ for (uint32_t temp154 = __disable_irq(), *__using_154_ptr = NULL; __using_154_ptr++ == NULL ? ((temp154 = temp154),1) : 0; __set_PRIMASK(temp154) ) { LCD_CS(0); GLCD_PORT-》DAT = (dat 》》 8); GLCD_PORT-》DAT = (dat & 0xFF); LCD_CS(1); }}

通過(guò)觀察,容易發(fā)現(xiàn),這里巧妙使用 init_clause 給 temp154 變量進(jìn)行賦值——在關(guān)閉中斷的同時(shí)保存了此前的狀態(tài);并在原本 after 的位置放置了 恢復(fù)中斷的語(yǔ)句 __set_PRIMASK(temp154)。

舉一反三,此類方法除了用來(lái)開(kāi)關(guān)中斷以外,還可以用在以下的場(chǎng)合:

在OOPC中自動(dòng)創(chuàng)建類,并使用 before 部分來(lái)執(zhí)行構(gòu)造函數(shù);在 after 部分完成 類的析構(gòu)。

在外設(shè)操作中,在 init_clause 部分定義指向外設(shè)的指針;在 before部分 Enable或者Open外設(shè);在after部分Disable或者Close外設(shè)。

RTOS中,在 before 部分嘗試進(jìn)入臨界區(qū);在 after 部分釋放臨界區(qū)

在文件操作中,在 init_clause 部分嘗試打開(kāi)文件,并獲得句柄;在 after 部分自動(dòng) close 文件句柄。

在有MPU進(jìn)行內(nèi)存保護(hù)的場(chǎng)合,在 before 部分,重新配置MPU獲取目標(biāo)地址的訪問(wèn)權(quán)限;在 after部分再次配置MPU,關(guān)閉對(duì)目標(biāo)地址范圍的訪問(wèn)權(quán)限。

……

【構(gòu)造with塊】

不知道你們?cè)趯?shí)際應(yīng)用中有沒(méi)有遇到一連串指針訪問(wèn)的情形——說(shuō)起來(lái)就好比是:

你鄰居的-》朋友的-》親戚家的-》一個(gè)狗的-》保姆的-》手機(jī)

如果我們要操作這里的“手機(jī)”,實(shí)在是不想每次都寫這么一長(zhǎng)串“惡心”的東西,為了應(yīng)對(duì)這一問(wèn)題,Visual Basic(其實(shí)最早是Quick Basic)引入了一個(gè)叫做 WITH 塊的概念,它的用法如下:

WITH 你鄰居的-》朋友的-》親戚家的-》一個(gè)狗的-》保姆的-》手機(jī) # 這里可以直接訪問(wèn)手機(jī)的各項(xiàng)屬性,用 “?!?開(kāi)頭就行 。 手機(jī)殼顏色 = xxxxx 。 貼膜 = 玻璃膜END WITH

不光是Visual Basic,我們使用C語(yǔ)言進(jìn)行大規(guī)模的應(yīng)用開(kāi)發(fā)時(shí),或多或少也會(huì)遇到同樣的情況,比如,配置 STM32 外設(shè)時(shí),填寫外設(shè)配置結(jié)構(gòu)體的時(shí)候,每一行都要重新寫一遍結(jié)構(gòu)體變量的名字,也是在是很繁瑣:

static UART_HandleTypeDef s_UARTHandle = UART_HandleTypeDef(); s_UARTHandle.Instance = USART2; s_UARTHandle.Init.BaudRate = 115200; s_UARTHandle.Init.WordLength = UART_WORDLENGTH_8B; s_UARTHandle.Init.StopBits = UART_STOPBITS_1; s_UARTHandle.Init.Parity = UART_PARITY_NONE; s_UARTHandle.Init.HwFlowCtl = UART_HWCONTROL_NONE; s_UARTHandle.Init.Mode = UART_MODE_TX_RX;

入股有了with塊的幫助,上述代碼可能就會(huì)變得更加清爽,比如:

static UART_HandleTypeDef s_UARTHandle = UART_HandleTypeDef();with(s_UARTHandle) { .Instance = USART2; .Init.BaudRate = 115200; .Init.WordLength = UART_WORDLENGTH_8B; .Init.StopBits = UART_STOPBITS_1; .Init.Parity = UART_PARITY_NONE; .Init.HwFlowCtl = UART_HWCONTROL_NONE; .Init.Mode = UART_MODE_TX_RX;}

遺憾的是,如果要完全實(shí)現(xiàn)上述的結(jié)構(gòu),在C語(yǔ)言中是不可能的,但借助我們的 using() 結(jié)構(gòu),我們可以做到一定程度的模擬

#define with(__type, __addr) using(__type *_p=(__addr))#define _ (*_p)

在這里,我們要至少提供目標(biāo)對(duì)象的類型,以及目標(biāo)對(duì)象的地址:

static UART_HandleTypeDef s_UARTHandle = UART_HandleTypeDef();with(UART_HandleTypeDef &s_UARTHandle) { _.Instance = USART2; _.Init.BaudRate = 115200; _.Init.WordLength = UART_WORDLENGTH_8B; _.Init.StopBits = UART_STOPBITS_1; _.Init.Parity = UART_PARITY_NONE; _.Init.HwFlowCtl = UART_HWCONTROL_NONE; _.Init.Mode = UART_MODE_TX_RX;}

注意到,這里“_”實(shí)際上被用來(lái)替代 s_UARTHandle——雖然感覺(jué)有點(diǎn)不夠完美,但考慮到腳本語(yǔ)言 perl 有長(zhǎng)期使用 “_” 表示本地對(duì)象的傳統(tǒng),這樣一看,似乎“_” 就是一個(gè)對(duì) “perl” 的完美致敬了。

【回歸本職 foreach】

很多高級(jí)語(yǔ)言都有專門的 foreach 語(yǔ)句,用來(lái)實(shí)現(xiàn)對(duì)數(shù)組(或是鏈表)中的元素進(jìn)行逐一訪問(wèn)。原生態(tài)C語(yǔ)言并沒(méi)有這種奢侈,即便如此,Linux也定義了一個(gè)“野生”的 foreach 來(lái)實(shí)現(xiàn)類似的功能。為了演示如何使用 using 結(jié)構(gòu)來(lái)構(gòu)造 foreach,我們不妨來(lái)看一個(gè)例子:

typedef struct example_lv0_t { uint32_t wA; uint16_t hwB; uint8_t chC; uint8_t chID;} example_lv0_t;example_lv0_t s_tItem[8] = { {.chID = 0}, {.chID = 1}, {.chID = 2}, {.chID = 3}, {.chID = 4}, {.chID = 5}, {.chID = 6}, {.chID = 7},};

我們希望實(shí)現(xiàn)一個(gè)函數(shù),能通過(guò) foreach 自動(dòng)的訪問(wèn)數(shù)組 s_tItem 的所有成員,比如:

foreach(example_lv0_t, s_tItem) { printf(“Processing item with ID = %d\r\n”, _.chID);}

跟With塊一樣,這里我們?nèi)匀弧爸戮础?perl——使用 “_” 表示當(dāng)前循環(huán)下的元素。在這個(gè)例子中,為了使用 foreach,我們需要提供至少兩個(gè)信息:目標(biāo)數(shù)組元素的類型(example_lv0_t)和目標(biāo)數(shù)組(s_tItem)。

這里的難點(diǎn)在于,如何定義一個(gè)局部的指針,并且它的作用范圍僅僅只覆蓋 foreach 的循環(huán)體。此時(shí),坐在角落里的 __with1() 按耐不住了,高高的舉起了雙手——是的,它僅有的功能就是允許用戶定義一個(gè)局部變量,并覆蓋由第三方所編寫的、由 {} 包裹的區(qū)域:

#define dimof(__array) (sizeof(__array)/sizeof(__array[0]))#define foreach(__type, __array) \ __using1(__type *_p = __array) \ for ( uint_fast32_t CONNECT2(count,__LINE__) = dimof(__array); \ CONNECT2(count,__LINE__) 》 0; \ _p++, CONNECT2(count,__LINE__)-- \ )

上述的宏并不復(fù)雜,大家完全可以自己看懂,唯一需要強(qiáng)調(diào)的是,using() 的本質(zhì)是一個(gè)for,因此__using1() 下方的for 實(shí)際上是位于由 __using1() 所提供的循環(huán)體內(nèi)的,也就是說(shuō),這里的局部變量_p其作用域也覆蓋 下面的for 循環(huán),這就是為什么我們可以借助:

#define _ (*_p)

的巧妙代換,通過(guò) “_” 來(lái)完成對(duì)指針“_p”的使用。為了方便大家理解,我們不妨將前面的例子代碼進(jìn)行宏展開(kāi):

for (example_lv0_t *_p = s_tItem, *__using_177_ptr = NULL; __using_177_ptr++ == NULL ? ((_p = _p),1) : 0; ) for ( uint_fast32_t count177 = (sizeof(s_tItem)/sizeof(s_tItem[0])); count177 》 0; _p = _p+1, count177-- ) { printf(“Processing item with ID = %d\r\n”, (*_p).chID); }

其執(zhí)行結(jié)果為:

foreach目前的用法看起來(lái)“歲月靜好”,似乎沒(méi)有什么問(wèn)題,可惜的是,一旦進(jìn)行實(shí)際的代碼編寫,我們會(huì)發(fā)現(xiàn),假如我們要在 foreach 結(jié)構(gòu)中再用一個(gè)foreach,或是在foreach中使用 with 塊,就會(huì)出現(xiàn) “_” 被覆蓋的問(wèn)題——也就是在里層的 foreach或是 with 無(wú)法通過(guò) “_” 來(lái)訪問(wèn)外層“_” 所代表的對(duì)象。為了應(yīng)對(duì)這一問(wèn)題,我們可以對(duì) foreach 進(jìn)行一個(gè)小小的改造——允許用戶再指定一個(gè)專門的局部變量,用于替代“_” 表示當(dāng)前循環(huán)下的對(duì)象:

#define foreach2(__type, __array) \ using(__type *_p = __array) \ for ( uint_fast32_t CONNECT2(count,__LINE__) = dimof(__array); \ CONNECT2(count,__LINE__) 》 0; \ _p++, CONNECT2(count,__LINE__)-- \ )#define foreach3(__type, __array, __item) \ using(__type *_p = __array, *__item = _p, _p = _p, ) \ for ( uint_fast32_t CONNECT2(count,__LINE__) = dimof(__array); \ CONNECT2(count,__LINE__) 》 0; \ _p++, __item = _p, CONNECT2(count,__LINE__)-- \ )

這里的 foreach3 提供了3個(gè)參數(shù),其中最后一個(gè)參數(shù)就是用來(lái)由用戶“額外”指定新的指針的;與之相對(duì),老版本的foreach我們稱之為 foreach2,因?yàn)樗恍枰獌蓚€(gè)參數(shù),只能使用“_”作為對(duì)象的指代。進(jìn)一步的,我們可以使用宏的重載來(lái)簡(jiǎn)化用戶的使用:

#define foreach(。..) \ CONNECT2(foreach, VA_NUM_ARGS(__VA_ARGS__))(__VA_ARGS__)

經(jīng)過(guò)這樣的改造,我們可以用下面的方法來(lái)為我們的循環(huán)指定一個(gè)叫做“ptItem”的指針:

foreach(example_lv0_t, s_tItem, ptItem) { printf(“Processing item with ID = %d\r\n”, ptItem-》chID);}

展開(kāi)后的形式如下:

for (example_lv0_t *_p = s_tItem, ptItem = _p, *__using_177_ptr = NULL; __using_177_ptr++ == NULL ? ((_p = _p),1) : 0; ) for ( uint_fast32_t count177 = (sizeof(s_tItem)/sizeof(s_tItem[0])); count177 》 0; _p = _p+1, ptItem = _p, count177-- ) { printf(“Processing item with ID = %d\r\n”, ptItem-》chID); }

代碼已經(jīng)做了適當(dāng)?shù)恼归_(kāi)和縮進(jìn),這里就不作進(jìn)一步的分析了。

【后記】

本文的目的,算是對(duì)【為宏正名】系列所介紹的知識(shí)進(jìn)行一次示范——告訴大家如何正確的使用宏,配合已有的老的語(yǔ)法結(jié)構(gòu)來(lái)“固化”一個(gè)新的模板,并以這個(gè)模板為起點(diǎn),理解它的語(yǔ)法意義和用戶,簡(jiǎn)化我們的日常開(kāi)發(fā)。在這篇文章中,老的語(yǔ)法結(jié)構(gòu)就是 for,它是由C語(yǔ)言原生支持的,借助宏,我們封裝了一個(gè)新的語(yǔ)法結(jié)構(gòu) using(), 借助它的4種不同形式、理解它們各自的特點(diǎn),我們又分別封裝了非常實(shí)用的SAFE_ATOM_CODE(),With塊和foreach語(yǔ)法結(jié)構(gòu)——他們的存在至少證明了以下幾點(diǎn):

宏不是奇技淫巧

宏可以封裝出其它高級(jí)語(yǔ)言所提供的“基礎(chǔ)設(shè)施”

設(shè)計(jì)良好的宏可以提升代碼的可讀性,而不是破壞它

設(shè)計(jì)良好的宏并不會(huì)影響調(diào)試

宏可以用來(lái)固化某些模板,避免每次都重新編寫復(fù)雜的語(yǔ)法結(jié)構(gòu),在這里,using() 模板的出現(xiàn),避免了我們每次都重復(fù)通過(guò)原始的 for 語(yǔ)句來(lái)構(gòu)造所需的語(yǔ)法結(jié)構(gòu),極大的避免了重復(fù)勞動(dòng),以及由重復(fù)勞動(dòng)所帶來(lái)的出錯(cuò)風(fēng)險(xiǎn)

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • for
    for
    +關(guān)注

    關(guān)注

    0

    文章

    44

    瀏覽量

    16286
  • 宏匯編器
    +關(guān)注

    關(guān)注

    0

    文章

    7

    瀏覽量

    9078
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    變頻器的特殊用法

    變頻器作為電力電子技術(shù)的重要應(yīng)用設(shè)備,其核心功能是通過(guò)改變電源頻率實(shí)現(xiàn)對(duì)電機(jī)轉(zhuǎn)速的精確控制。然而在實(shí)際工業(yè)場(chǎng)景和創(chuàng)意應(yīng)用中,工程師們?cè)缫淹黄苽鹘y(tǒng)認(rèn)知,開(kāi)發(fā)出一系列令人驚嘆的特殊用法。這些創(chuàng)新實(shí)踐不僅拓展了變頻器的應(yīng)用邊界,更展現(xiàn)了電力電子技術(shù)的無(wú)限可能。
    的頭像 發(fā)表于 03-03 17:08 ?467次閱讀

    IIC的正確用法

    外部上拉電阻將I2C信號(hào)線拉至高電平。 在單片機(jī)應(yīng)用中,就要看你使用的單片機(jī)是否標(biāo)準(zhǔn)的IIC標(biāo)準(zhǔn)接口了,如果你使用了標(biāo)準(zhǔn)的IIC接口,這個(gè)接口在使能的時(shí)候,引腳進(jìn)入漏極開(kāi)路模式,不過(guò)一些單片機(jī)內(nèi) 部
    發(fā)表于 01-21 07:28

    在物聯(lián)網(wǎng)設(shè)備面臨的多種安全威脅中,數(shù)據(jù)傳輸安全威脅和設(shè)備身份安全威脅何本質(zhì)區(qū)別?

    在物聯(lián)網(wǎng)設(shè)備面臨的多種安全威脅中,數(shù)據(jù)傳輸安全威脅和設(shè)備身份安全威脅何本質(zhì)區(qū)別,實(shí)際應(yīng)用中哪一種更難防范?
    發(fā)表于 11-18 06:41

    C語(yǔ)言的printf基本用法介紹

    中使用頻率最高的一個(gè)函數(shù)一點(diǎn)也不為過(guò),每個(gè)C語(yǔ)言程序員都應(yīng)該掌握 printf 的用法,這是最基本的技能。 不過(guò) printf 的用法比較靈活,也比較復(fù)雜,初學(xué)者知識(shí)儲(chǔ)備不足,不能一下子掌握,目前
    發(fā)表于 11-12 07:04

    超級(jí)電容的作用與用法哪些

    超級(jí)電容以高能量密度和快速充放電特性,革新能源存儲(chǔ)技術(shù),廣泛應(yīng)用于電力系統(tǒng)、新能源車和高鐵等領(lǐng)域。
    的頭像 發(fā)表于 10-15 09:17 ?1023次閱讀
    超級(jí)電容的作用與<b class='flag-5'>用法</b><b class='flag-5'>有</b>哪些

    編譯vision_board_mipi_2.0inch_lvgl工程,cpu能跑到100%,竟然需要41分鐘,怎么解決?

    編譯vision_board_mipi_2.0inch_lvgl工程,cpu能跑到100%,竟然需要41分鐘,這種有解決方案嗎?
    發(fā)表于 08-29 08:19

    深入剖析電阻的用法和作用

    信號(hào)線上,為什么要接電阻?你一定想不到小小電阻,竟然有這么大的作用。本期貿(mào)澤科普實(shí)驗(yàn)室,就讓我們一起來(lái)重新認(rèn)識(shí)——電阻。
    的頭像 發(fā)表于 08-21 09:10 ?4.1w次閱讀
    深入剖析電阻的<b class='flag-5'>用法</b>和作用

    Allegro Skill布線功能之刪除Dangling介紹

    高速pcb布線完成之后,需要檢查pcb板上是否存在多余的走線、過(guò)孔以及孤島銅皮等情況,那么多余的走線以及過(guò)孔被稱為Dangling line/Dangling Vais。
    的頭像 發(fā)表于 06-25 10:03 ?2340次閱讀
    Allegro Skill布線功能之刪除Dangling介紹

    光模塊為什么那么多的波長(zhǎng)?該如何選擇?

    光纖世界里,波長(zhǎng)選擇如同調(diào)頻收音,選對(duì)頻道才能清晰接收信號(hào)。為什么有的光模塊傳輸距離僅 500 米,有的卻能跨越上百公里?答案藏在那束光的顏色里 —— 準(zhǔn)確地說(shuō),是光的波長(zhǎng)。 現(xiàn)代光通信網(wǎng)絡(luò)中,不同波長(zhǎng)的光模塊扮演著截然不同的角色。850nm、1310nm、1550nm 這三個(gè)數(shù)字構(gòu)成了光通信的基礎(chǔ)波長(zhǎng)框架,它們各自在傳輸距離、損耗特性和應(yīng)用場(chǎng)景上形成明確分工。? 一.為什么光模塊需要如此多的波長(zhǎng)? 光模塊的波長(zhǎng)多樣性源于光纖傳輸中的兩個(gè)
    的頭像 發(fā)表于 06-12 14:20 ?881次閱讀
    光模塊為什么<b class='flag-5'>有</b><b class='flag-5'>那么多</b>的波長(zhǎng)?該如何選擇?

    UIAbility組件基本用法說(shuō)明

    UIAbility組件基本用法 UIAbility組件的基本用法包括:指定UIAbility的啟動(dòng)頁(yè)面以及獲取UIAbility的上下文UIAbilityContext。 指定UIAbility
    發(fā)表于 05-16 06:32

    harmony OS NEXT-Navagation基本用法

    # Navagation基本用法 > Navigation組件是路由導(dǎo)航的根視圖容器,一般作為Page頁(yè)面的根容器使用,其內(nèi)部默認(rèn)包含了標(biāo)題欄,內(nèi)容欄和公工具欄,其中內(nèi)容區(qū)默認(rèn)首頁(yè)顯示導(dǎo)航內(nèi)容
    的頭像 發(fā)表于 04-27 17:39 ?934次閱讀

    UPS(不間斷電源)故障頻發(fā)?原因竟然是這樣

    UPS(不間斷電源)故障頻發(fā)?原因竟然是這樣
    的頭像 發(fā)表于 04-19 13:53 ?1828次閱讀
    UPS(不間斷電源)故障頻發(fā)?原因<b class='flag-5'>竟然</b>是這樣

    Linux中文本處理命令的用法

    Linux 三劍客是(grep,sed,awk)三者的簡(jiǎn)稱,熟練使用這三個(gè)工具可以提升運(yùn)維效率。Linux 三劍客以正則表達(dá)式作為基礎(chǔ),而在Linux系統(tǒng)中,支持兩種正則表達(dá)式,分別為“標(biāo)準(zhǔn)正則表達(dá)式”和“擴(kuò)展正則表達(dá)式”。在掌握好正則表達(dá)式后,將具體講解三劍客的用法。
    的頭像 發(fā)表于 04-15 10:22 ?773次閱讀
    Linux中文本處理命令的<b class='flag-5'>用法</b>

    看了那么多書,第一次有人把ZVS(零電壓開(kāi)通)說(shuō)的那么簡(jiǎn)單通透

    。上圖中ZVS綠色部分針對(duì)的是Q2而言,紅圈ZVS針對(duì)Q1而言。注意看ZVS電壓先到零,那么ZCS呢? 2. ZVS實(shí)現(xiàn)機(jī)制 在全橋或半橋拓?fù)渲?,常?jiàn)的上下管(High-Side和Low-Side
    發(fā)表于 04-08 14:21

    看完這篇,SPI其實(shí)也很簡(jiǎn)單嘛(可下載)

    ,為什么要弄那么多種總線?太難了一會(huì)I2C,一會(huì)SPI;一會(huì)內(nèi)部總線,一會(huì)外部總線碰到總線這樣的字眼,千萬(wàn)別急,通過(guò)接觸你會(huì)發(fā)現(xiàn)都有各自的特點(diǎn)通過(guò)實(shí)踐了你才會(huì)真正理解這些總線的用途,那么我們今天就來(lái)聊一聊SPI
    發(fā)表于 03-26 14:29 ?2次下載