詳解C/C++前置處理器 .

來源:互聯網
上載者:User
C/C++編譯系統編譯器的過程為預先處理、編譯、連結。前置處理器是在程式源檔案被編譯之前根據預先處理指令對程式源檔案進行處理的程式。前置處理器指令以#號開頭標識,末尾不包含分號。預先處理命令不是C/C++語言本身的組成部分,不能直接對它們進行編譯和連結。C/C++語言的一個重要功能是可以使用預先處理指令和具有預先處理的功能。C/C++提供的預先處理功能主要有檔案包含、宏替換、條件編譯等。

       1、檔案包含
       預先處理指令#include用於包含標頭檔,有兩種形式:#include <xxx.h>,#include "xxx.h"。
角括弧形式表示被包含的檔案在系統目錄中。如果被包含的檔案不一定在系統目錄中,應該用雙引號形式。
在雙引號形式中可以指出檔案路徑和檔案名稱。如果在雙引號中沒有給出絕對路徑,則預設為使用者目前的目錄中的檔案,此時系統首先在使用者目前的目錄中尋找要包含的檔案,若找不到再在系統目錄中尋找。
對於使用者自己編寫的標頭檔,宜用雙引號形式。對於系統提供的標頭檔,既可以用角括弧形式,也可以用雙引號形式,都能找到被包含的檔案,但顯然用角括弧形式更直截了當,效率更高。
./表示目前的目錄,../表示目前的目錄的父目錄。

       2、宏替換
       ① 宏定義
       宏定義的作用一般是用一個短的名字代表一個長的代碼序列。宏定義包括無參數宏定義和帶參數宏定義兩類。宏名和宏參數所代表的代碼序列可以是任何意義的內容,如類型、常量、變數、操作符、運算式、語句、函數、代碼塊等。但要尤其注意的是宏名和宏參數必須是合法的標識符,其所代表的內容及意義在宏展開前後必須一直是獨立且保持不變的,不能分開解釋和執行。
       無參數宏定義。用一個使用者指定的稱為宏名的標識符來代表一個代碼序列,這種定義的一般形式為#define 標識符 代碼序列。其中#define之後的標識符稱為宏定義名(簡稱宏名),在宏定義#define之前可以有若干個空格、定位字元,但不允許有其它字元,宏名與代碼序列之間用空格符分隔。
帶參數宏定義。帶參數宏定義進一步擴充了無參數宏定義的能力,這時的宏展開既進行宏名的替換又進行宏參數的替換。帶參數的宏定義的一般形式為#define 標識符(參數表) 代碼序列,其中參數表中的參數之間用逗號分隔,在代碼序列中必須要包含參數表中的的參數。在定義帶參數的宏時,宏名與左圓括弧之間不允許有空白符,應緊接在一起,否則變成了無參數的宏定義。帶參數宏調用提供的實在參數個數必須與宏定義中的形式參數個數相同。
宏定義的有效範圍稱為宏名的範圍,宏名的範圍從宏定義的結束處開始到其所在的原始碼檔案末尾。宏名的範圍不受分程式結構的影響。如果需要終止宏名的範圍,可以用預先處理指令#undef加上宏名。
宏名一般用大寫字母,以便與變數名區別。如有必要,宏名可被重複定義,被重複定義後,宏名原先的意義被新意義所代替。
       宏定義代碼序列中必須把""配對,不能把字串""拆開。例如#define NAME "vrmozart不合法,應為#define NAME "vrmozart"。
       宏定義代碼序列中可以引用已經定義的宏名,即宏定義可以嵌套。
       ② 多行宏
       宏定義在源檔案中必須單獨另起一行,分行符號是宏定義的結束標誌,因此宏定義以換行結束,不需要分號等符號作分隔字元。如果一個宏定義中代碼序列太長,一行不夠時,可採用續行的方法。續行是在鍵入斷行符號符之前先鍵入符號\,注意斷行符號要緊接在符號\之後,中間不能插入其它符號,當然代碼序列最後一行結束時不能有\。注意多行宏在調用時只能單獨一行調用,不能用在運算式中或作為函數參數。
       ③ 宏展開
       前置處理器在處理宏定義時,會對宏進行展開(即宏替換)。宏替換首先將源檔案中在宏定義隨後所有出現的宏名均用其所代表的代碼序列替換之,如果是帶參數宏則接著將代碼序列中的宏形參名替換為宏實參名。宏替換隻作代碼字元序列的替換工作,不作任何文法的檢查,也不作任何的中間計算,一切其它操作都要在替換完後才能進行。如果宏定義不當,錯誤要到預先處理之後的編譯階段才能發現。
