(一) 宏定義
(二) 文件包含
(三) 條件編譯
在C語言中,並沒有任何內在的機制來完成如下一些功能:在編譯時包含其他源文件、定義宏、根據條件決定編譯時是否包含某些代碼。要完成這些工作,就需要使用預處理程序。儘管在目前絕大多數編譯器都包含了預處理程序,但通常認爲它們是獨立於編譯器的。預處理過程讀入源代碼,檢查包含預處理指令的語句和宏定義,並對源代碼進行響應的轉換。預處理過程還會刪除程序中的註釋和多餘的空白字符。
預處理指令是以#號開頭的代碼行。#號必須是該行除了任何空白字符外的第一個字符。#後是指令關鍵字,在關鍵字和#號之間允許存在任意個數的空白字符。整行語句構成了一條預處理指令,該指令將在編譯器進行編譯之前對源代碼做某些轉換。下面是部分預處理指令:
指令 用途
# 空指令,無任何效果
#include 包含一個源代碼文件
#define 定義宏
#undef 取消已定義的宏
#if 如果給定條件爲真,則編譯下面代碼
#ifdef 如果宏已經定義,則編譯下面代碼
#ifndef 如果宏沒有定義,則編譯下面代碼
#elif 如果前面的#if給定條件不爲真,當前條件爲真,則編譯下面代碼
#endif 結束一個#if……#else條件編譯塊
#error 停止編譯並顯示錯誤信息
在C++中,我們一般用const定義符號常量。很顯然,用const定義常量比用define定義常量更好。
(a) 在書寫#define 命令時,注意<宏名>和<字符串>之間用空格分開,而不是用等號連接。
(b) 使用#define定義的標識符不是變量,它只用作宏替換,因此不佔有內存。
(c) 習慣上用大寫字母表示<宏名>,這只是一種習慣的約定,其目的是爲了與變量名區分,因爲變量名
通常用小寫字母。
如果某一個標識符被定義爲宏名後,在取消該宏定義之前,不允許重新對它進行宏定義。取消宏定義使用如下命令:
#undef<標識符>
其中,undef是關鍵字。該命令的功能是取消對<標識符>已有的宏定義。被取消了宏定義的標識符,可以對它重新進行定義。
宏定義可以嵌套,已被定義的標識符可以用來定義新的標識符。例如:
#define PI 3.14159265
#define R 10
#define AREA (PI*R*R)
宏定義了一個代表特定內容的標識符。預處理過程會把源代碼中出現的宏標識符替換成宏定義時的值。宏最常見的用法是定義代表某個值的全局符號。宏的第二種用法是定義帶參數的宏,這樣的宏可以象函數一樣被調用,但它是在調用語句處展開宏,並用調用時的實際參數來代替定義中的形式參數。
1.#define指令
#define預處理指令是用來定義宏的。該指令最簡單的格式是:首先神明一個標識符,然後給出這個標識符代表的代碼。在後面的源代碼中,就用這些代碼來替代該標識符。這種宏把程序中要用到的一些全局值提取出來,賦給一些記憶標識符。
#define MAX_NUM 10
int array[MAX_NUM];
for(i=0;i<MAX_NUM;i++) /*……*/
在這個例子中,對於閱讀該程序的人來說,符號MAX_NUM就有特定的含義,它代表的值給出了數組所能容納的最大元素數目。程序中可以多次使用這個值。作爲一種約定,習慣上總是全部用大寫字母來定義宏,這樣易於把程序紅的宏標識符和一般變量標識符區別開來。如果想要改變數組的大小,只需要更改宏定義並重新編譯程序即可。
宏表示的值可以是一個常量表達式,其中允許包括前面已經定義的宏標識符。例如:
#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"
2.帶參數的#define指令
帶參數的宏和函數調用看起來有些相似。看一個例子:
#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++;
3.#運算符
出現在宏定義中的#運算符把跟在其後的參數轉換成一個字符串。有時把這種用法的#稱爲字符串化運算符。例如:
#define PASTE(n) "adhfkj"#n
main()
{
printf("%s\n",PASTE(15));
}
宏定義中的#運算符告訴預處理程序,把源代碼中任何傳遞給該宏的參數轉換成一個字符串。所以輸出應該是adhfkj15。
4.##運算符
##運算符用於把參數連接到一起。預處理程序把出現在##兩側的參數合併成一個符號。看下面的例子:
#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
千萬別擔心,除非需要或者宏的用法恰好和手頭的工作相關,否則很少有程序員會知道##運算符。絕大多數程序員從來沒用過它。
#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"
第一種方法是用尖括號把頭文件括起來。這種格式告訴預處理程序在編譯器自帶的或外部庫的頭文件中搜索被包含的頭文件。第二種方法是用雙引號把頭文件括起來。這種格式告訴預處理程序在當前被編譯的應用程序的源代碼文件中搜索被包含的頭文件,如果找不到,再搜索編譯器自帶的頭文件。
採用兩種不同包含格式的理由在於,編譯器是安裝在公共子目錄下的,而被編譯的應用程序是在它們自己的私有子目錄下的。一個應用程序既包含編譯器提供的公共頭文件,也包含自定義的私有頭文件。採用兩種不同的包含格式使得編譯器能夠在很多頭文件中區別出一組公共的頭文件
條件編譯指令
條件編譯指令將決定那些代碼被編譯,而哪些是不被編譯的。可以根據表達式的值或者某個特定的宏是否被定義來確定編譯條件。
1.#if指令
#if指令檢測跟在製造另關鍵字後的常量表達式。如果表達式爲真,則編譯後面的代碼,知道出現#else、#elif或#endif爲止;否則就不編譯。
2.#endif指令
#endif用於終止#if預處理指令。
#define DEBUG 0
main()
{
#if DEBUG
printf("Debugging\n");
#endif
printf("Running\n");
}
由於程序定義DEBUG宏代表0,所以#if條件爲假,不編譯後面的代碼直到#endif,所以程序直接輸出Running。
如果去掉#define語句,效果是一樣的。
3.#ifdef和#ifndef
#define DEBUG
main()
{
#ifdef DEBUG
printf("yes\n");
#endif
#ifndef DEBUG
printf("no\n");
#endif
}
#if defined等價於#ifdef; #if !defined等價於#ifndef
4.#else指令
#else指令用於某個#if指令之後,當前面的#if指令的條件不爲真時,就編譯#else後面的代碼。#endif指令將中指上面的條件塊。
#define DEBUG
main()
{
#ifdef DEBUG
printf("Debugging\n");
#else
printf("Not debugging\n");
#endif
printf("Running\n");
}
5.#elif指令
#elif預處理指令綜合了#else和#if指令的作用。
#define TWO
main()
{
#ifdef ONE
printf("1\n");
#elif defined TWO
printf("2\n");
#else
printf("3\n");
#endif
}
程序很好理解,最後輸出結果是2。
6.其他一些標準指令
#error指令將使編譯器顯示一條錯誤信息,然後停止編譯。
#line指令可以改變編譯器用來指出警告和錯誤信息的文件號和行號。
#pragma指令沒有正式的定義。編譯器可以自定義其用途。典型的用法是禁止或允許某些煩人的警告信息。
一:#pragma warning指令
該指令允許有選擇性的修改編譯器的警告消息的行爲
指令格式如下:
#pragma warning( warning-specifier : warning-number-list [; warning-specifier : warning-number-list...]
#pragma warning( push[ ,n ] )
#pragma warning( pop )
主要用到的警告表示有如下幾個:
once:只顯示一次(警告/錯誤等)消息
default:重置編譯器的警告行爲到默認狀態
1,2,3,4:四個警告級別
disable:禁止指定的警告信息
error:將指定的警告信息作爲錯誤報告
如果大家對上面的解釋不是很理解,可以參考一下下面的例子及說明
#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 warning 也支持如下格式:
#pragma warning( push [ ,n ] )
#pragma warning( pop )
這裏n代表一個警告等級(1---4)。
#pragma warning( push )保存所有警告信息的現有的警告狀態。
#pragma warning( push, n)保存所有警告信息的現有的警告狀態,並且把全局警告
等級設定爲n。
#pragma warning( pop )向棧中彈出最後一個警告信息,在入棧和出棧之間所作的
一切改動取消。例如:
#pragma warning( push )
#pragma warning( disable : 4705 )
#pragma warning( disable : 4706 )
#pragma warning( disable : 4707 )
#pragma warning( pop )
在這段代碼的最後,重新保存所有的警告信息(包括4705,4706和4707)
在使用標準C++進行編程的時候經常會得到很多的警告信息,而這些警告信息都是不必要的提示,
所以我們可以使用#pragma warning(disable:4786)來禁止該類型的警告
在vc中使用ADO的時候也會得到不必要的警告信息,這個時候我們可以通過
#pragma warning(disable:4146)來消除該類型的警告信息
二:#pragma pack()
注:如果設置的值比結構體中字節最長的類型還要大,則這個變量(注意僅針對這一個變量)只按照它的字節長度對齊,即不會出現內存浪費的情況。請參見(4)。
(1)
#pragma pack(1) //每個變量按照1字節對齊
struct A
{
char x; //aligned on byte boundary 0
int y; //aligned on byte boundary 1
}a;
sizeof(a)==5
(2)
#pragma pack(2) //每個變量按照2字節對齊
struct A
{
char x; //aligned on byte boundary 0
int y; //aligned on byte boundary 2
}a;
sizeof(a)==6
(3)
#pragma pack(4) //每個變量按照4字節對齊
struct A
{
char x; //aligned on byte boundary 0
int y; //aligned on byte boundary 4
}a;
sizeof(a)==8
(4)
#pragma pack() //默認,相當於#pragma pack(8) 每個變量按照8字節對齊
struct A
{
char x; //aligned on byte boundary 0
int y; //aligned on byte boundary 4
}a;
sizeof(a)==8
但是這裏y的大小是4字節,所以不會按照8字節對齊,否則將造成1個int空間的浪費
三.#pragma comment
The following pragma causes the linker to search for the EMAPI.LIB library while linking. The linker searches first in the current working directory and then in the path specified in the LIB environment variable:
#pragma comment( lib, "emapi" )
四.#pragma deprecated
When the compiler encounters a deprecated symbol, it issues C4995:
void func1(void) {}
void func2(void) {}
int main() {
func1();
func2();
#pragma deprecated(func1, func2)
func1(); // C4995
func2(); // C4995
}
五.#pragma message
The following code fragment uses the message pragma to display a message during compilation:
#if _M_IX86 == 500
#pragma message( "P
#endif