指令說明
宏,又叫預處理指令,是以#號開頭的代碼行。#號必須是該行除了任何空白字符外的第一個字符。#後是指令關鍵字,在關鍵字和#號之間允許存在任意個數的空白字符。整行語句構成了一條預處理指令,宏定義在源程序中單獨另起一行,換行符是宏定義的結束標誌。如果一個宏定義太長,一行不夠時,可採用續行的方法。續行是在鍵人回車符之前先鍵入符號""。注意回車要緊接在符號""之後,中間不能插入其它符號。該指令將在編譯器進行編譯之前對源代碼做某些轉換。下面是部分預處理指令,對於windows c和c++ ,可以到 http://msdn.microsoft.com/en-us/library/503x3e3s.aspx 上面查看更加完整的信息。
指令 用途
# 空指令,無任何效果
#include 包含一個源代碼文件
#define 定義宏
#undef 取消已定義的宏
#if 如果給定條件爲真,則編譯下面代碼
#ifdef 如果宏已經定義,則編譯下面代碼
#ifndef 如果宏沒有定義,則編譯下面代碼
#elif 如果前面的#if給定條件不爲真,當前條件爲真,則編譯下面代碼
#endif 結束一個#if……#else條件編譯塊
#error 停止編譯並顯示錯誤信息
文件包含
#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.#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
千萬別擔心,除非需要或者宏的用法恰好和手頭的工作相關,否則很少有程序員會知道##運算符。絕大多數程序員從來沒用過它。
條件編譯指令
條件編譯指令將決定那些代碼被編譯,而哪些是不被編譯的。可以根據表達式的值或者某個特定的宏是否被定義來確定編譯條件。
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指令沒有正式的定義。編譯器可以自定義其用途。典型的用法是禁止或允許某些煩人的警告信息。
與編譯器相關的宏
宏說明
文中只列出部分相關宏,如果想查看完整的宏列表,可參考MSDN的"Predefined Macros"一節
01) _MSC_VER
Microsoft 編譯器的版本宏.
各版本編譯器相應的數值如下表所示:
Compiler _MSC_VER value
-------------------- -----------------------
C Compiler version 6.0 600
C/C++ compiler version 7.0 700
Visual C++, Windows, version 1.0 800
Visual C++, 32-bit, version 1.0 800
Visual C++, Windows, version 2.0 900
Visual C++, 32-bit, version 2.x 900
Visual C++, 32-bit, version 4.0 1000
Visual C++, 32-bit, version 5.0 1100
Visual C++, 32-bit, version 6.0 1200
Visual C++, 32-bit, version 2002 1300
Visual C++, 32-bit, version 2003 1310
Visual C++, 32-bit, version 2005 1400
Embedded Visual C++, 32-bit,version 4.0 Cross 1200 - 1202
02) _MFC_VER
MFC版本宏.
Version _MFC_VER value
-------------- -----------------
4.21 0x0421
6.0 0x0600
7.0 0x0700
03) __TIME__
編譯當前源文件的時間,格式爲 "hh:mm:ss" 樣式的字符串.
04) __DATE__
編譯當前源文件的日期,格式爲 "Mmm dd yyyy" 樣式的字符串.
05) __FILE__
編譯的當前源文件名.
實例解說
01) 根據_MFC_VER值判斷當前的編譯環境.
#if _MSC_VER >= 1400
// this is Visual C++ 2005
#elif _MSC_VER >= 1310
// this is Visual c++ .NET 2003
#elif _MSC_VER > 1300
// this is Visual C++ .NET 2002
#endif
02) #else if 和 #elif 的細微差別
#if _MSC_VER < 1202
//EVC 4.0 complier is cross 1200 - 1202
#else if _MSC_VER >= 1400
//Visual C++ 2005 complier is 1400
#else
//Visual C++ 2005 complier is 1400
#endif
這段預編譯代碼在Evc4.0和visual studio 2005中編譯會出錯,提示錯誤爲"unexpected #else".此時只要將"#else if"置換成"#elif"即可:
#if _MSC_VER < 1202
//EVC 4.0 complier is cross 1200 - 1202
#elif _MSC_VER >= 1400
//Visual C++ 2005 complier is 1400
#else
//Visual C++ 2005 complier is 1400
#endif
上面代碼可以順利編譯通過.
03) 包含上級目錄的某個頭文件
如果當前文件需要包含上級目錄的某個頭文件,可採用"..//"形式,比如:
#include "..//Configure.h"
甚至還可以層層遞推,定位於上上級目錄:
#include "..//..//Configure.h"
04) 包含當前目錄的某個文件夾下的頭文件
如果當前文件需要包含當前目錄下的某個文件夾中的某個頭文件,可採用".//"形式,比如:
#include ".//Include//Configure.h"
05) 判斷當前CPU類型
#ifdef _X86_
//x86
#endif
#ifdef _MIPS_
//Mips
#endif
#ifdef _ARM_
//Arm
#endif
06)代碼中手動添加lib庫
#pragma comment (lib,"..//Engine//Engine.lib")
濾掉空字符
如果我們不使用反斜槓,當我們試圖初始化一個跨多行的字符串是,c語言編譯器就會發出警告。如下面的語句所示:
char letters[] = {"abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ"};
但是我們在行尾使用反斜槓, 那麼就可以吧字符串常量跨行書寫, 如下所示:
char letters[] = {"abcdefghijklmnopqrstuvwxyz\
ABCDEFGHIJKLMNOPQRSTUVWXYZ"};
從續行的開始輸入字符串,可以避免在整個字符串中加入多於的空格。綜上所述,上面的語句定義了一個字符數組letters,
並將其初始化爲如下的初值:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
c語言中還有一種拆分字符串的方法,那就是將其寫個多個相鄰的字符串。這些字符串之間用0個或者多個空白、製作符以及換行符隔開。c語言編譯器會自動將這些字符串連接起來。因此,下面的表達式:"one" "two" "three" 實際上相當於 "onetwothree".因此前面跨行的初始化語句也可以用下面的形式完成:
char letters[] = {"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"}
用do{}while(0) 結構也是濾掉空字符的一個好方法
定義多行宏
換行符是宏定義的結束標誌。如果一個宏定義太長,一行不夠時,可採用續行的方法。續行是在鍵人回車符之前先鍵入符號""。注意回車要緊接在符號""之後,中間不能插入其它符號。一般用do{}while(0)語句包含多語句防止錯誤,還可以濾掉不必要的空字符
例如:#difne DO(a,b) a+b;\
a++;
應用時:if(….)
DO(a,b); //產生錯誤
else
解決方法: #difne DO(a,b) do{a+b;\
a++;}while(0)
擴展閱讀
還有一篇文章寫得比較深入,我轉載過來了 http://hi.baidu.com/abcd1f2/item/4ca222508d898da2adc85751,值得一讀,其實最好找一本相關的經典書籍好好讀一下,大家誰有推薦一下啊
參考文章:
(1)http://blog.csdn.net/norains/article/details/1942594
(2)http://flywindwyy.blog.163.com/blog/static/1725508532011110102759553/