C學習-預處理指令-宏定義、文件包含、條件編譯(六)

預處理指令簡介

1.C語言在對源程序進行編譯以前,會先對一些特殊的預處理指令做解釋(好比以前使用的#include文件包含指令),產生一個新的源程序(這個過程稱爲編譯預處理),以後再進行一般的編譯ios

2.爲了區分預處理指令和通常的C語句,全部預處理指令都以符號"#"開頭,而且結尾不用分號函數

3.預處理指令能夠出如今程序的任何位置,它的做用範圍是從它出現的位置到文件尾。習慣上咱們儘量將預處理指令寫在源程序開頭,這種狀況下,它的做用範圍就是整個源程序文件spa

4.C語言提供的預處理指令主要有:宏定義、文件包含、條件編譯操作系統

這一講先介紹一下宏定義,宏定義能夠分爲2種:不帶參數的宏定義帶參數的宏定義code

1、宏定義

一、不帶參數的宏定義

1.1 通常形式

#define 宏名 字符串blog

好比#define ABC 10遞歸

右邊的字符串也能夠省略,好比#define ABCip

1.2 做用

它的做用是在編譯預處理時,將源程序中全部"宏名"替換成右邊的"字符串",經常使用來定義常量作用域

接下來寫個程序根據圓的半徑計算周長開發

1 #include <stdio.h>
 2 
 3 // 源程序中全部的宏名PI在編譯預處理的時候都會被3.14所代替
 4 #define PI 3.14
 5 
 6 // 根據圓的半徑計radius算周長
 7 float girth(float radius) {
 8     return 2 * PI *radius;
 9 }
10 
11 int main ()
12 {
13     float g = girth(2);
14     
15     printf("周長爲:%f", g);
16     return 0;
17 }

在第4行定義了一個叫PI的宏,在編譯預處理以後,第8行中的2 PI radius就會變成2 3.14 radius。

輸出結果:clipboard.png

1.3 使用習慣與注意

1> 宏名通常用大寫字母,以便與變量名區別開來,但用小寫也沒有語法錯誤

2> 對程序中用雙引號擴起來的字符串內的字符,不進行宏的替換操做。好比:

1 #define R 10
2 int main ()
3 {
4     char *s = "Radio";
5     return 0;
6 }

在第1行定義了一個叫R的宏,可是第4行中"Radio"裏面的'R'並不會被替換成10

3> 在編譯預處理用字符串替換宏名時,不做語法檢查,只是簡單的字符串替換。只有在編譯的時候纔對已經展開宏名的源程序進行語法檢查

1 #define I 100
2 int main ()
3 {
4     int i[3] = I;
5     return 0;
6 }

在作編譯預處理的時候,無論語法對不對,第4行的I都會被替換爲100。不過在編譯的時候就會報第4行的錯。

4> 宏名的有效範圍是從定義位置到文件結束。若是須要終止宏定義的做用域,能夠用#undef命令

1 #define PI 3.14
2 /*
3 .
4 .
5 .
6 .
7 */
8 #undef PI

PI這個宏在第1行到第8行之間是有效的,第8行後就無效了

5> 定義一個宏時能夠引用已經定義的宏名

#define R  3.0
#define PI 3.14
#define L  2*PI*R
#define S  PI*R*R

二、帶參數的宏定義

2.1 通常形式

#define 宏名(參數列表) 字符串

2.2 做用

在編譯預處理時,將源程序中全部宏名替換成字符串,而且將 字符串中的參數 用 宏名右邊參數列表 中的參數替換

1 #include <stdio.h>
 2 
 3 #define average(a, b) (a+b)/2
 4 
 5 int main ()
 6 {
 7     int a = average(10, 4);
 8     
 9     printf("平均值:%d", a);
10     return 0;
11 }

第3行中定義了一個帶有2個參數的宏average,第7行其實會被替換成:int a = (10 + 4)/2;,輸出結果爲:clipboard.png
。是否是感受這個宏有點像函數呢?

2.3 使用注意

1 #define average (a, b) (a+b)/2
2 
3 int main ()
4 {
5     int a = average(10, 4);
6     return 0;
7 }

注意第1行的宏定義,宏名average跟(a, b)之間是有空格的,因而,第5行就變成了這樣:

int a = (a, b) (a+b)/2(10, 4);

這個確定是編譯不經過的

2> 帶參數的宏在展開時,只做簡單的字符和參數的替換,不進行任何計算操做。因此在定義宏時,通常用一個小括號括住字符串的參數。

下面定義一個宏D(a),做用是返回a的2倍數值:

  • 若是定義宏的時候不用小括號括住參數

1 #include <stdio.h>
 2 
 3 #define D(a) 2*a
 4 
 5 int main ()
 6 {
 7     int b = D(3+4);
 8     
 9     printf("%d", b);
10     return 0;
11 }

第7行將被替換成int b = 2*3+4;,輸出結果:10

若是定義宏的時候用小括號括住參數,把上面的第3行改爲:

#define D(a) 2*(a)

注意右邊的a是有括號的,第7行將被替換成int b = 2*(3+4);,輸出結果:14

3> 計算結果最好也用括號括起來

下面定義一個宏P(a),做用是返回a的平方:

  • 若是不用小括號括住計算結果

1 #include <stdio.h>
 2 
 3 #define Pow(a) (a) * (a)
 4 
 5 int main(int argc, const char * argv[]) {
 6     int b = Pow(10) / Pow(2);
 7     
 8     printf("%d", b);
 9     return 0;
10 }

注意第3行,沒有用小括號擴住計算結果,只是括住了參數而已。第6行代碼被替換爲:

int b = (10) * (10) / (2) * (2);

簡化以後:int b = 10 (10 / 2) 2;,最後變量b爲:100

  • 若是用小括號括住計算結果
    將上面的第3行代碼改成:

#define Pow(a) ( (a) * (a) )

那麼第6行被替換爲:

int b = ( (10) * (10) ) / ( (2) * (2) );

簡化以後:int b = (10 * 10) / (2 * 2);,最後輸出結果:25。這個纔是咱們想要的結果。

也就意味着前面的#define average(a, b) (a+b)/2應該寫成#define average(a, b) (((a)+(b))/2)

2.5 與函數的區別

從整個使用過程能夠發現,帶參數的宏定義,在源程序中出現的形式與函數很像。可是二者是有本質區別的:

1> 宏定義不涉及存儲空間的分配、參數類型匹配、參數傳遞、返回值問題

2> 函數調用在程序運行時執行,而宏替換隻在編譯預處理階段進行。因此帶參數的宏比函數具備更高的執行效率

2、條件編譯

條件編譯的概念

在不少狀況下,咱們但願程序的其中一部分代碼只有在知足必定條件時才進行編譯,不然不參與編譯(只有參與編譯的代碼最終才能被執行),這就是條件編譯

一、基本用法

#if 條件1
 ...code1...
#elif 條件2
 ...code2...
#else
 ...code3...
#endif

1> 若是條件1成立,那麼編譯器就會把#if#elif 之間的code1代碼編譯進去(注意:是編譯進去,不是執行,很平時用的if-else是不同的)

2> 若是條件1不成立、條件2成立,那麼編譯器就會把#elif 與 #else之間的code2代碼編譯進去

3> 若是條件一、2都不成立,那麼編譯器就會把#else 與 #endif之間的code3編譯進去

4> 注意,條件編譯結束後,要在最後面加一個#endif,否則後果很嚴重(本身思考一下後果)

5> #if#elif後面的條件通常是判斷宏定義不是判斷變量,由於條件編譯是在編譯以前作的判斷,宏定義也是編譯以前定義的,而變量是在運行時才產生的、纔有使用的意義

二、舉個例子

1 #include <stdio.h>
 2 
 3 #define MAX 11
 4 
 5 int main ()
 6 {
 7 #if MAX == 0
 8     printf("MAX是0");
 9 #elif MAX > 0
10     printf("MAX大於0");
11 #else
12     printf("MAX小於0");
13 #endif
14     return 0;
15 }

在第3行定義了一個宏MAX,固然在開發中這個MAX可能被定義在其餘頭文件中,如今只是爲了方便演示,就寫到main函數上面了。注意第7到第13行的條件編譯語句。
因爲MAX爲11,因此#elif的條件成立,第10行代碼將會被編譯進去,其實編譯預處理後的代碼是這樣的:

/*stdio.h文件中的內容將會代替#include <stdio.h>的位置*/

int main ()
{
    printf("MAX大於0");
    return 0;
}

代碼變得很是簡潔,輸出結果:clipboard.png

三、其餘用法

3.1 #if defined()和#if !defined()的用法

#if#elif後面的條件不只僅能夠用來判斷宏的值,還能夠判斷是否認義過某個宏。好比:

1 #if defined(MAX)
2     ...code...
3 #endif

若是前面已經定義過MAX這個宏,就將code編譯進去。它不會管MAX的值是多少,只要定義過MAX,條件就成立

條件也能夠取反:

1 #if !defined(MAX)
2     ...code...
3 #endif

若是前面沒有定義過MAX這個宏,就將code編譯進去。

3.2 #ifdef#ifndef的使用

  • #ifdef的使用和#if defined()的用法基本一致

1 #ifdef MAX
2     ...code...
3 #endif

若是前面已經定義過MAX這個宏,就將code編譯進去。

#ifndef又和#if !defined()的用法基本一致

1 #ifndef MAX
2     ...code...
3 #endif

若是前面沒有定義過MAX這個宏,就將code編譯進去。

3、文件包含

一、概念

其實咱們早就有接觸文件包含這個指令了, 就是#include,它能夠將一個文件的所有內容拷貝另外一個文件中。

二、通常形式

2.1 第1種形式#include <文件名>

直接到C語言庫函數頭文件所在的目錄中尋找文件

2.2 第2種形式 #include "文件名"

系統會先在源程序當前目錄下尋找,若找不到,再到操做系統的path路徑中查找,最後纔到C語言庫函數頭文件所在目錄中查找

三、使用注意

1.#include指令容許嵌套包含,好比a.h包含b.h,b.h包含c.h,可是不容許遞歸包含,好比 a.h 包含 b.h,b.h 包含 a.h。

下面的作法是錯誤

clipboard.png

clipboard.png

2.使用#include指令可能致使屢次包含同一個頭文件,下降編譯效率
好比下面的狀況:

clipboard.png

clipboard.png

在one.h中聲明瞭一個one函數;在two.h中包含了one.h,順便聲明瞭一個two函數。(這裏就不寫函數的實現了,也就是函數的定義)

假如我想在main.c中使用one和two兩個函數,並且有時候咱們並不必定知道two.h中包含了one.h,因此可能會這樣作:

clipboard.png

編譯預處理以後main.c的代碼是這樣的:

1 void one();
2 void one();
3 void two();
4 int main ()
5 {
6 
7     return 0;
8 }

第1行是由#include "one.h"致使的,第二、3行是由#include "two.h"致使的(由於two.h裏面包含了one.h)。能夠看出來,one函數被聲明瞭2遍,根本就沒有必要,這樣會下降編譯效率。

爲了解決這種重複包含同一個頭文件的問題,通常咱們會這樣寫頭文件內容:

clipboard.png

clipboard.png

大體解釋一下意思,就拿one.h爲例:當咱們第一次#include "one.h"時,由於沒有定義_ONE_H_,因此第9行的條件成立,接着在第10行定義了_ONE_H_這個宏,而後在13行聲明one函數,最後在15行結束條件編譯。當第二次#include "one.h",由於以前已經定義過_ONE_H_這個宏,因此第9行的條件不成立,直接跳到第15行的#endif,結束條件編譯。就是這麼簡單的3句代碼,防止了one.h的內容被重複包含。

這樣子的話,main.c中的:

#include "one.h"
#include "two.h"

就變成了:

1 // #include "one.h"
 2 #ifndef _ONE_H_
 3 #define _ONE_H_
 4 
 5 void one();
 6 
 7 #endif
 8 
 9 // #include "two.h"
10 #ifndef _TWO_H_
11 #define _TWO_H_
12 
13 // #include "one.h"
14 #ifndef _ONE_H_
15 #define _ONE_H_
16 
17 void one();
18 
19 #endif
20 
21 void two();
22 
23 #endif

第2~第7行是#include "one.h"致使的,第10~第23行是#include "two.h"致使的。編譯預處理以後就變爲了:

1 void one();
2 void two();

這纔是咱們想要的結果


注:本文轉自李明傑老師的博文

相關文章
相關標籤/搜索