轉自:http://www.cnblogs.com/pmars/archive/2012/10/17/2727626.htmlhtml
C++的預處理(Preprocess),是指在C++程序源代碼被編譯以前,由預處理器(Preprocessor)對C++程序源代碼進行的處理。這個過程並不對程序的源代碼進行解析,但它把源代分割或處理成爲特定的符號用來支持宏調調用。ios
1)經常使用的預處理:git
#include 包含頭文件 #if 條件 #else 不然 #elif 不然若是 #endif 結束條件 #ifdef 或 #if defined 若是定義了一個符號, 就執行操做 #ifndef 或 #if !defined 若是沒有定義一個符號,就指執行操做 #define 定義一個符號 #undef 刪除一個符號 #line 從新定義當前行號和文件名 #error 輸出編譯錯誤 消息, 中止編譯 #pragma 提供 機器專用的特性,同時保證與C++的徹底兼容
2)#include 在 程序中包含頭文件程序員
頭文件一般以.h結尾,其 內容可以使用#include預處理器指令包含到 程序中頭文件中通常包含: 函數原型 與 全局變量express
形式常有下面兩種ide
#include <iostream> #include "myheader.h"
前者<>用來引用標準庫頭文件,後者""經常使用來引用自定義的頭文件函數
前者<>編譯器只搜索包含標準庫頭文件的默認 目錄,後者首先搜索正在編譯的源文件所在的 目錄,找不到時再搜索包含標準庫頭文件的默認 目錄.佈局
若是把頭文件放在其餘 目錄下,爲了查找到它,必須在雙引號中指定從源文件到頭文件的完整路徑測試
3)#define 定義符號、宏spa
1>符號
#define PI 3.1415925 定義符號PI爲3.1415925 #undef PI 取消PI的值 這裏PI看起來像一個變量,但它與變量沒有任何關係,它只是一個符號或標誌,在 程序代碼編譯前,此符號會用一組指定的字符來代替 3.14159265 不是一個數值,只是一個字符串,不會進行檢查 在編譯前,預處理器會遍歷代碼,在它認爲置換有意義的地方,用字符串PI的定義值(3.14159265)來代替 在註釋或字符串中的PI不進行替換 在C中常以#define來定義符號常量,但在C++中最好使用const 來定義常量 #define PI 3.14159265 const long double PI=3.14159265; 二者比較下,前者沒有類型的指定容易引發沒必要須的麻煩,然後者定義清楚,因此在C++中推薦使用const來定義常量 #define 的缺點: 1)不支持類型檢查 2)不考慮做用域 3)符號名不能限制在一個命名 空間中
2>#undef 刪除#define定義的符號
define PI 3.14159265 ... //之間全部的PI均可以被替換爲3.14159265 #undef PI 以後再也不有PI這個標識符
3>定義宏
#define Print(Var) cout<<(Var)<<endl 用宏名中的參數帶入語句中的參數 宏後面沒有;號 Print(Var)中的Print和(之間不能有空格,不然(就會被解釋爲置換字符串的一部分 #define Print(Var, digits) cout << setw(digits) << (Var) << endl 調用 Print(ival, 15) 預處理器就會把它換成 cout << setw(15) << (ival) << endl; 全部的狀況下均可以使用內聯函數來代替宏,這樣能夠加強類型的檢查 template<class T> inline void Print (const T& var, const int& digits) { cout<<setw(digits)<<var<<endl; } 調用 Print(ival, 15); 使用宏時應注意的易引發的錯誤: #define max(x,y) x>y?x:y;+ 調用 result = max(myval, 99); 則換成 result = myval>99?myval:99; 這個沒有問題是正確的 調用 result = max(myval++, 99); 則換成 result = myval++>99?myval++:99; 這樣若是myval>99那麼myval就會遞增兩次,這種狀況下()是沒什麼用的如result=max((x),y)則 result = (myval++)>99?(myval++):99; 再如 #define product(m,n) m*n 調用 result = product(5+1,6);則替換爲result = 5+1*6; 因此產生了錯誤的結果,此時應使用()把參數括起 #define product(m,n) (m)*(n) 則result = product(5+1,6);則替換爲result = (5+1)*(6);
結論: 通常用內聯函數來代替預處理器宏
技巧:
1)給替換變量加引號
#define MYSTR "I love you" cout << MYSTR ; //I love you而不是"I love you" 若是 cout << "MYSTR" ; //則會輸出"MYSTR"而不是"I love you" 能夠這樣作 cout << #MYSTR ; //則會輸出 "I love you"即cout << "\"I love you\"";
2)在宏表達式中鏈接幾個參數
如 #define join(a,b) ab 這樣不會理解爲參數a的值與參數b的值的鏈接,即如join(10,999)不會理解爲10999而是把ab理解爲字符串,即輸出ab 這時能夠 #define join(a,b) a##b 則join(10,999)就會輸出10999
3)邏輯預處理器指令
#if defined CALCAVERAGE 或 #ifdef CALCAVERAGE int count=sizeof(data)/sizeof(data[0]); for(int i=0; i<count; i++) average += data; average /= count; #endif 若是已經定義符號CALCAVERAGE則把#if與#endif間的語句放在要編譯的源代碼內
4)防止重複引入某些頭文件
#ifndef COMPARE_H #define COMPARE_H 注意: 這裏只是定義一個沒有值的符號COMPARE_H, 下面的namespace compare不是COMPARE_H的 內容,這裏的定義不像是定義一個常量或宏,僅僅定義一個符號,指出此符號已定義,則就會有下面的 內容namespace compare{... namespace compare{ double max(const double* data, int size); double min(const double* data, int size); } #endif 比較 #define VERSION \ 3 由於有換行符\ 因此上句等價於 #define VERSION 3 由此能夠看出#define COMPARE_H與namespace compare是獨立沒有關係的兩個行 也能夠這樣用 #if defined block1 && defined block2 ... #endif #if CPU==PENTIUM4 ... #endif #if LANGUAGE == ENGLISH #define Greeting "Good Morning." #elif LANGUAGE == GERMAN #define Greeting "Guten Tag." #elif LANGUAGE == FRENCH #define Greeting "Bonjour." #else #define Greeting "Hi." #endif std::cout<<Greeting << std::endl; #if VERSION == 3 ... #elif VERSION == 4 ... #else ... #endif
5)標準的預處理器宏
__LINE__ 當前源文件中的代碼行號,十進制整數 __FILE__ 源文件的名稱,字符串字面量 __DATE__ 源文件的處理日期,字符串字面量,格式mmm dd yyyy其中mmm是月份如Jan、Feb等 dd是01-31 yyyy是四位的年份 __TIME__ 源文件的編譯 時間,也是字符串字面量格式是hh:mm:ss __STDC__ 這取決於實現方式,若是編譯器選項設置爲編譯標準的C代碼,一般就定義它,不然就不定義它 __cplusplus 在編譯C++ 程序時,它就定義爲199711L 使用#line能夠修改__FILE__返回的字符串 如 #line 1000 把當前行號設置爲1000 #line 1000 "the program file" 修改__FILE__返回的字符串行號改成了1000,文件名改成了"the program file" #line __LINE__ "the program file" 修改__FILE__返回的字符串行號沒變,文件名改成了"the program file" cout << "program last complied at "<<__TIME__ << " on " << __DATE__ << endl;
6)#error
在預處理階段,若是出現了錯誤,則#error指令能夠生成一個診斷 消息,並顯示爲一個編譯錯誤,同時停止編譯 #ifndef __cplusplus #error "Error - Should be C++" #endif
7)#pragma
專門用於實現預先定義好的選項,其結果在編譯器說明文檔中進行了詳細的解釋。編譯器未識別出來的#pragma指令都會被忽略
8)assert()宏
在標準庫頭文件<cassert>中聲明 用於在 程序中 測試一個邏輯表達式,若是邏輯表達式爲false, 則assert()會終止 程序,並顯示診斷 消息 用於在條件不知足就會出現重大錯誤,因此應確保後面的語句不該再繼續執行,因此它的應用很是靈活 注意: assert不是錯誤處理 機制,邏輯表達式的結果不該產生負面效果,也不該超出 程序員的控制(如找開一個文件是否成功), 程序應提供適當的代碼來處理這種狀況 assert(expression); assert(expression) && assert(expression2); 可使用#define NDEBUG來關閉斷言 機制 #include <iostream> #include <cassert> using std::cout; using std::endl; int main() { int x=0; int y=0; cout<<endl; for(x=0; x<20; x++) { cout<<"x= "<<x <<" y= "<<y<<endl; assert(x<y); //當x>=y與x==5時,就報錯,並終止 程序的執行 } return 0; }
1、預處理的由來:
在C++的歷史發展中,有不少的語言特徵(特別是語言的晦澀之處)來自於C語言,預處理就是其中的一個。C++從C語言那裏把C語言預處理器繼承過來(C語言預處理器,被Bjarne博士簡稱爲Cpp,不知道是否是C Program Preprocessor的簡稱)。
2、常見的預處理功能:
預處理器的主要做用就是把經過預處理的內建功能對一個資源進行等價替換,最多見的預處理有:文件包含,條件編譯、佈局控制和宏替換4種。
1,文件包含:#include 是一種最爲常見的預處理,主要是作爲文件的引用組合源程序正文。
2,條件編譯:#if,#ifndef,#ifdef,#endif,#undef等也是比較常見的預處理,主要是進,行編譯時進行有選擇的挑選,註釋掉一些指定的代碼,以達到版本控制、防止對文件重複包含的功能。
3,佈局控制:#progma,這也是咱們應用預處理的一個重要方面,主要功能是爲編譯程序提供很是規的控制流信息。
4,宏替換: #define,這是最多見的用法,它能夠定義符號常量、函數功能、從新命名、字符串的拼接等各類功能。
3、預處理指令:
預處理指令的格式以下:
#directive tokens #符號應該是這一行的第一個非空字符,通常咱們把它放在起始位置。若是指令一行放不下,能夠經過\進行控制,例如: #define Error if(error) exit(1) 等價於 #define Error \ if(error) exit(1) 不過咱們爲了美化起見,通常都不怎麼這麼用,更常見的方式以下: # ifdef __BORLANDC__ if_true<(is_convertible<Value,named_template_param_base>::value)>::template then<make_named_arg, make_key_value>::type Make; # else enum { is_named = is_named_parameter<Value>::value }; typedef typename if_true<(is_named)>::template then<make_named_arg, make_key_value>::type Make; # endif 下面咱們看一下常見的預處理指令: #define 宏定義 #undef 未定義宏 #include 文本包含 #ifdef 若是宏被定義就進行編譯 #ifndef 若是宏未被定義就進行編譯 #endif 結束編譯塊的控制 #if 非零就對代碼進行編譯 #else 做爲其餘預處理的剩餘選項進行編譯 #elif 這是一種#else和#if的組合選項 #line 改變當前的行數和文件名稱 #error 輸出一個錯誤信息 #pragma 爲編譯程序提供很是規的控制流信息
下面咱們對這些預處理進行一一的說明,考慮到宏的重要性和繁瑣性,咱們把它放到最後講。
4、文件包含指令:
這種預處理使用方式是最爲常見的,平時咱們編寫程序都會用到,最多見的用法是:
#include <iostream> //標準庫頭文件 #include <iostream.h> //舊式的標準庫頭文件 #include "IO.h" //用戶自定義的頭文件 #include "../file.h" //UNIX下的父目錄下的頭文件 #include "/usr/local/file.h" //UNIX下的完整路徑 #include "..\file.h" //Dos下的父目錄下的頭文件 #include "\usr\local\file.h" //Dos下的完整路徑
這裏面有2個地方要注意:
一、咱們用<iostream>仍是<iostream.h>?
咱們主張使用<iostream>,而不是<iostream.h>,爲何呢?我想你可能還記得我曾經給出過幾點理由,這裏我大體的說一下:
首先,.h格式的頭文件早在98年9月份就被標準委員會拋棄了,咱們應該緊跟標準,以適合時代的發展。
其次,iostream.h只支持窄字符集,iostream則支持窄/寬字符集。
還有,標準對iostream做了不少的改動,接口和實現都有了變化。
最後,iostream組件所有放入namespace std中,防止了名字污染。
二、<io.h>和"io.h"的區別?
其實他們惟一的區別就是搜索路徑不一樣:
對於#include <io.h> ,編譯器從標準庫路徑開始搜索
對於#include "io.h" ,編譯器從用戶的工做路徑開始搜索
5、編譯控制指令:
這些指令的主要目的是進行編譯時進行有選擇的挑選,註釋掉一些指定的代碼,以達到版本控制、防止對文件重複包含的功能。
使用格式,以下:
一、 #ifdef identifier your code #endif 若是identifier爲一個定義了的符號,your code就會被編譯,不然剔除 二、 #ifndef identifier your code #endif 若是identifier爲一個未定義的符號,your code就會被編譯,不然剔除 三、 #if expression your code #endif 若是expression非零,your code就會被編譯,不然剔除 四、 #ifdef identifier your code1 #else your code2 #endif 若是identifier爲一個定義了的符號,your code1就會被編譯,不然your code2就會被編譯 五、 #if expressin1 your code1 #elif expression2 your code2 #else your code3 #enif 若是epression1非零,就編譯your code1,不然,若是expression2非零,就編譯y our code2,不然,就編譯your code3