C/C++宏的使用總結

?宏替換是C/C++系列語言的技術特色,C/C++語言提供了強大的宏替換功能,源代碼在進入編譯器之前,要先經過一個稱為“預處理器”的模塊,這個模塊將宏根據編譯參數和實際編碼進行展開,展開后的代碼才正式進入編譯器,進行詞法分析、語法分析等等。

??? 我們常用的宏替換主要有這么幾個類型。
1.宏常量
??? 在ACM等算法競賽中,經常會把數組的最大下標通過宏定義的方法給出,以方便調試,例如:
#define MAX 1000

int array[MAX][MAX]
......

for(int i = 0; i < MAX; i++)
......

??? 將一個數字定義成全局的常量,這個用法在國產垃圾教材上十分常見。但在經典著作《Effective C++》中,這種做法卻并不提倡,書中更加推薦以const常量來代替宏常量。因為在進行詞法分析時,宏的引用已經被其實際內容替換,因此宏名不會出現在符號表中。所以一旦出錯,看到的將是一個無意義的數字,比如上文中的1000,而不是一個有意義的名稱,如上文中的MAX。而const在符號表中會有自己的位置,因此出錯時可以看到更加有意義的錯誤提示。

2.用于條件編譯標識的宏
#define常與#ifdef/#ifndef/defined指令配合使用,用于條件編譯。
#ifndef _HEADER_INC_
#define _HEADER_INC_
……
……
#endif
??? 這種宏標記在頭文件中十分常見,用于防止頭文件被反復包含。應該養成習慣在每個頭文件中都添加這種標記。
??? 還有一種用于條件編譯的用法
#ifdef DEBUG
printf("{“}Debug information\n");
#endif
??? 通過DEBUG宏,我們可以在代碼調試的過程中輸出輔助調試的信息。當DEBUG宏被刪除時,這些輸出的語句就不會被編譯。更重要的是,這個宏可以通過編譯參數來定義。因此通過改變編譯參數,就可以方便的添加和取消這個宏的定義,從而改變代碼條件編譯的結果。
?
在條件編譯時建議使用#if defined和#if !defined來代替使用#ifdef/#ifndef,因為前者更方便處理多分支的情況與較復雜條件表達式的情況。#ifdef/#ifndef只能處理兩個分支:#ifdef/#ifndef,#else,#endfi;#if defined和#if !defined可以處理多分支的情況:#if defined/#if !defined, #elif defined, #else, #endif。#ifdef只能判斷是否定義,但是#if defined可以判斷復雜的表達式的值是否為真。
#if defined(OS_HPUX)&&(defined(HPUX_11_11)|| defined(HPUX_11_23)?
// for HP-UX 11.11 and 11.23?
#elif defined(OS_HPUX) && defined(HPUX_11_31?
// for HP-UX 11.31?
#elif defined(OS_AIX)?
// for AIX?
#else?
…?
#endif

條件編譯時,如果一個文件中太多條件編譯的代碼,有些編輯器的智能感知可能都不能很好地解析,還是保持代碼越簡單越好。對于函數級別的條件編譯主要有兩種實現方式:?
(1) 同一個函數聲明,同一個函數定義,函數體內使用條件編譯代碼。這種方式有個問題,如果條件編譯代碼太多,會導致這個函數體很長,不利于閱讀與維護;有一個優點是,有利于編輯器的智能感知,因為這樣解析函數名比較方便,但隨著編輯器功能的完善,這方面的差別就不明顯了。
(2) 根據編譯條件,將編譯條件相同的代碼放到單獨的文件中,這些文件在頂層文件中使用條件編譯指令來引用。這種方式最大的優點就是不同平臺的程序由不同的源文件來實現,很便于多人分工合作,對于某一部分代碼由一個人實現并測試完成后直接把源文件復制過來就可以了,進行低層次的單元測試非常方便;它的缺點就是增加了目錄中的文件數量。

