C語言基礎知識:幾種特殊的函數宏封裝方式

2020-11-12 C語言進階之路



函數宏介紹

函數宏,即包含多條語句的宏定義,其通常為某一被頻繁調用的功能的語句封裝,且不想通過函數方式封裝來降低額外的彈棧壓棧開銷。

函數宏本質上為宏,可以直接進行定義,例如:

#define INT_SWAP(a,b) \int tmp = a; \a = b; \b = tmp

但上述的宏具有一個明顯的缺點:當遇到 if、while 等語句且不使用花括號僅調用宏時,實際作用範圍在宏的第一個分號後便結束。即 a = b 和 b = tmp 均不受控制語句所作用。

因此,在工程中,一般使用三種方式來對函數宏進行封裝,分別為 {}、do{...}while(0) 和({})。下文將一一對三種方式進行分析,比較各自的優劣點。


{} 方式

INT_SWAP 宏使用 {} 封裝後形態如下:

#define INT_SWAP(a,b)\{ \int tmp = a; \a = b; \b = tmp; \}

此時,直接調用與在無花括號的控制語句(如 if、while)中調用均能正常運行,例如:

#define INT_SWAP(a,b) \{ \int tmp = a; \a = b; \b = tmp; \}int main(){int var_a = 1;int var_b = 2;INT_SWAP(var_a, var_b);printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1if (1)INT_SWAP(var_a, var_b);printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 1, var_b = 2}

但當無花括號的 if 語句存在其他分支(else if、else 等)如:

if (1)INT_SWAP(var_a, var_b);elseprintf("hello world!\n");

會發現編譯出錯:

.../mnt/hgfs/share/pr_c/src/main.c: In function 『main』:/mnt/hgfs/share/pr_c/src/main.c:18:2: error: 『else』 without a previous 『if』else

這是因為 INT_SWAP(var_a, var_b); 最後的 ; 已經把 if 的作用域終結了,後續的 else 當然沒有找到與之匹配的 if 了。

因此,解決方法有兩種,分別為不使用 ;(port.1)或規定必須使用帶花括號的if(port.2),例如:

/* port.1 */if (1)INT_SWAP(var_a, var_b)else{printf("hello world!\n");}/* port.2 */if (1){INT_SWAP(var_a, var_b);}else{printf("hello world!\n");}

可見,不使用 ; 的調用方式無論從程序閱讀還是使用方法方面都是十分彆扭的;而規定必須使用帶花括號的 if 的調用方式有違常理的,因為宏函數應該適用於任何語法。

優缺點總結:

  • 優點:簡單粗暴。
  • 缺點:不能在無花括號且有分支的 if 語句中直接調用;能夠不帶 ; 直接調用。



do{...}while(0) 方式

INT_SWAP 宏使用 do{...}while(0) 封裝後形態如下:

#define INT_SWAP(a,b) \do{ \int tmp = a; \a = b; \b = tmp; \}while(0)

do{...}while(0) 表示只執行一遍 {} 內的語句,表象來說與 {} 的功能是一致的。不同的是,do{...}while(0) 可以提前退出函數宏、整合為一條語句與強制調用時必須使用 ;。

由於 do{...}while(0) 實際為 while 循環,因此可以使用關鍵字 break 提前結束循環。利用該特性,可以為函數宏添加參數檢測。例如:

#define INT_SWAP(a,b) \do{ \if (a < 0 || b < 0) \break; \int tmp = a; \a = b; \b = tmp; \}while(0)

由於 do{...}while(0); 實際為一種語法,編譯器會把 do{...}while(0); 認為為一條語句。

因此,do{...}while(0) 方式的函數宏可以在無花括號且有分支的 if 語句中直接調用。例如:

#define INT_SWAP(a,b) \do{ \if (a < 0 || b < 0) \break; \int tmp = a; \a = b; \b = tmp; \}while(0)int main(){int var_a = 1;int var_b = 2;if (1)INT_SWAP(var_a, var_b);elseprintf("hello world!\n"); printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1return 0;}

C 語言規定,do{...}while(0) 語法必須使用 ; 作為語句結尾。因此不可能存在以下語句的程序出現:

if (1)INT_SWAP(var_a, var_b)else{printf("hello world!\n"); }

