C++詳解預處理

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

顯然,后者的展開結果不是程序設計者所希望的。為能保持宏實參名替換后的獨立性,應在宏定義中給形式參數加上括號。進一步,為了保證宏名調用的獨立性,作為算式的宏定義代碼序列也應加括號。

#define SQR(x) ((x)*(x))  //正確的宏定義。
宏調用與函數調用的區別

函數調用在程序運行時實行,而宏展開是在編譯的預處理階段進行;
函數調用占用程序運行時間,宏調用只占編譯時間;
函數調用對實參有類型要求,而宏調用實在參數與宏定義形式參數之間沒有類型的概念,只有字符序列的對應關系;
函數調用可返回一個值,宏調用獲得希望的代碼序列。
另外,函數調用時,實參表達式分別獨立求值在前,執行函數體在后.宏調用是實在參數字符序列替換形式參數。

預定義宏

__DATE__,字符串常量類型,表示當前所在源文件的編譯日期,輸出格式為Mmm dd yyyy(如May 27 2006)。
__TIME__,字符串常量類型,表示當前所在源文件的編譯日期,輸出格式為hh:mm:ss(如09:11:10)。
__FILE__,字符串常量類型,表示當前所在源文件名,且包含文件路徑。
__LINE__,整數常量類型,表示當前所在源文件中的行號。
__FUNCTION__,字符串常量類型,表示當前所在函數名。
這些預定義宏在調試程序時是很有用的,因為你可以很容易的知道程序運行到了那個文件的那一行,是那個函數。
用戶除了可以在源文件的開頭使用#define定義宏外,還可在編譯器項目屬性“預處理器”屬性頁定義宏。這種宏定義方式支持數字和字符串,一般形式為:標識符=數字或字符串常量,如果省略=以及后面的內容,則宏名標識符默認為整數1。定義宏的方法是在“預處理器定義”屬性輸入宏定義內容,多個宏定義之間用分號隔開?!邦A處理器定義”中的宏定義要先于源文件中的宏定義被處理,其有效范圍為整個項目,除非在源文件中遇到重定義或用 #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;否則編譯后面的程序段

// #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"),其作用與在項目屬性鏈接器“附加依賴項”中輸入庫文件的效果相同。

文章摘自:http://blog.csdn.net/huang_xw/article/details/7648117

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 目錄 一.預處理的工作方式... 3 1.1.預處理的功能... 3 1.2預處理的工作方式... 3 二.預處理...
    朱森閱讀 1,401評論 0 2
  • C中的預編譯宏定義 2009-02-10 作者: infobillows 來源:網絡 在將一個C源程序轉換為可執行...
    白水灬煮一切閱讀 1,640評論 0 5
  • 日更的伙伴提議展望一下五年后的自己,主題挺有意義的,我積極響應。 五年的約定時間為2022年1月1日。 回顧過去五...
    山澗百合Y閱讀 453評論 3 2
  • 平時的螃蟹不是很貴,看上眼的也就20多元,也會打打饞蟲,十一遇到中秋節,螃蟹翻了一倍,還是那樣的螃蟹,對于有事要買...
    沉默雨佳閱讀 188評論 0 0
  • 她第一眼看到的,從不是我要分享的生活方式,而是我在哪里,做著什么 1, 前些天,公號里的直男直女們第一次決定,要找...
    Seven睡的很開心閱讀 735評論 0 1