3.宏函數
??? 宏函數的語法有以下特點:
??? (1)、如果需要換行,則行末要加反斜杠“\”表示換行。宏函數體的最后一行不加反斜杠。
??? (2)、假設有參數ARGU,值為argu,則所有的ARGU被直接替換為argu,#ARGU被認為是字符串,會被替換成"argu"(帶引號)。
??? (3)、由于宏函數是直接替換,所有一般情況下對宏函數的調用時行末不需要加分號。
?
宏函數的作用:
1)、避免函數調用,提高程序效率
常用的就是最大值與最小值的判斷函數,由于函數內容并不多,如果定義為函數在調用比較頻繁的場合會明顯降低程序的效率,其實宏是用空間效率換取了時間效率。如取兩個值的最大值:?
#define MAX(a,b) ((a)<(b) ? (b) : (a))
定義為函數:?
inline int Max(int a, int b)
{
?return a<b ? b : a;
}
定義為模板:?
template <typename T>?
inline T TMax(T a, T b)
{
?return a < b ? b : a ;
}
使用宏函數的優點有兩個:
(1)適用于任何實現了operator<的類型,包括自定義類型;
(2)效率最高。雖然使用inline提示符也將函數或模板定義為內聯的,但這只是一種提示而已,到底編譯器有沒有優化還依賴于編譯器的實現,而使用宏函數則是完全由代碼本身控制。?
需要注意的是,由于宏的本質是直接的文本替換,所以在宏函數的“函數體”內都要把參數使用括號括起來,防止參數是表達式時造成語法錯誤或結果錯誤,如:
#define MIN( a, b) b < a ? b : a?
#define SUM( a, b) a + b?
cout<<MIN(3,5)<<endl;?// 語法錯誤:cout<<b < a ? b : a<<endl;?
int c = SUM(a,b)*2;??// c的期望值:16,實際值:13

2)、引用編譯期數據
上述的這些作用雖然使用宏函數可以取得更好的性能,但如果從功能上講完全可以不使用宏函數,而使用模板函數或普通函數實現,但還有些時候只能通過宏實現。例如,程序中在執行某些操作時可能會失敗,此時要打印出失敗的代碼位置,只能使用宏實現。?
#define SHOW_CODE_LOCATION() cout<<__FILE__<<':'<<__LINE__<<'\n'?
if( 0 != rename("oldFileName", "newFileName") )
{?
?cout<<"failed to move file"<<endl;?
?SHOW_CODE_LOCATION();?
}
雖然宏是簡單的替換,所以在調用宏函數SHOW_CODE_LOCATION時,分號可以直接寫到定義里,也可以寫到調用處,但最好還是寫到調用處,看起來更像是調用了函數,否則看著代碼不倫不類,如:
#define SHOW_CODE_LOCATION() cout<<__FILE__<<':'<<__LINE__<<'\n'?
if( 0 != rename("oldFileName", "newFileName") )
{?
?cout<<"failed to move file"<<endl;?
?SHOW_CODE_LOCATION()
}

3)、do-while的妙用
do-while循環控制語句的特點就是循環體內的語句至少會被執行一次,如果while(…)內的條件始終為0時,循環體內的語句就會被執行且只被執行一次,這樣的執行效果與直接使用循環體內的代碼相同,但這們會得到更多的益處。?
#define SWAP_INT(a, b) do
{\
?int tmp = a; \
?a = b; \
?b = tmp; \
}while(0)

int main( void )?
{?
?int x = 3, y = 4;
?if( x > y )
?{
??SWAP_INT(x, y);
?}
?return 0;
}
通過do-while代碼塊的宏定義我們不僅可以把SWAP_INT像函數一樣用,而且還有優點:?
(1)、在宏定義中可以使用局部變量;?
(2)、在宏定義中可以包含多個語句,但可以當作一條語句使用,如代碼中的if分支語句,如果沒有do-while把多條語句組織成一個代碼塊,則程序的運行結果就不正確,甚至不能編譯。?
其實我們定義的SWAP_INT(a, b)相當于定義了引用參數或指針參數的函數,因為它可以改變實參的值。在C++0X中有了decltype關鍵詞,這種優勢就更顯示了,因為在宏中使用了局部變量必須確定變量的類型,所以這個宏只能用于交換int型的變量值,如果換作其它類型則還必須定義新的宏,如SWAP_FLOAT、SWAP_CHAR等,而通過decltype,我們就可以定義一個萬能的宏。?
#include <iostream>?
using namespace std;?
#define SWAP(a, b) do
{ \
?decltype(a) tmp = a; \
?a = b; \
?b = tmp; \
}while(0)

int main( void )?
{?
?int a = 1, b = 2;?
?float f1 = 1.1f, f2 = 2.2f;?
?SWAP(a, b);?
?SWAP(f1,f2);?
?return 0;?
}
通過宏實現的SWAP“函數”要比使用指針參數效率還要高,因為它連指針參數都不用傳遞而是使用直接代碼,對于一些效率要求比較明顯的場合,宏還是首選。