優缺點總結:

  • 優點:支持在無花括號且有分支的 if 語句中直接調用;支持提前退出函數宏;強制調用時必須使用 ;。
  • 缺點:無返回值,不能作為表達式的右值使用。


({}) 方式


({}) 為 GNU C 擴展的語法,非 C 語言的原生語法。

INT_SWAP 宏使用 ({}) 封裝後形態如下:

#define INT_SWAP(a,b) \({ \int tmp = a; \a = b; \b = tmp; \})

與 do{...}while(0) 相同,({}) 支持在無花括號且有分支的 if 語句中直接調用。例如:

#define INT_SWAP(a,b) \({ \int tmp = a; \a = b; \b = tmp; \})int main(){int var_a = 1;int var_b = 2;if (1)INT_SWAP(var_a, var_b);elseprintf("hello world!\n");printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1return 0;}

與 do{...}while(0) 不同的是,({}) 不能提前退出函數宏與支持返回值。({}) 畢竟不是 while 循環,不能直接使用 break退出函數宏是比較容易理解。那支持返回值是什麼意思呢?

答案是 C 語言規定 ({}) 中的最後一條語句的結果為該雙括號體的返回值。例如:

int main(){int a = ({10;1000;});printf("a = %d\n", a); // a = 1000}

因此,({}) 可以為函數宏提供返回值。例如:

#define INT_SWAP(a,b) \({ \int ret = 0; \if (a < 0 || b < 0) \{ \ret = -1; \} \else \{ \int tmp = a; \a = b; \b = tmp; \} \ret; \})int main(){int var_a = 1;int var_b = 2;if (INT_SWAP(var_a, var_b) != -1)printf("swap success !!\n"); // swap success !!elseprintf("swap fail !!\n"); printf("var_a = %d, var_b = %d\n", var_a, var_b); // var_a = 2, var_b = 1return 0;}

可見,此時的 INT_SWAP 宏已與函數十分接近。

優缺點總結:

  • 優點:支持在無花括號且有分支的 if 語句中直接調用;有返回值,支持作為表達式的右值。
  • 缺點:不支持提前退出函數宏;非 C 的原生語法,編譯器可能不支持。


總結

綜上,在 {}、do{...}while(0) 和 ({}) 這三種函數宏的封裝方式之中,應儘可能不使用 {},考慮兼容性一般選擇使用 do{...}while(0),當需要函數宏返回時可以考慮使用 ({}) 或直接定義函數。

來源:https://blog.csdn.net/qq_35692077/article/details/102994959

免責聲明:本文來源網絡,歸原作者所有,侵聯刪。


想要了解C語言更多知識,點擊下方【了解更多】,與志同道合的小夥伴一起學習~

相關焦點

  • C語言中幾種特殊標準定義和用法
    除了大家說的PHP,其實,C語言也是世界上最好的語言。C語言已經連續幾個月佔比 TIOBE 榜首,成為最受歡迎的程式語言了。那麼,今年我們就來了解一下C語言的一些特殊功能。__FUNCTION__ :函數名,類型為:字符常量指針。
  • 那些主流程式語言的知識,C語言
    大家最好不要停留在語言層面去爭執,不如把時間花在計算機實現原理和結構的本質上,這樣更能理解程式語言每一行描述的東西在計算機是幹什麼的。本系列將總結現在IT領域主流的那些程式語言的相關知識。提到C首先必然會讓人關聯到指針,當年在大學讓你困惑的指針卻是C語言威力無窮的基礎。C語言可能從更高層面的設計和編寫效率上有所欠缺,但卻足夠經典且容易操控底層。指針雖然風險不小,但卻十分強大。此外ANSI C也增強了C程序在不同作業系統的遷移性,下面列一些C語言的一些基礎知識。
  • C語言陷阱與技巧第7節,define函數式宏定義不能用普通函數代替嗎...
    上一節基於 usleep() 函數,使用若干行代碼,簡單實現了用於避免C語言程序陷入死循環的「超時」功能,並且為了方便之後的調用,我們還使用了 define 宏定義將「超時」代碼封裝成一個方法。相信讀者已經發現 C語言中的 define 宏定義的強大了,它遠遠不止僅提供常數替換的功能。
  • C語言基礎知識
    C 傳遞指針給函數通過傳遞指針給函數,可以直接修改原參數(實參),而不是引用實參到形參。pow(x, y)函數C 和 Python 語言的 pow(x, y) 方法都是用於返回 (x 的 y 次方) 的值,C 語言中其原型為:double pow(double x, double y)。
  • STM32 嵌入式學習入門(0)——C語言基礎複習
    本文並沒有將相關C語言知識點介紹地很詳細,畢竟這麼多知識點要想掌握絕對不是看幾篇文檔就能掌握的。因此博主建議,如果上述的C語言知識掌握得還不是很好的話,找一本C語言的書好好研究研究。尤其是結構體和結構體指針、還有函數的知識(本文沒提到),一定要很熟練。
  • 自學單片機第八篇:基礎知識——C語言基礎
    對於剛學計算機編程的同學來說,每一個編程知識都覺得很重要,下面小編為大家整理了相關大學C語言必背基礎知識,希望大家喜歡。C語言基礎知識大全C語言程序的結構認識用一個簡單的c程序例子,介紹c語言的基本構成、格式、以及良好的書寫風格,使小夥伴對c語言有個初步認識。
  • 程式設計師求職的一些經驗和需要掌握的基礎知識(C、C+方向程式設計師必看)
    軟體公司分幾種: 1、純自主研髮型軟體公司,這樣的公司國內有很多,規模大小不一,例如騰訊,暴風,金山。 以上就是大概的幾種軟體公司,由於是剛畢業的應屆生,沒有實際的項目經驗,編程知識也不夠豐富,我本人不推薦去外派公司。 無論你想去哪種軟體公司,前提都是你必須要有一個很好的編程基礎,有技術做支持。 當你在招聘網上投簡歷,並接到了面試通知,說明你的簡歷合格了。恭喜你艱難的第一步你邁出了。
  • C語言陷阱與技巧第18節,函數式宏定義的「缺陷」,沒有參數類型檢查...
    在之前的文章裡,我們曾討論C語言程序開發中 define 宏定義的「陷阱」之一就是可能會產生多次「副作用」,這也是C語言中函數式宏定義與真正函數的主要區別之一。顯然,define 宏定義的這種「陷阱」會導致程序存在隱患,而且這種隱患造成的危害不亞於「野指針」。
  • C語言數學庫的3種類型
    當然,C庫還提供了atan2()函數。它接受兩個參數:x的值和y的值。這樣,通過檢查x和y的正負號就可以得出正確的角度值。atan2()和atan()均返回弧度值。把弧度轉換為度,只需將弧度值乘以180,再除以pi即可。pi的值通過計算表達式4*atan(1)得到。程序rectpol.c演示了這些步驟。另外,學習該程序還複習了結構和typedef相關的知識。
  • 學員問:C語言入門要掌握哪些基礎知識?
    01C語言程序的結構認識用一個簡單的c程序例子,介紹c語言的基本構成、格式、以及良好的書寫風格,使小夥伴對c語言有個初步認識。2、main()——在c語言中稱之為「主函數」,一個c程序有且僅有一個main函數,任何一個c程序總是從main函數開始執行,main函數後面的一對圓括號不能省略。3、被大括號{ }括起來的內容稱為main函數的函數體,這部分內容就是計算機要執行的內容。
  • C語言編程核心要點
    類型C是強類型語言,有short、long、int、char、float、double等build-in數據類型,類型是貫穿c語言整個課程的核心概念。函數函數封裝行為,是模塊化的最小單元,函數使得邏輯復用變得可能。C是過程式的,現實世界都可以封裝為一個個過程(函數),通過過程串聯和編排模擬世界。用C編程,行為和數據是分離的。
  • C/C++學習日記:C語言宏定義 連接符和 符的使用
    前言:C語言中如何使用宏C(和C++)中的宏(Macro)屬於編譯器預處理的範疇,屬於編譯期概念(而非運行期概念)。下面對常遇到的宏的使用問題做了簡單總結。關於#和##在C語言的宏中,#的功能是將其後面的宏參數進行字符串化操作(Stringfication),簡單說就是在對它所引用的宏變量通過替換後在其左右各加上一個雙引號。
  • C語言基礎知識學習(四)
    C語言知識點補充關於'\0'與'0'與0與NULL(1)'\0'是字符串的結束標誌其ASCII值為0.(如果字符串中有'\0'那麼此處就是字符串的結束位置,如果沒有結尾默認'\0')(2)'0'是單個的數字字符ASCII值為48.(3) 0是數字0.(4) NULL是宏定義的0.
  • C語言基礎知識學習(二)
    函數C語言函數說明1. 一個C程序的基本結構包括預處理部分和函數部分;2. 函數是C語言的基本單位;3. C語言程序開始於主函數,結束於主函數;4. C語言中沒有輸入輸出語句但有輸入輸出函數.2.二維數組初始化int array[3][2] = {{a,b},{c,d},{e,f}};int array[3][2] = {a,b,c,d,e,f};int array[][2] = {a,b,c,d,e,f};for (i = 0; i < 3; i++){for (j = 0; j &
  • C語言知識梳理
    12.1.生存周期劃分存儲方式C語言根據變量的生存周期來劃分,可以分為靜態存儲方式和動態存儲方式。靜態存儲方式:是指在程序運行期間分配固定的存儲空間的方式。靜態存儲區中存放了在整個程序執行過程中都存在的變量,如全局變量。動態存儲方式:是指在程序運行期間根據需要進行動態的分配存儲空間的方式。
  • C語言列印函數硬核玩法?看這文章,C語言難學的感覺又增加了
    C語言可變參數函數C 語言允許定義參數數量可變的函數,這稱為可變參數函數。這種函數需要固定數量的強制參數,後面是數量可變的可選參數。這種函數必須至少有一個強制參數。可選參數的類型可以變化。可選參數的數量由強制參數的值決定,或由用來定義可選參數列表的特殊值決定。C 語言中最常用的可變參數函數例子是 printf()和 scanf()。這兩個函數都有一個強制參數,即格式化字符串。格式化字符串中的轉換修飾符決定了可選參數的數量和類型。對於每一個強制參數來說,函數頭部都會顯示一個適當的參數,像普通函數聲明一樣。參數列表的格式是強制性參數在前,後面跟著一個逗號和省略號(...)
  • 最全的C語言基礎知識都在這了
    C語言是一門面向過程、抽象化的通用程序設計語言,廣泛應用於底層開發。C語言能以簡易的方式編譯、處理低級存儲器。C語言是僅產生少量的機器語言以及不需要任何運行環境支持便能運行的高效率程序設計語言。儘管C語言提供了許多低級處理的功能,但仍然保持著跨平臺的特性,以一個標準規格寫出的C語言程序可在包括一些類似嵌入式處理器以及超級計算機等作業平臺的許多計算機平臺上進行編譯。
  • C語言基礎知識學習(一)
    C程序基礎1. 標識符在程序中使用的變量名、函數名、數組名、指針名、標號等稱為標識符.標識符分類a) 關鍵字原先的C89標準中只有32個關鍵字然後在1999年之後進行了兩次技術修正,新出臺了C99標準新增加了5個關鍵字 分別是:restrict, inline, _Complex, _Imaginary, _Boolb) 預定義標識符包括C語言提供的庫函數、預編譯處理命令。
  • C語言入門基礎整理
    學習計算機技術,C語言可以說是必備的,他已經成為現在計算機行業人學習必備的,而且應用也是十分的廣泛,今天就來看看擁有幾年c語言工作經驗的大神整理的C語言入門基礎知識,沒有學不會,只有不肯學。「%c」字符串常量:雙撇號括起來的一系列字符序列。算法結構:一、順序結構、選擇結構、循環結構;二、循環結構又分為while型、until型、for循環結構;程序流程圖;控制語句:完成一定的控制功能。
  • 快速上手系列-C語言之預編譯命令、宏定義及條件編譯
    上一篇寫了C語言中變量的存儲類別,提到普通局部變量、普通全局變量和靜態局部變量及靜態全局變量,這裡簡單了解一下C語言的預編譯命令、宏定義和條件編譯。.c所在的目錄找file2.c,如果找不到,再按系統指定的目錄檢索。