1. 程式人生 > >C++預處理、巨集

C++預處理、巨集

一、C++編譯過程

從C++原始檔到可執行檔案的編譯過程,有如下幾個步驟,g++提供了很多編譯選項,可以讓我們控制整個編譯過程:

  • 預處理(g++選項 -E):預處理就是本文要詳細說的巨集替換、標頭檔案包含等,結果直接輸出到控制檯
  • 編譯 (g++選項 -S):編譯是指對預處理後的程式碼進行語法和語義分析,最終得到彙編程式碼或接近彙編的其他中間程式碼,結果儲存為.s檔案
  • 彙編 (g++選項 -c):彙編是指將上一步得到的彙編或中間程式碼轉換為目標機器的二進位制指令,一般是每個原始檔生成一個二進位制檔案(VS是.obj,GCC是.o)
  • 連結 (g++選項 空) :連結是對上一步得到的多個二進位制檔案“連結”成可執行檔案或庫檔案等,連結後結果就是可執行檔案了

使用這些選項,我們就可以讓編譯在某一步結束之後停下來,輸出那一步的結果。

二、預處理功能

前處理器是在程式原始檔被編譯之前根據預處理指令對程式原始檔進行處理的程式。前處理器指令以#號開頭標識,末尾不包含分號。預處理命令不是C/C++語言本身的組成部分,不能直接對它們進行編譯和連結。

最常見的預處理有:原始檔包含,巨集替換,條件編譯、編譯器預留指令4種。 

  1. 原始檔包含: #include 是一種最為常見的預處理,主要是做為檔案的引用組合源程式正文。 
  2. 巨集替換: #define,這是最常見的用法,它可以定義符號常量、函式功能、重新命名、字串的拼接等各種功能。
  3. 條件編譯: #if,#ifndef,#ifdef,#endif,#undef等也是比較常見的預處理,主要是進行編譯時進行有選擇的挑選,註釋掉一些指定的程式碼,以達到版本控制、防止對檔案重複包含的功能。 
  4. 編輯器預留指令: #progma,這也是我們應用預處理的一個重要方面,主要功能是為編譯程式提供非常規的控制流資訊。 
  5. 重定義行號和檔名: #line,
  6. 錯誤資訊: #error,

1、原始檔包含 #include

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

2、巨集替換 #define

現代的C++程式設計原則不推薦適用巨集定義常量或函式巨集,應該儘量少的使用 #define ,如果可能,用 const 變數或 inline 函式代替。

預定義巨集

  • __DATE__,字串常量型別,表示當前所在原始檔的編譯日期,輸出格式為Mmm dd yyyy(如May 27 2006)。
  • __TIME__,字串常量型別,表示當前所在原始檔的編譯日期,輸出格式為hh:mm:ss(如09:11:10)。
  • __FILE__,字串常量型別,表示當前所在原始檔名,且包含檔案路徑。
  • __LINE__,整數常量型別,表示當前所在原始檔中的行號。
  • __FUNCTION__,字串常量型別,表示當前所在函式名。
這些預定義巨集在除錯程式時是很有用的,用於輸出除錯資訊。
int main()
{
	#define PRINT(arg) std::cout << #arg": " << arg << '\n'
	PRINT(__cplusplus);
	PRINT(__LINE__);
	PRINT(__FILE__);
	PRINT(__DATE__);
	PRINT(__TIME__);
	#ifdef __STDC__
     		PRINT(__STDC__);
	#endif
}
註釋:預定義巨集一般以“__”作為字首,所以使用者自定義巨集應該避開“__”開頭。

3、條件編譯指令

       一般情況下,在進行編譯時對源程式中的每一行都要編譯,但是有時希望程式中某一部分內容只在滿足一定條件時才進行編譯,如果不滿足這個條件,就不編譯這部分內容,這就是條件編譯。條件編譯主要是進行編譯時進行有選擇的挑選,註釋掉一些指定的程式碼,以達到多個版本控制、防止對檔案重複包含的功能。

常用形式:

#if_#endif形式:
       #if expression 或 #ifdef identifier 或 #ifndef identifier
          your code
       #endif
#if_#else_#endif形式:
       #if expression 或 #ifdef identifier 或 #ifndef identifier
          your code
       #else
           your code
       #endif
#if_#elif_#endif形式:
       #if expression1
         your code1
       #elif expression2
          your code2
       #elif expression3
          your code3
       #endif
注意這種形式#elif不可以用於#ifdef和#ifndef中,但#else可以。

4、編譯器預留指令 #progma

作用是設定編譯器的狀態或指示編譯器完成一些特定的動作。
#pragma一般形式為#pragma para,其中para為引數,下面介紹一些常用的引數。
  1. #pragma once,只要在標頭檔案的最開始加入這條指令就能夠保證標頭檔案被編譯一次。
  2. #pragma message("info"),在編譯資訊輸出視窗中輸出相應的資訊,例如#pragma message("Hello")。
  3. #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號警告資訊作為一個錯誤)。
  4. #pragma comment(…),設定一個註釋記錄到物件檔案或者可執行檔案中。常用lib註釋型別,用來將一個庫檔案連結到目標檔案中,一般形式為#pragma comment(lib,"*.lib"),其作用與在專案屬性連結器“附加依賴項”中輸入庫檔案的效果相同。

5、錯誤資訊 #error

  • 作用是在程式崩潰之前能夠給出錯誤資訊。
  • #error語法:#error   info
例如:     #ifndef UNIX 
         #error This software requires the UNIX OS. 
     #endif