4、取消宏定義
#undef指令用于取消前面用#define定義的宏,取消后就可以重新定義宏。該指令用的并不多,因為過多的#undef會使代碼維護起來非常困難,一般也只用于配置文件中,用來清除一些#define的開關,保證宏定義的唯一性。
// config.h?
#undef HAS_OPEN_SSL?
#undef HAS_ZLIB?
#if defined(HAS_OPEN_SSL)?
…?
#endif?
#if defined(HAS_ZLIB)?
…?
#endif
將對該頭文件的引用放到所有代碼文件的第一行,就可以保證HAS_OPEN_SSL沒有被定義,即使是在編譯選項里定義過一宏,也會被#undef指令取消,這樣使得config.h就是唯一一處放置條件編譯開關的地方,更有利于維護。

5、注意事項
1)、普通宏定義
(1)宏名一般用大寫
(2)使用宏可提高程序的通用性和易讀性,減少不一致性,減少輸入錯誤和便于修改。
(3)預處理是在編譯之前的處理,而編譯工作的任務之一就是語法檢查,預處理不做語法檢查。
(4)宏定義末尾不加分號;
(5)宏定義寫在函數的花括號外邊,作用域為其后的程序,通常在文件的最開頭。
(6)可以用#undef命令終止宏定義的作用域
(7)宏定義可以嵌套
(8)字符串""中永遠不包含宏
(9)宏定義不分配內存,變量定義分配內存。
2)、帶參宏定義
(1)實參如果是表達式容易出問題
(2)宏名和參數的括號間不能有空格
(3)宏替換只作替換,不做計算,不做表達式求解
(4)函數調用在編譯后程序運行時進行,并且分配內存。宏替換在編譯前進行,不分配內存
(5)宏的啞實結合不存在類型,也沒有類型轉換。
(6)函數只有一個返回值,利用宏則可以設法得到多個值
(7)宏展開使源程序變長,函數調用不會
(8)宏展開不占運行時間,只占編譯時間,函數調用占運行時間(分配內存、保留現場、值傳遞、返回值)