原始碼中的宏名和宏定義代碼序列中的宏形參名必須是標識符才會被替換,即只替換標識符,不替換別的東西,像注釋、字串常量以及標識符內出現的宏名或宏形參名則不會被替換。例如:
       #define NAME vrmozart,原始碼//NAME、/*NAME*/、"NAME"、my_NAME_blog中的宏名NAME都不會被替換。
       #define BLOG(name) my_name_blog="name",宏定義代碼序列中的宏形參名name也都不會被替換。
       如果希望宏定義代碼序列中標識符內出現的宏形參名能夠被替換,可以在宏形參名與標識符之間添加串連符##,在宏替換過程中宏形參名和串連符##一起將被替換為宏實參名。##用於把宏參數名與宏定義代碼序列中的標識符串連在一起,形成一個新的標識符。例如:
       #define BLOG(name) my_##name,BLOG(vrmozart)表示my_vrmozart
       #define BLOG(name) name##_ blog,BLOG(vrmozart)表示vrmozart_ blog
       #define BLOG(name) my_##name##_blog,BLOG(vrmozart)表示my_vrmozart_ blog
       如果希望宏定義代碼序列中的宏形參名被替換為宏實參名的字串形式(即在宏實參名兩端加雙引號"),而不是替換為宏實參名,可以在宏定義代碼序列中的宏形參名前面添加符號#。#用於把宏參數名變為一個字串形式。例如:
       #define STR(name) #vrmozart,STR(vrmozart)表示"vrmozart"
       當宏參數是另一個宏的時候,需要注意的是宏定義代碼序列中有用#或##的宏參數是不會再展開。
       ④ 宏的獨立性
       在宏定義中說過,宏名和宏形參名所代表的內容及意義在宏展開前後必須一直是獨立且保持不變的,不能分開解釋和執行。其原因如下,在宏調用時,用宏定義的代碼序列替換宏名,用宏實參名替換宏形參名。替換後,宏定義的代碼序列就與源檔案中相鄰的代碼自然串連,宏實參名也與代碼序列中相鄰的代碼自然串連,宏定義的代碼序列和宏實參名的獨立性就不一定依舊存在。例如:
       #define SQR(x) x*x,希望實現運算式的平方計算。
       對於宏調用p=SQR(y),能得到希望的宏展開p=y*y。但對於宏調用q=SQR(u+v),得到的宏展開是q=u+v*u+v。顯然,後者的展開結果不是程式設計者所希望的。為能保持宏實參名替換後的獨立性,應在宏定義中給形式參數加上括弧。進一步,為了保證宏名調用的獨立性,作為算式的宏定義代碼序列也應加括弧。SQR宏定義改寫成#define SQR(x) ((x)*(x))才是正確的宏定義。
       ⑤ 宏調用與函數調用的區別
       函數調用在程式運行時實行,而宏展開是在編譯的預先處理階段進行;函數調用佔用程式已耗用時間,宏調用只佔編譯時間;函數調用對實參有類型要求,而宏調用實在參數與宏定義形式參數之間沒有類型的概念,只有字元序列的對應關係;函數調用可返回一個值,宏調用獲得希望的代碼序列。另外,函數調用時,實參運算式分別獨立求值在前,執行函數體在後。宏調用是實在參數字元序列替換形式參數。
       ⑥ 預定義宏
       __DATE__,字串常量類型,表示當前所在源檔案的編譯日期,輸出格式為Mmm dd yyyy(如May 27 2006)。
       __TIME__,字串常量類型,表示當前所在源檔案的編譯日期,輸出格式為hh:mm:ss(如09:11:10)。
       __FILE__,字串常量類型,表示當前所在源檔案名稱,且包含檔案路徑。
       __LINE__,整數常量類型,表示當前所在源檔案中的行號。
       __FUNCTION__,字串常量類型,表示當前所在函數名。
       這些預定義宏在偵錯工具時是很有用的,因為你可以很容易的知道程式運行到了那個檔案的那一行,是那個函數。
       使用者除了可以在源檔案的開頭使用#define定義宏外,還可在編譯器項目屬性“前置處理器”屬性頁面定義宏。這種宏定義方式支援數字和字串,一般形式為:標識符=數字或字串常量,如果省略=以及後面的內容,則宏名標識符預設為整數1。定義宏的方法是在“前置處理器定義”屬性輸入宏定義內容,多個宏定義之間用分號隔開。“前置處理器定義”中的宏定義要先於源檔案中的宏定義被處理,其有效範圍為整個項目,除非在源檔案中遇到重定義或用 #undef 指定取消宏定義名,否則該宏定義名在源檔案中一直保持有效。

       3、條件編譯指令
       一般情況下,在進行編譯時間對來源程式中的每一行都要編譯,但是有時希望程式中某一部分內容只在滿足一定條件時才進行編譯,如果不滿足這個條件,就不編譯這部分內容,這就是條件編譯。條件編譯主要是進行編譯時間進行有選擇的挑選,注釋掉一些指定的代碼,以達到多個版本控制、防止對檔案重複包含的功能。#if,#ifndef,#ifdef,#else,#elif,#endif是比較常見條件編譯預先處理指令,可根據運算式的值或某個特定宏是否被定義來確定編譯條件。
       ① 指令意義
       #if 運算式非零就對代碼進行編譯;
       #ifdef 如果宏被定義就進行編譯;
       #ifndef 如果宏未被定義就進行編譯;
       #else 作為其它預先處理的剩餘選項進行編譯;
       #elif 這是一種#else和#if的組合選項;
       #endif 結束編譯塊的控制。
       ② 常用形式
       #if_#endif形式:
       #if 常數運算式 或 #ifdef 宏名 或 #ifndef 宏名
          程式段
       #endif
       如果常數運算式為真或者該宏名已定義或者該宏名未定義,則編譯後面的程式段;否則就不編譯,跳過這段程式。
       #if_#else_#endif形式:
       #if 常量運算式 或 #ifdef 宏名 或 #ifndef 宏名
          程式段1
       #else
           程式段2
       #endif
       如果常數運算式為真或者該宏名已定義或者該宏名未定義,則編譯後面的程式段1;否則編譯後面的程式段2。
       #if_#elif_#endif形式:
       #if 常量運算式1
         程式段1
       #elif 常量運算式2
          程式段2
         .......
       #elif 常量運算式n
          程式段n
       #endif
       注意這種形式#elif不可以用於#ifdef和#ifndef中,但#else可以。
       ③ 運算式
       前置處理器運算式包括的操作符主要涉及到單個數的操作(+、-、~、<<、>>)、多個數的運算(*、/、%、+、-、&、^、|)、關係比較(<、<=、>、>=、==、!=)、宏定義判斷(defined)、邏輯操作(!、&&、||),其優先順序和行為方式與C++運算式操作符相同。對於前置處理器運算式,一定要記住它們是在編譯器前置處理器上執行的,是在編譯前進行的。
       下表列出了操作符的優先順序順序,從上到下,優先順序從高到低。可以用圓括弧改變優先順序順序。

       例子:#ifndef 與#if !defined意義相同,#ifdef 與#if defined意義相同。

       4、其它預先處理指令
       除了上面討論的常用預先處理指令外,還有三個不太常見的預先處理指令:#line、#error、#pragma,下面分別介紹。
       ① #line
       #line指令用於重新設定當前由__FILE__和__LINE__宏指定的源檔案名稱字和行號。
       #line一般形式為#line number "filename",其中行號number為任何正整數,檔案名稱filename可選。#line主要用於調試及其它特殊應用,注意在#line後面指定的行號數字是表示從下一行開始的行號。
       ② #error
       #error指令使前置處理器發出一條錯誤訊息,然後停止執行預先處理。
       #error 一般形式為#error info,如#error MFC requires C++ compilation。
       ③ #pragma
       #pragma指令可能是最複雜的預先處理指令,它的作用是設定編譯器的狀態或指示編譯器完成一些特定的動作。
       #pragma一般形式為#pragma para,其中para為參數,下面介紹一些常用的參數。
       #pragma once,只要在標頭檔的最開始加入這條指令就能夠保證標頭檔被編譯一次。
       #pragma message("info"),在編譯資訊輸出視窗中輸出相應的資訊,例如#pragma message("Hello")。
       #pragma warning,設定編譯器處理編譯警告資訊的方式,例如#pragma warning(disable:4507 34;once : 4385;error:164)等價於#pragma warning(disable:4507 34)(不顯示4507和34號警告資訊)、#pragma warning(once:4385)(4385號警告資訊僅報告一次)、#pragma warning(error:164)(把164號警告資訊作為一個錯誤)。
       #pragma comment(…),設定一個注釋記錄到對象檔案或者可執行檔中。常用lib注釋類型,用來將一個庫檔案連結到目標檔案中,一般形式為#pragma comment(lib,"*.lib"),其作用與在項目屬性連結器“附加依賴項”中輸入庫檔案的效果相同。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.