文章目錄
- 3.2.7 命名約定
- 3.3 #undef
- 3.4 命令行定義
- 3.5 條件編譯
- 3.6 文件包含
- 3.6.1 頭文件被包含的方式
- 3.6.2 嵌套文件包含
- 4. 其他預處理指令
3.2.7 命名約定
一般來講函數和宏的使用語法很相似,所以語言本身沒法幫我們區分二者,那我們平時的一個習慣是:
把宏名全部大寫
函數名不要全部大寫
#define MAX(x, y) ((x)>(y)?(x):(y))int Max(int x, int y)
{return x > y ? x : y;
}//有一個特例
//offsetof - 宏 - 全小寫int main()
{return 0;
}
3.3 #undef
這條指令用于移除一個宏定義。
#undef NAME
//如果現存的一個名字需要被重新定義,那么它的舊名字首先要被移除。
#include <stdio.h>#define MAX(x, y) ((x)>(y)?(x):(y))int Max(int x, int y)
{return x > y ? x : y;
}int main()
{int c = MAX(3, 5);printf("%d\n", c);
#undef MAXc = MAX(5, -5);//編譯器會報錯:“MAX”未定義printf("%d\n", c);return 0;
}
3.4 命令行定義
許多C的編譯器提供了一種能力,允許在命令行中定義符號,用于啟動編譯過程。
例如:當我們根據同一個源文件要編譯出一個程序的不同版本的時候,這個特性有點用處。(假定某個程序中聲明了一個某個長度的數組,如果機器內存有限,我們需要一個很小的數組;但是另外一個機器內存大些,我們需要一個數組能夠大些。)
注:VS環境無法演示,gcc來演示
#include <stdio.h>int main()
{int arr [SZ];int i = 0;for(i = 0; i < SZ; i++){arr[i] = i + 1;}for(i = 0; i < SZ; i++){printf("%d " , arr[i]);}return 0;
}
3.5 條件編譯
在編譯一個程序的時候我們如果要將一條語句(一組語句)編譯或者放棄是很方便的,因為我們有條件編譯指令。
比如我們要在不同的操作系統上執行代碼,那么就要執行不同的代碼,我們就可以使用條件編譯,滿足相應的條件,就編譯相應的代碼,另外一份代碼就不會被編譯了。
常見的條件編譯指令:
#if 常量表達式
//…
#endif
//常量表達式由預處理器求值。
#include <stdio.h>#define M 0int main()
{
#if 1 == M //#if 后面的語句為真,就參與編譯;否則就不參與編譯printf("hehe\n");
#endifreturn 0;
}
注: #if 條件編譯不滿足是不編譯;而 if 條件語句不滿足是不執行,但是它的代碼運行時是存在的。
- 多個分支的條件編譯
#if 常量表達式
//…
#elif 常量表達式
//…
#else
//…
#endif
#include <stdio.h>#define M 0int main()
{
#if 1 == Mprintf("hehe\n");
#elif 2 == Mprintf("haha\n");
#elseprintf("heihei\n");
#endifreturn 0;
}
- 判斷是否被定義
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
#include <stdio.h>#define WIN 0int main()
{
#if defined(WIN)printf("windows");
#endifreturn 0;
}
也可以這樣寫:
#include <stdio.h>#define WIN 0int main()
{
#ifdef WINprintf("windows");
#endifreturn 0;
}
#include <stdio.h>#define WIN 0int main()
{
#if !defined(WIN)printf("windows\n");
#endifreturn 0;
}
也可以這樣寫:
#include <stdio.h>#define WIN 0int main()
{
#ifndef WINprintf("windows\n");
#endifreturn 0;
}
- 嵌套指令
#if defined(OS_UNIX)#ifdef OPTION1unix_version_option1();#endif#ifdef OPTION2unix_version_option2();#endif
#elif defined(OS_MSDOS)#ifdef OPTION2msdos_version_option2();#endif
#endif
我們在 stdio.h 這個頭文件中就能看到條件編譯的應用:
有些人會將條件編譯當注釋來用:
#if 0
int main()
{return 0;
}
#endif#include <stdio.h>int main()
{printf("hehe\n");return 0;
}
3.6 文件包含
我們已經知道, #include 指令可以使另外一個文件被編譯,就像它實際出現于 #include 指令的地方一樣。
這種替換的方式很簡單:
預處理器先刪除這條指令,并用包含文件的內容替換。
這樣一個源文件被包含10次,那就實際被編譯10次。
3.6.1 頭文件被包含的方式
- 本地文件包含
#include “filename”
查找策略:先在源文件所在目錄下查找,如果該頭文件未找到,編譯器就像查找庫函數頭文件一樣在標準位置查找頭文件,如果找不到就提示編譯錯誤。
Linux環境的標準頭文件的路徑:
/usr/include
VS環境的標準頭文件的路徑:
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\include
//這是VS2013的默認路徑
注意按照自己的安裝路徑去找。
- 庫文件包含
#include <filename.h>
查找頭文件直接去標準路徑下去查找,如果找不到就提示編譯錯誤。
這樣是不是可以說,對于庫文件也可以使用 “” 的形式包含?
答案是肯定的,可以。
但是這樣做查找的效率就低些,當然這樣也不容易區分是庫文件還是本地文件了。
3.6.2 嵌套文件包含
如果出現這樣的場景:
comm.h和comm.c是公共模塊。
test1.h和test1.c使用了公共模塊。
test2.h和test2.c使用了公共模塊。
test.h和test.c使用了test1模塊和test2模塊。
這樣最終程序中就會出現兩份comm.h的內容。這樣就造成了文件內容的重復。
例子如下:
如何解決這個問題?
答案:條件編譯。
每個頭文件的開頭寫:
#ifndef __TEST_H__
#define __TEST_H__
//頭文件的內容
#endif //__TEST_H__
或者:
#pragma once
就可以避免頭文件的重復引入。
例子:
//test.h#ifndef __TEST_H__
#define __TEST_H__int Add(int x, int y);#endif
或者這樣寫:
//test.h#pragma onceint Add(int x, int y);
4. 其他預處理指令
#error
#pragma
#line
…
不做介紹,自己去了解。
#pragma pack()在結構體部分介紹。