6、關于#和##
在C語言的宏中,#的功能是將其后面的宏參數進行字符串化操作(Stringfication),簡單說就是在對它所引用的宏變量通過替換后在其左右各加上一個雙引號。比如下面代碼中的宏:
#define WARN_IF(EXP)??? \
??? do{ if (EXP)??? \
??????????? fprintf(stderr, "Warning: " #EXP "\n"); }?? \
??? while(0)
那么實際使用中會出現下面所示的替換過程:
WARN_IF (divider == 0);

?被替換為

do {
??? if (divider == 0)
? fprintf(stderr, "Warning" "divider == 0" "\n");
} while(0);
這樣每次divider(除數)為0的時候便會在標準錯誤流上輸出一個提示信息。
而##被稱為連接符(concatenator),用來將兩個Token連接為一個Token。注意這里連接的對象是Token就行,而不一定是宏的變量。比如你要做一個菜單項命令名和函數指針組成的結構體的數組,并且希望在函數名和菜單項命令名之間有直觀的、名字上的關系。那么下面的代碼就非常實用:
struct command
{
?char * name;
?void (*function) (void);
};

#define COMMAND(NAME) { NAME, NAME ## _command }

// 然后你就用一些預先定義好的命令來方便的初始化一個command結構的數組了:

struct command commands[] = {
?COMMAND(quit),
?COMMAND(help),
?...
}
COMMAND宏在這里充當一個代碼生成器的作用,這樣可以在一定程度上減少代碼密度,間接地也可以減少不留心所造成的錯誤。我們還可以n個##符號連接 n+1個Token,這個特性也是#符號所不具備的。比如:
#define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d

typedef struct _record_type LINK_MULTIPLE(name,company,position,salary);
// 這里這個語句將展開為:
//? typedef struct _record_type name_company_position_salary;

7、關于...的使用
在C宏中稱為Variadic Macro,也就是變參宏。比如:
#define myprintf(templt,...) fprintf(stderr,templt,__VA_ARGS__)

?// 或者

#define myprintf(templt,args...) fprintf(stderr,templt,args)
第一個宏中由于沒有對變參起名,我們用默認的宏__VA_ARGS__來替代它。第二個宏中,我們顯式地命名變參為args,那么我們在宏定義中就可以用args來代指變參了。同C語言的stdcall一樣,變參必須作為參數表的最有一項出現。當上面的宏中我們只能提供第一個參數templt時,C標準要求我們必須寫成:
myprintf(templt,);
的形式。這時的替換過程為:
myprintf("Error!\n",);

?替換為:
?
fprintf(stderr,"Error!\n",);
這是一個語法錯誤,不能正常編譯。這個問題一般有兩個解決方法。首先,GNU CPP提供的解決方法允許上面的宏調用寫成:
myprintf(templt);
而它將會被通過替換變成:
fprintf(stderr,"Error!\n",);
很明顯,這里仍然會產生編譯錯誤(非本例的某些情況下不會產生編譯錯誤)。除了這種方式外,c99和GNU CPP都支持下面的宏定義方式:
#define myprintf(templt, ...) fprintf(stderr,templt, ##__VAR_ARGS__)
這時,##這個連接符號充當的作用就是當__VAR_ARGS__為空的時候,消除前面的那個逗號。那么此時的翻譯過程如下:
myprintf(templt);

?被轉化為:

fprintf(stderr,templt);
這樣如果templt合法,將不會產生編譯錯誤。 這里列出了一些宏使用中容易出錯的地方,以及合適的使用方式。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/458739.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/458739.shtml
英文地址,請注明出處:http://en.pswp.cn/news/458739.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Macosx 安裝 ionic 成功教程

2019獨角獸企業重金招聘Python工程師標準>>> 一、首先介紹一下ionic ionic是一個用來開發混合手機應用的&#xff0c;開源的&#xff0c;免費的代碼庫。可以優化html、css和js的性能&#xff0c;構建高效的應用程序&#xff0c;而且還可以用于構建Sass和AngularJS的…

hp g6服務器安裝系統,HPProLiantDL180G6服務器安裝圖.PDF

HPProLiantDL180G6服務器安裝圖4 前面板組件 / 25 個 2.5 英寸硬盤型號HP ProLiant DL180 G6 識別服務器組件2 光驅服務器 前面板組件 3 前部 UID LED 指示燈/開關4 系統運行狀況 LED 指示燈1 前面板組件/4 個 3.5 英寸硬盤型號 5 網卡 1 活動 LED 指示燈安裝圖 6 網卡 2 活動 …

九度OJ 1076:N的階乘 (數字特性、大數運算)

時間限制&#xff1a;3 秒 內存限制&#xff1a;128 兆 特殊判題&#xff1a;否 提交&#xff1a;6384 解決&#xff1a;2238 題目描述&#xff1a;輸入一個正整數N&#xff0c;輸出N的階乘。 輸入&#xff1a;正整數N(0<N<1000) 輸出&#xff1a;輸入可能包括多組數據&a…

Visual C++中 #include stdafx.h 頭文件的用法

今天在做VC實驗時&#xff0c;總是出現莫名其妙的錯誤。比如說&#xff1a; unexpected end of file whilelooking for precompiled header directive 再比如說這么一大串&#xff1a; mainframe.cpp 有錯誤\firstdlg.h(21) :error C2065: IDD_DIALOG_FIRST : undeclared ide…

mac顯示無法連接adobe服務器,Mac安裝Adobe軟件,如遇Error提示解決方法

Mac10.15.3 安裝Adobe Photoshop 2020的時候一直提示Error錯誤The installation cannot continue as the installer file may be damaged. Download the installer file again.看到這種問題&#xff0c;一般第一想法就是安裝包損壞了&#xff0c;本能的會再下載一遍甚至多遍&am…

android開發中EditText自動獲取焦點時隱藏hint的代碼

只需讓EditText設置以下的OnFocusChangeListener就可以了 private OnFocusChangeListener mOnFocusChangeListener new OnFocusChangeListener() {Overridepublic void onFocusChange(View v, boolean hasFocus){EditText textView (EditText)v;String hint;if (hasFocus) {h…

Grovvy初識

1.Groovy和Java對比 Groovy的松散的語法允許省略分號和修飾符除非另行指定&#xff0c;Grovvy的所有內容都為publicGrovvy允許定義簡單腳本&#xff0c;同時無需定義正規的class對象Grovvy在普通的常用java對象上增加了一些獨特的方法和快捷方式&#xff0c;使得他們更容易使用…

C和C++混合編程(__cplusplus使用)

第一種理解 比如說你用C開發了一個DLL庫&#xff0c;為了能夠讓C語言也能夠調用你的DLL輸出(Export)的函數&#xff0c;你需要用extern "C"來強制編譯器不要修改你的 函數名。 通常&#xff0c;在C語言的頭文件中經常可以看到類似下面這種形式的代碼&#xff1a; …

$.ajax 同步一不,ajax 同步不生效

可以用的生效代碼注意 boolean 的位置var baseUrl ${pageContext.request.contextPath };function formcheck(){var flag false;var customerNameaa;var countryaa;var citybeijing;$.ajax({type: POST,url:baseUrl "/exports/credit/findBuyersBySerach",data:{&…

iOS工程中創建pch文件

1.新建pch類文件 2.在工程配置中,Build Setting 下搜索"pre"尋找Apple LLVM6.1 - Language下的 Preflx Header 3.點開Preflx Header 把左邊pch類拖拽進去 4.把/"工程名"/....前邊的內容全部換為$(SRCROOT) (具體替換內容看報錯自己靈活運用)轉載于:https:/…

批處理中setlocal enabledelayedexpansion的作用詳細整理

設置本地為延遲擴展。其實也就是&#xff1a;延遲變量&#xff0c;全稱延遲環境變量擴展, 想進階&#xff0c;變量延遲是必過的一關&#xff01;所以這一部分希望你能認真看。 為了更好的說明問題&#xff0c;我們先引入一個例子。 例1: echo off set a4 set a5&echo…

一個服務器多個網站多個域名,多個域名一個服務器嗎

多個域名一個服務器嗎 內容精選換一換PAS(Primary Application Server)&#xff1a;主應用服務器。AAS(Additional Application Server)&#xff1a;擴展應用服務器。ASCS(ABAP Central Services)&#xff1a;SAP應用核心服務&#xff0c;是SAP應用的一個核心控件&#xff0c;包…

iframe 子父窗口互掉 js

一、父窗口調用iframe子窗口方法 1、HTML語法&#xff1a;<iframe name"myFrame" src"child.html"></iframe> 2、父窗口調用子窗口&#xff1a;myFrame.window.functionName(); 3、子窗品調用父窗口&#xff1a;parent.functionName(); 簡單地…

yii2 ajax分頁,Yii框架分頁技術實例分析

本文實例講述了Yii框架分頁技術。分享給大家供大家參考&#xff0c;具體如下&#xff1a;直接上代碼&#xff1a;1.首先寫控制器層先引用pagination類use yii\data\Pagination;寫自己的方法:function actionFenye(){$data Field::find(); //Field為model層,在控制器剛開始use了…

Spring源碼解析——如何閱讀源碼

閱讀目錄 下面看一下如何使用jar包以及源碼的source包  下面給出一個簡單的spring樣例  如何閱讀源碼最近沒什么實質性的工作&#xff0c;正好有點時間&#xff0c;就想學學別人的代碼。也看過一點源碼&#xff0c;算是有了點閱讀的經驗&#xff0c;于是下定決心看下spring…

c++多線程編程

一直對多線程編程這一塊很陌生&#xff0c;決定花一點時間整理一下。 os:ubuntu 10.04 c 1.最基礎&#xff0c;進程同時創建5個線程&#xff0c;各自調用同一個函數 [html] view plaincopy #include <iostream> #include <pthread.h> //多線程相關操作頭文件&am…

ajax當頁post請求,tag落地頁--通過ajax-post請求數據

查詢所有tag及其對應跳轉鏈接$tags get_tags(array(get>all));$output . ;if($tags) {foreach ($tags as $tag):$output . . $tag->name .;endforeach;} else {_e(No tags created., text-domain);}$output . ;echo $output;交互tag查詢image場景如下&#xff0c;通過頁…

GIT的PUSH指令

### GIT的PUSH指令 $ git push <遠程主機名> <本地分支名>:<遠程分支名> * git push命令用于將本地分支的更新&#xff0c;推送到遠程主機。 * 如果省略遠程分支名&#xff0c;則表示將本地分支推送到與之對應的遠程分支&#xff08;通常兩者同名&#xff…

Android 編程下 Touch 事件的分發和消費機制

Android 中與 Touch 事件相關的方法包括&#xff1a;dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev)&#xff1b;能夠響應這些方法的控件包括&#xff1a;ViewGroup、View、Activity。方法與控件的對應關系如下表所…

ios微信本地視頻上傳到服務器,ios本地視頻wx.uploadFile上傳

//上傳視頻uploadVideo:function(){let _this this;let list [camera, album];wx.showActionSheet({itemList: [拍攝視頻,從相冊選擇視頻,從視頻庫選擇視頻],success: function (res) {if(res.tapIndex0 || res.tapIndex1){wx.chooseVideo({sourceType:[list[res.tapIndex]],…