C++中,在編譯器對源程序進行編譯之前,首先要由預處理對程序文本進行預處理。預處理器提供了一組預編譯處理指令和預處理操作符。預處理指令實際上不是C++語言的一部分,它只是用來擴充C++程序設計的環境。所有的預處理指令在程序中都是以“#”來引導,每一條預處理指令單獨占用一行,不要用分號結束。預處理指令可以根據需要出現在程序的位置。
先來看看一些預處理指令
C++提供的編譯預處理功能主要有以下三種:
① 宏定義
② 文件包含
③條件編譯
首先是宏定義:
(2) 使用#define定義的標識符不是變量,它只用作宏替換,因此不占有內存。
(3) 習慣上用大寫字母表示<宏名>,這只是一種習慣的約定,其目的是為了與變量名區分,因為變量名
通常用小寫字母。
如果某一個標識符被定義為宏名后,在取消該宏定義之前,不允許重新對它進行宏定義。取消宏定義使用如下命令:
#undef<標識符>
其中,undef是關鍵字。該命令的功能是取消對<標識符>已有的宏定義。被取消了宏定義的標識符,可以對它重新進行定義。
宏定義可以嵌套,已被定義的標識符可以用來定義新的標識符。例如:
#define PI 3.14159265
#define R 10
#define AREA (PI*R*R)
int array[MAX_SIZE];
for(i=0;i<MAX_SIZE;i++) /*……*/
宏表示的值可以是一個常量表達式,其中允許包括前面已經定義的宏標識符。例如:
#define ONE 1
#define TWO 2
#define THREE (ONE+TWO)
注意上面的宏定義使用了括號。盡管它們並不是必須的。但出於謹慎考慮,還是應該加上括號的。例如:
six=THREE*TWO;
six=(ONE+TWO)*TWO;
如果沒有那個括號,就轉換成six=ONE+TWO*TWO;了。
宏還可以代表一個字符串常量,例如:
#define VERSION "Version 1.0 Copyright(c) 2003"
帶參數的宏和函數調用看起來有些相似。看一個例子:
#define Cube(x) (x)*(x)*(x)
可以時任何數字表達式甚至函數調用來代替參數x。這里再次提醒大家注意括號的使用。宏展開后完全包含在一對括號中,而且參數也包含在括號中,這樣就保證了宏和參數的完整性。看一個用法:
int num=8+2;
volume=Cube(num);
展開后為(8+2)*(8+2)*(8+2);
如果沒有那些括號就變為8+2*8+2*8+2了。
volume=Cube(num++);
如果Cube是一個函數,上面的寫法是可以理解的。但是,因為Cube是一個宏,所以會產生副作用。這里的擦書不是簡單的表達式,它們將產生意想不到的結果。它們展開后是這樣的:
volume=(num++)*(num++)*(num++);
很顯然,結果是10*11*12,而不是10*10*10;
那么怎樣安全的使用Cube宏呢?必須把可能產生副作用的操作移到宏調用的外面進行:
int num=8+2;
volume=Cube(num);
num++;
#define KEY_DOWN(vk_code) (GetAsyncKeyState(vk_code)&0x80000?1:0) //獲取鍵盤輸入
出現在宏定義中的#運算符把跟在其后的參數轉換成一個字符串。有時把這種用法的#稱為字符串化運算符。例如:
#define PASTE(n) "adhfkj"#n
main()
{
printf("%s\n",PASTE(15));
}
宏定義中的#運算符告訴預處理程序,把源代碼中任何傳遞給該宏的參數轉換成一個字符串。所以輸出應該是adhfkj15。
##運算符用於把參數連接到一起。預處理程序把出現在##兩側的參數合並成一個符號。看下面的例子:
#define NUM(a,b,c) a##b##c
#define STR(a,b,c) a##b##c
main()
{
printf("%d\n",NUM(1,2,3));
printf("%s\n",STR("aa","bb","cc"));
}
最后程序的輸出為:
123
aabbcc
#define CC_READONLY_FUN(varType, varName, funName) \
public:\
void set##funName(varType var)\
{\
varName = var;\
}\
varType get##funName()\
{\
return varName;\
}\
protected:\
varType varName;
那么CC_READONLY_FUN(int, m_Size, Size) 就等同於
public:
void setSize(int var)
{
m_Size = var;
}
int getSize()
{
return m_Size;
}
protected:
int m_Size
千萬別擔心,除非需要或者宏的用法恰好和手頭的工作相關,否則很少有程序員會知道##運算符。絕大多數程序員從來沒用過它,但是還是要理解一下的。
然后是文件包含:
#include預處理指令的作用是在指令處展開被包含的文件。包含可以是多重的,也就是說一個被包含的文件中還可以包含其他文件。標准C編譯器至少支持八重嵌套包含。
預處理過程不檢查在轉換單元中是否已經包含了某個文件並阻止對它的多次包含。這樣就可以在多次包含同一個頭文件時,通過給定編譯時的條件來達到不同的效果。例如:
#define AAA
#include "t.c"
#undef AAA
#include "t.c"
為了避免那些只能包含一次的頭文件被多次包含,可以在頭文件中用編譯時條件來進行控制。例如:
/*my.h*/
#ifndef MY_H
#define MY_H
……
#endif
在程序中包含頭文件有兩種格式:
#include <my.h>
#include "my.h"
第一種方法是用尖括號把頭文件括起來。這種格式告訴預處理程序在編譯器自帶的或外部庫的頭文件中搜索被包含的頭文件。第二種方法是用雙引號把頭文件括起來。這種格式告訴預處理程序在當前被編譯的應用程序的源代碼文件中搜索被包含的頭文件,如果找不到,再搜索編譯器自帶的頭文件。
采用兩種不同包含格式的理由在於,編譯器是安裝在公共子目錄下的,而被編譯的應用程序是在它們自己的私有子目錄下的。一個應用程序既包含編譯器提供的公共頭文件,也包含自定義的私有頭文件。采用兩種不同的包含格式使得編譯器能夠在很多頭文件中區別出一組公共的頭文件
最后是條件編譯指令:
使用條件編譯指令,可以限定程序中的某些內容要在滿足一定條件的情況下才參與編譯。因此,利用條件編譯可以使同一個源程序在不同的編譯條件下產生不同的目標代碼。例如,可以在調試時增加一些調試語句,以達到跟蹤的目的,並利用條件 編譯指令,限定當程序調試后,重新編譯時,使調試語句不參與編譯。常用的條件編譯語句有下列5種形式:
(1)形式一
(2)形式二
(3)形式三
(4)形式四
如果標識符經“#defined”定義過,且未經undef 刪除,則編譯程序段1,否則編譯程序段2.如果沒有程序段2,則#else可以省略:
(5)形式五
如果“標識符”未被定義過,則編譯程序段1,否則編譯程序段2.如果沒有程序段2,則#else 可以省略:
至此,結束。