第 8 章 C 前置處理

在本章中 將 介紹 C 程式 中 #define 及 #include 等的使用, 與 C 編譯器之間的關係。

對於一個 C 程式 編譯之前, C 編譯器 會先處理 C 程式 中含有 #define、#undef、#if、#ifdef、#ifndef、#endif、#elif、#else 及 #include 敘述, 將之置換成一新 C 程式, 再將之編譯 成一 assembly 程式, 再轉成 machine code(或 object code)。 再經由 linker 變成一執行檔。

本章主要重點 即在討論 C 編譯器 前置處理 的部份 #define 及 #include 等的使用。

於 第 8.1 節 將介紹 #define 巨集 ( macro ) 的宣告與使用, 於 第 8.2 節 將介紹 #include 的使用, 於 第 8.3 節 將介紹 #if、#ifdef 等的使用。

本 章 主 要 內 容 如 下:

回第 7 章
至第 9 章
回 C 程式主目錄


第 8.1 節 #define 巨集的宣告與使用

巨集指令 #define 宣告的格式如下

#define macroname string
其說明如下:

巨集指令的使用(或呼叫),如同函數的呼叫,將 macroname 置於程式中, 為一辨識個體(identifier)即可。對於含引數型的巨集指令(將於 8.1.2 節談到 引數型的巨集指令),其呼叫方式與函數的呼叫型式完全一樣。

巨集指令的使用範圍為有宣告處起至檔案的結束,一般而言,巨集指令 都是宣告於檔案的開端,或是宣告於檔頭,即檔案其副檔名為 .h 再以 #include 的方式加入。

例 1: 設有一 C 程式檔 file.c 其內容為:

        #define MAXSIZE 100
        main( )
        {   int A[MAXSIZE];   }
於編譯時經過前置處理後,該程式變成
        main( )      
        {   int A[100];   }
其中所有的 #define 指令都不見了,MAXSIZE 也都變成了 100。當然前 置處理後的結果我們也見不到。

第 8.1.1 節 常數辨識個體的宣告

為了程式易讀易維修,程式中的常數通常都是以巨集方式來宣告一常數 名稱,該名稱通常是以大寫的字串來表示。 本節將重複介紹 第 2.2.5 節 所提到的 符號常數。

例 2: 設有一 C 程式其內容如下:

      #define MAXSIZE 100
           :
           :
      main( )
      {   int A[MAXSIZE]; .....;}
      void f( )
      { ......
          for (i=0;i < MAXSIZE; i++)
          .........
       }

說明:

  1. 由辨識體 MAXSIZE 本身即可明白該文的意義。
  2. 如果陣列的大小要改為 200,只要改巨集指令為 #define MAXSIZE 200 即可, 整個程式仍保持其一致性。

一些常用的常數如 EOF, NULL 都是宣告於 stdio.h 檔中,要使用這些常數,於檔案 中必須加上 #include 指令。前置處理指令 #include 將於 8.2 節中討論

第 8.1.2 節 含引數型的巨集指令

含引數型的巨集指令,其宣告格式為

#define macroname(arg1[,arg]) string1 arg1

其說明如下:

其呼叫方式如同函數呼叫方式一樣。

例 3:對於巨集指令的宣告

     #define set_nonnegative(A);  if (A<0)\
                                     A=A*(-1);
則於程式中
        set_nonnegative(i);
經過前置處理後即變成
        if(i<0)
         i=i*(-1);
其中形式引數 A 皆由實際引數 i 來替代。

例 4:對於巨集指令的宣告

     #define  swap_int(A,B);  {int t;\
                                  t=A;A=B;B=t;}
於程式中巨集呼叫
      swap_int(i,j);
經過前置處理後即變成
      {int t; t=i; i=j; j=t;}

含引數的巨集指令,稍一不慎就會產生錯誤,如例 4 中 swap_int(s,t); 則會產生問題, 為什麼?

巨集指令的宣告亦有階層式,即可利用前面宣告過的巨集指令,如例 5。

例 5:階層式巨集指令

           #define max(A,B)  ((A>B)?A:B)
           #define max3(A,B,C)  max( max(A,B),c)

問題:試比較 macro 與 function 的優劣點。
C++ 的 inline 作用如同 macro 的作用。

回本章主目錄


第 8.2 節 #include 的使用

C 程式的設計,不僅可以用模組化的方式,以函數來表現,亦可將程式碼 依其不同的目的與功能,呈現在不同的檔案中。為了程式碼的一致性,如 常數,巨集指令,全域(global)變數的宣告(含 extern,將於第九章說明)一 些函數的宣告(僅列出函數的標題 heading)等,都放在一檔頭檔 .h, 當然不見的是要以 .h 當作副檔名,不過,一般都是以 .h 來當作 include 檔的副檔名, 再以指令 #include 方式將該檔 加入即可。指令 #include 的宣告方式如下

#include <filename>
#include "filename"

經過編譯器前置處理後,含指令 #include 這行則由該檔 filename 來代替, 如果 filename 是由 <,> 所含,則編譯器會到 include 目錄下去找該檔或至 預先所定的目錄去找。

例 6: 假設檔案 file1.h 的內容如下:

      #define MAXSIZE 100
      extern int size;
      int f(int x);
假設檔案 file2.c 的內容如下:
      #include "file1.h"
      main()
      {  int A[MAXSIZE],i,sum;
         for(i=0;i < size;i++)
         sum=sum+f(i);
      }
又假設 file1.h 與 file2.c 都在同一目錄下 則經過前置處理, file2.c 就先變成
      #define MAXSIZE 100
      extern int size;
      int f(int x);
      main()
      { int A[MAXSIZE],i,sum;
        for(i=0;i < size;i++)
        sum=sum+f(i);
      }
經過前置處理完成後,file2.c 就變成
    extern int size;
    int f(int x);
    main()
    { int A[100],i,sum;
      for(i=0;i < size;i++)
      sum=sum+f(x);
    } 

註:

回本章主目錄

第 8.3 節 #if、#ifdef 等的使用

如果我們在一檔案欲 include 一檔頭檔 file1.h ,又想避免在同一檔案宣告兩次相 同的東西,例如:

檔案 file1.h 含 extern int size
檔案 file2.h 含 #include "file1.h"
檔案 file3.h 含 #include "file1.h"
檔案 file.c 含 #include "file2.h" 及 #include "file3.h"
則在檔案 file.c 內,變數 size 重複宣告了兩次而產生錯誤。

欲避免這種情況發生,每個 #include "file1.h" 指令, 我們可以改變為如下指令:

      
      #if !define(FILE1)
      #define FILE1 "file1.h"
      #include FILE1
      #endif
或是
      #ifndef FILE1
      #define FILE1 "file1.h"
      #include FILE1
      #endif

註:

回本章主目錄


回第 7 章
至第 9 章
回 C 程式 主目錄