本章集中討論與預先處理程式有關的問題。在編譯器對程式進行通常的編譯之前,要先運行預先處理程式。可能你以前沒有見過這個程式,因為它通常在幕後運行,程式員是看不見它的,然而,這個程式非常有用。
預先處理程式將根據原始碼中的預先處理指令來修改你的程式。預先處理指令(如#define)為預先處理程式提供特定的指令,告訴它應該如何修改你的原始碼。預先處理程式讀入所有包含的檔案和待編譯的原始碼,經過處理產生原始碼的預先處理版本。在該版本中,宏和常量標識符已用相應的代碼和值代替。如果原始碼中包含條件預先處理指令(如#if),預先處理程式將先判斷條件,然後相應地修改原始碼。
預先處理程式有許多非常有用的功能,例如宏定義,條件編譯,在原始碼中插入預定義的環境變數,開啟或關閉某個編譯選項,等等。對專業程式員來說,深入瞭解預先處理程式的各種特徵,是建立快速和高效的程式的關鍵之一。
在閱讀本章時,請記住本章採用的一些技術(以及所提到的一些常見陷阱),以便更好地利用預先處理程式的各種功能。
5.1 什麼是宏(macro)?怎樣使用宏?
宏是一種預先處理指令,它提供了一種機制,可以用來替換原始碼中的字串,宏是用“#define"語句定義的,下面是一個宏定義的例子:
#define VERSION—STAMP "1.02"
上例中所定義的這種形式的宏通常被稱為標識符。在上例中,標識符VERSION_STAMP即代表字串"1.02"——在編譯預先處理時,原始碼中的每個VERSION_STAMP標識符都將被字串“1.02”替換掉。
以下是另一個宏定義的例子:
#define CUBE(x)((x),(x)*(x))
上例中定義了一個名為CUBE的宏,它有一個參數x。CUBE宏有自己的宏體,即((x)*(x)*(x))——在編譯預先處理時,原始碼中的每個CUBE(x)宏都將被((x)*(x)*(x))替換掉。
使用宏有以下幾點好處;
(1)在輸入原始碼時,可省去許多鍵入操作。
(2)因為宏只需定義一次,但可以多次使用,所以使用宏能增強程式的易讀性和可靠性。
(3)使用宏不需要額外的開銷,因為宏所代表的代碼只在宏出現的地方展開,因此不會引起程式中的跳轉。
(4)宏的參數對類型不敏感,因此你不必考慮將何種資料類型傳遞給宏。
需要注意的是,在宏名和括起參數的括弧之間絕對不能有空格。此外,為了避免在翻譯宏時產生歧義,宏體也應該用括弧括起來。例如,象下例中這樣定義CUBE宏是不正確的:
denne CUBE(x) x * x * x
對傳遞給宏的參數也要小心,例如,一種常見的錯誤就是將自增變數傳遞給宏,請看下例:
#include <stdio. h>
#include CUBE(x) (x * x * x)
void main (void);
void main (void)
{
int x, y;
x = 5;
y = CUBE( + +x);
printfC'y is %d/n" . y);
}
在上例中,y究竟等於多少呢?實際上,y既不等於125(5的立方),也不等於336(6* 7*8),而是等於512。因為變數x被作為參數傳遞給宏時進行了自增運算,所以上例中的CUBE宏實際上是按以下形式展開的:
y = ((++x) * (++x) * (++x));
這樣,每次引用x時,x都要自增,所以你得到的結果與你預期的結果相差很遠,在上例中,由於x被引用了3次,而且又使用了自增運算子,因此,在展開宏的代碼時,x實際上為8,你將得到8的立方,而不5的立方。
上述錯誤是比較常見的,作者曾親眼見過有多年C語言編程經驗的人犯這種錯誤。因為在程式中檢查這種錯誤是非常費勁的,所以你要給予充分的注意。你最好試一下上面的例子,親眼看一下那個令人驚訝的結果值(512)。
宏也可使用一些特殊的運算子,例如字串化運算子“#”和。串連運算子“##”。“#”運算子能將宏的參數轉換為帶雙引號的字串,請看下例:
define DEBUG_VALUE(v) printf(#v"is equal to %d./n",v)
你可以在程式中用DEBUG_VALUE宏檢查變數的值,請看下例:
int x=20;
DEBUG_VALUE(x);
上述語句將在螢幕上列印"x is equal to 20"。這個例子說明,宏所使用的“#”運算子是一種非常方便的調試工具。
“##”運算子的作用是將兩個獨立的字串串連成一個字串,詳見5.16。
請參見:
5.10 使用宏更好,還是使用函數更好?
5.16 串連運算子“##”有什麼作用?
5.17 怎樣建立對類型不敏感的宏?
5.18 什麼是標準預定義宏?
5.31 怎樣取消一個已定義的宏?
5.2 預先處理程式(preprocessor)有什麼作用?
C語言預先處理程式的作用是根據原始碼中的預先處理指令修改你的原始碼。預先處理指令是一種命令語句(如#define),它指示預先處理程式如何修改原始碼。在對程式進行通常的編譯處理之前,編譯器會自動運行預先處理程式,對程式進行編譯預先處理,這部分工作對程式員來說是不可見的。
預先處理程式讀入所有包含的檔案以及待編譯的原始碼,然後產生原始碼的預先處理版本。在預先處理版本中,宏和常量標識符已全部被相應的代碼和值替換掉了。如果原始碼中包含條件預先處理指令(如#if),那麼預先處理程式將先判斷條件,再相應地修改原始碼。
下面的例子中使用了多種預先處理指令:
# include <stdio. h>
# define TRUE 1
# define FALSE (!TRUE)
# define GREATER (a, b) ((a) > (b) ? (TRUE) : (FALSE))
# define PIG-LATIN FALSE
void main (void);
void main (void)
{
int x, y;
# if PIG_LATIN
printf("Easeplay enternay ethay aluevay orfay xnay:") ;
scanf("%d", &x) ;
printf("Easeplay enternay ethay aluevay orfay ynay:");
scanf("%d", &y);
#else
printf(" Please enter the value for x: ");
scanf("%d", &x);
printf("Please enter the value for y: ");
scanf("%d", &y);
# endif
if (GREATER(x, y) = = TRUE)
{
# if PIG- LATIN
printf("xnay islay eatergray anthay ynay!/n");
#else
printf {" x is greater than y! /n" ) ;
# endif
}
else
{
# if PIG_LATIN
printf ("xnay islay otnay eatergray anthay ynay!/n");
#else
printf ("x is not greater than y!/n");
# endif
}
}
上例通過預先處理指令定義了3個標識符常量(即TRUE,FALSE和PIG_LATIN)和一個宏(即GREATER(a,b)),並使用了一組條件編譯指令。當預先處理程式處理上例中的原始碼時,它首先讀入stdio.h標頭檔,並解釋其中的預先處理指令,然後把所有標識符常量和宏用相應的值和代碼替換掉,最後判斷PIG_LATIN是否為TRUE,並由此決定是使用拉丁文還是
使用英文。
如果PIG_LATIN為FALSE,則上例的預先處理版本將如下所示:
/ * Here is where all the include files
would be expanded. * /
void main (void)
{
int x, y;
printf("Please enter the value for X: ");
scanf("%d", &x);
printf("Please enter the value for y: ");
scanf("%d", &y),
if (((x) > (y) ? (1) : (!1)) == 1)
{
printf("x is greater than y!/n");
}
else
{
printf{"x is not greater than y!/n");
}
}
多數編譯器都提供了一個命令列選項,或者有一個獨立的預先處理程式,可以讓你只啟動預先處理程式並將原始碼的預先處理版本儲存到一個檔案中。你可以用這種方法查看原始碼的預先處理版本,這對調試與宏或其它預先處理指令有關的錯誤是比較有用的。
請參見:
5.3 怎樣避免多次包含同一個標頭檔?
5.4 可以用#include指令包含類型名不是".h"的檔案嗎?
5.12 #include <file>和#include"file"有什麼不同?
5.22 預先處理指令#pragma有什麼作用?
5.23 #line有什麼作用?
5.3 怎樣避免多次包含同一個標頭檔?
通過#ifndef和#define指令,你可以避免多次包含同一個標頭檔。在建立一個標頭檔時,你可以用#define指令為它定義一個唯一的標識符名稱。你可以通過#ifndef指令檢查這個標識符名稱是否已被定義,如果已被定義,則說明該標頭檔已經被包含了,就不要再次包含該標頭檔;反之,則定義這個標識符名稱,以避免以後再次包含該標頭檔。下述標頭檔就使用了這種技術:
# ifndef _FILENAME_H
#define _FILENAME_H
#define VER_NUM " 1. 00. 00"
#define REL_DATE "08/01/94"
#if _WINDOWS_
# define OS_VER "WINDOWS"
#else
#define OS_VER "DOS"
# endif
# endif
當預先處理程式處理上述標頭檔時,它首先檢查標識符名稱_FILENAME_H是否已被定義——如果沒有被定義,預先處理程式就對此後的語句進行預先處理,直到最後一個#endif語句;反之,預先處理程式就不再對此後的語句進行預先處理。
請參見:
5.4 可以用#include指令包含類型名不是".h"的檔案嗎?
5,12 #include <file>和#include“file”有什麼不同?
5.14 包含檔案可以嵌套嗎?
5.15 包含檔案最多可以嵌套幾層?
5. 4 可以用#include指令包含類型名不是".h"的檔案嗎?
預先處理程式將包含用#include指令指定的任意一個檔案。例如,如果程式中有下面這樣一條語句,那麼預先處理程式就會包含macros.inc檔案。
#include <macros.inc>
不過,最好不要用#include指令包含類型名不是".h"的檔案,因為這樣不容易區分哪些檔案是用於編譯預先處理的。例如,修改或調試你的程式的人可能不知道查看macros.inc檔案中的宏定義,而在類型名為".h"的檔案中,他卻找不到在macros.inc檔案中定義的宏。如果將macros.inc檔案改名為macros.h,就可以避免發生這種問題。
請參見:
5.3怎樣避免多次包含同一個標頭檔?
5.12#include<file>和#include“file”有什麼不同?
5,14包含檔案可以嵌套嗎?
5.15包含檔案最多可以嵌套幾層?
5.5 用#define指令說明常量有什麼好處?
如果用#define指令說明常量,常量只需說明一次,就可多次在程式中使用,而且維護程式時只需修改#define語句,不必一一修改常量的所有執行個體。例如,如果在程式中要多次使用PI(約3.14159),就可以象下面這樣說明一個常量:
#define PI 3.14159
如果想提高PI的精度,只需修改在#define語句中定義的PI值,就不必在程式中到處修改了。通常,最好將#define語句放在一個標頭檔中,這樣多個模組就可以使用同一個常量了。
用#define指令說明常量的另一個好處是佔用的記憶體最少,因為以這種方式定義的常量將直接進入原始碼,不需要再在記憶體中分配變數空間。
但是,這種方法也有缺點,即大多數偵錯工具無法檢查用#define說明的常量。
用#define指令說明的常量可以用#under指令取消。這意味著,如果原來定義的標識符(如NULL)不符合你的要求,你可以先取消原來的定義,然後重新按自己的要求定義一個標識符,詳見5.31。
請參見:
5.6用enum關鍵字說明常量有什麼好處?
5.7與用#define指令說明常量相比,用enum關鍵字說明常量有什麼好處?
5.31怎樣取消一個已定義的宏?
5.6 用enum關鍵字說明常量有什麼好處?
用enum關鍵字說明常量(即說明枚舉常量)有三點好處:
(1)用enum關鍵字說明的常量由編譯器自動產生,程式員不需要用手工對常量一一賦值。
(2)用enum關鍵字說明常量使程式更清晰易讀,因為在定義enum常量的同時也定義了一個枚舉類型標識符。
(3)在偵錯工具時通常可以檢查枚舉常量,這一點是非常有用的,尤其在不得不手工檢查標頭檔中的常量值時。
不過,用enum關鍵字說明常量比用#define指令說明常量要佔用更多的記憶體,因為前者需要分配記憶體來儲存常量。
以下是一個在檢測程式錯誤時使用的枚舉常量的例子:
enum Error_Code
{
OUT_OF_MEMORY,
INSUFFICIENT_DISK_SPACE,
LOGIC_ERROR,
FILE_NOT_FOUND
} ;
與用#define說明常量相比,用enum說明常量還有其它好處,這一點將在5.7中作更詳細的介紹。
請參見:
5.5 用#define指令說明常量有什麼好處?
5.7 與用#denne指令說明常量相比,用enum關鍵字說明常量有什麼好處?
5.7 與用#define指令說明常量相比,用enum關鍵字說明常量有什麼好處?
與用#define指令說明常量(即說明標識符常量)相比,用enum關鍵字說明常量(即說明枚舉常量)有以下幾點好處:
(1) 使程式更容易維護,因為枚舉常量是由編譯器自動產生的,而標識符常量必須由程式員手工賦值。例如,你可以定義一組枚舉常量,作為程式中可能發生的錯誤的錯誤號碼,請看下例:
enum Error_Code
{
OUT_OF_MEMORY,
INSUFFICIENT_DISK_SPACE,
LOGIC_ERROR,
FILE+NOT_FOUND
} ;
在上例中,OUT_OF_MEMORY等枚舉常量依次被編譯器自動賦值為0,1,2和3。
同樣,你也可以用#define指令說明類似的一組常量,請看下例:
#define OUT_OF_MEMORY 0
#define INSUFFICIENT_DISK_SPACE 1
#define LOGIC_ERROR 2
#define FILE_NOT_FOUND 3
上述兩例的結果是相同的。
假設你要增加兩個新的常量,例如DRIVE_NOT_READY和CORRUPT_FILE。如果常量原來是用enum關鍵字說明的,你可以在原來的常量中的任意一個位置插入這兩個常量,因為編譯器會自動賦給每一個枚舉常量一個唯一的值;如果常量原來是用#define指令說明的,你就不得不手工為新的常量賦值。在上面的例子中,你並不關心常量的實際值,而只關心常量的值是否唯一,因此,用enum關鍵字說明常量使程式更容易維護,並且能防止給不同的常量賦予相同的值。
(2)使程式更易讀,這樣別人修改你的程式時就比較方便。請看下例:
void copy_file(char* source_file_name, char * dest_file_name)
{
......
Error_Code,err;
......
if(drive_ready()!=TRUE)
err=DRIVE_NOT_READY;
......
}
在上例中,從變數err的定義就可以看出;賦予err的值只能是枚舉類型Error_Code中的數值。因此,當另一個程式員想修改或增加上例的功能時,他只要檢查一下Error_Code的定義,就能知道賦給err的有效值都有哪些。
注意:將變數定義為枚舉類型後,並不能保證賦予該變數的值就是枚舉類型中的有效值。
在上例中,編譯器並不要求賦予err的值只能是Error—Code類型中的有效值,因此,程式員自己必須保證程式能實現這一點。
相反,如果常量原來是用#define指令說明的,那麼上例就可能如下所示:
void copy_file(char *source *file,char *dest_file)
{
......
int err;
......
if(drive_ready()!=TRUE)
err=DRIVE_NOT_READY;
......
}
當另一個程式員想修改或增加上例的功能時,他就無法立即知道變數err的有效值都有哪些,他必須先檢查標頭檔中的#defineDRIVE_NOT_READY語句,並且寄希望於所有相關的常量都在同一個標頭檔中定義。
(3)使程式調試起來更方便,因為某些標識符偵錯工具能列印枚舉常量的值。這一點在偵錯工具時是非常用的,因為如果你的程式在使用枚舉常量的一行語句中停住了,你就能馬上檢查出這個常量的值;反之,絕大多數偵錯工具無法列印標識符常量的值,因此你不得不在標頭檔中手工檢查該常量的值。
請參見:
5.5 用#dehne指令說明常量有什麼好處?
5.6 用enum關鍵字說明常量有什麼好處?
5.8 如何使部分程式在示範版中失效?
如果你在為你的程式製作一個示範版,你可以通過預先處理指令使你的程式的一部分生效或失效。以下是一個用#if和#endif指令實現上述功能的例子:
int save_document(char * doc_name)
{
#if DEMO_VERSION
printf("Sorry! You can't save documents using the DEMO version Of
->this program!/n");
return(0);
#endif
}
在編寫示範版程式的原始碼時,如果插入了#define DEMO_VERSION這行語句,預先處理程式就會將上述save_document()函數中符合編譯條件的程式碼封裝含進來,這樣,使用示範版的使用者就無法儲存他們的檔案。更好的方法是,在編譯選項中定義DEMO_VERSION,這樣就不必修改程式的原始碼了。
上述技巧在許多不同的情況下都很有用。例如,如果你編寫的程式可能要在多種作業系統或作業環境下使用,你就可以定義一些象WINDOWS_VER,UNIX_VER和DOS_VER這樣的宏,通過它們指示預先處理程式如何根據具體條件將相應的程式碼封裝含到你的程式中去。
請參見:
5.32怎樣檢查一個符號是否已被定義?
5.9 什麼時候應該用宏代替函數?
見5.1-0。
請參見:
5.1什麼是宏(macro)?怎樣使用宏?
5.1-0使用宏更好,還是使用函數更好?
5.17怎樣建立對類型不敏感的宏?
5.10 使用宏更好,還是使用函數更好?
這取決於你的代碼是為哪種情況編寫的。宏與函數相比有一個明顯的優勢,即它比函數效率更高(並且更快),因為宏可以直接在原始碼中展開,而調用函數還需要額外的開銷。但是,宏一般比較小,無法處理大的、複雜的代碼結構,而函數可以。此外,宏需要逐行展開,因此宏每出現一次,宏的代碼就要複製一次,這樣你的程式就會變大,而使用函數不會使程式變大。
一般來說,應該用宏去替換小的、可重複的程式碼片段,這樣可以使程式運行速度更快;當任務比較複雜,需要多行代碼才能實現時,或者要求程式越小越好時,就應該使用函數。
請參見:
5.1 什麼是宏(macro)?怎樣使用宏?
5.17 怎樣建立對類型不敏感的宏?
5.11 在程式中加入注釋的最好方法是什麼?
大部分C編譯器為在程式中加註釋提供了以下兩種方法:
(1)分別是用符號“/*”和“*/”標出注釋的開始和結束,在符號“/*”和“*/”之間的任何內容都將被編譯器當作注釋來處理。這種方法是在程式中加入注釋的最好方法。
例如,你可以在程式中加入下述注釋:
/*
This portion Of the program contains
a comment that is several lines long
and is not included in the compiled
Version Of the program.
*/
(2)用符號“// ”標出注釋行,從符號“// ”到當前行末尾之間的任何內容都將被編譯器當作注釋來處理。當要加入一行獨立的注釋時,使用符號“//”是最方便的。但是,對於上面的例子,由於一段獨立的注釋中有4行內容,因此使用符號“//”是不合適的,請看下例:
// This portion Of the program contains
// a comment that is several lines long
// and is not included in the compiled
// Version Ofthe program.
需要注意的是,用符號"// "加入注釋的方法與ANSI標準是不相容的,許多版本較早的編譯器不支援這種方法。
請參見:
5.8 如何使部分程式在示範版中失效?
5.12 #include <file>和#include“file”有什麼不同?
在C程式中包含檔案有以下兩種方法:
(1)用符號“<”和“>”將要包含的檔案的檔案名稱括起來。這種方法指示預先處理程式到預定義的預設路徑下尋找檔案。預定義的預設路徑通常是在INCLUDE環境變數中指定的,請看下例:
INCLUDE=C:/COMPILER/INCLUDE;S:/SOURCE/HEADERS;
對於上述INCLUDE環境變數,如果用#include<file>語句包含檔案,編譯器將首先到C:/COMPILER/INCLUDE目錄下尋找檔案;如果未找到,則到S:/SOURCE/HEADERS
目錄下繼續尋找;如果還未找到,則到目前的目錄下繼續尋找。
(2)用雙引號將要包含的檔案的檔案名稱括起來。這種方法指示預先處理程式先到目前的目錄下尋找檔案,再到預定義的預設路徑下尋找檔案。
對於上例中的INCLUDE環境變數,如果用#include“file”語句包含檔案,編譯器將首先到目前的目錄下尋找檔案;如果未找到,則到C:/COMPILER/INCLUDE目錄下繼續尋找;如果還未找到,則到S:/SOURCE/HEADERS目錄下繼續尋找。
#include<file>語句一般用來包含標準標頭檔(例如stdio.h或stdlib.h),因為這些標頭檔極少被修改,並且它們總是存放在編譯器的標準包含檔案目錄下。#include“file”語句一般用來包含非標準標頭檔,因為這些標頭檔一般存放在目前的目錄下,你可以經常修改它們,並且要求編譯器總是使用這些標頭檔的最新版本。
請參見:
5.3 怎樣避免多次包含同一個標頭檔?
5. 4 可以用#include指令包含類型名不是“.h”的檔案嗎?
5.14 包含檔案可以嵌套嗎?
5.15 包含檔案最多可以嵌套幾層?
5.13 你能指定在編譯時間包含哪一個標頭檔嗎?
你可以通過#if,#else和#endif這組指令實現這一點。例如,標頭檔alloc.h和malloc.h的作用和內容基本相同,但前者供BorlandC++編譯器使用,後者供MicrosoftC++編譯器使用。如果你在編寫一個既支援BorlandC++又支援MicrosoftC++的程式,你就應該指定在編譯時間是包含alloc.h標頭檔還是包含malloc.h標頭檔,請看下例:
#ifdef __BORLANDC__
#include<alloc.h>
#else
#include<malloc.h>
#endif
當用BorlandC++編譯器處理上例時,編譯器會自動定義__BORLANDC__標識符名稱,因此alloc.h標頭檔將被包含進來;當用microsoftC++編譯器處理上例時,由於編譯器檢查到__BORLANDC__標識符名稱沒有被定義,因此malloc.h標頭檔將被包含進來。
請參見:
5.21怎樣判斷一個程式是用C編譯器還是用C++編譯器編譯的?
5.32怎樣檢查一個符號是否已被定義?
5.14 包含檔案可以嵌套嗎?
包含檔案可以嵌套,但你應該避免多次包含同一個檔案(見5.3)。
過去,人們認為標頭檔嵌套是一種不可取的編程方法,因為它增加了MAKE程式中的依賴跟蹤函數(dependencytrackingfunction)的工作負擔,從而降低了編譯速度。現在,通過引入先行編譯標頭檔(precompiledheaders,即所有標頭檔和相關的依賴檔案都以一種已被先行編譯的狀態儲存起來)這樣一種技術,許多流行的編譯器已經解決了上述問題。
許多程式員都喜歡建立一個自己的標頭檔,使其包含每個模組所需的每個標頭檔。這是一個標頭檔。
包含檔案可以嵌套,但你應該避免多次包含同一個檔案(見5.3)。
過去,人們認為標頭檔嵌套是一種不可取的編程方法,因為它增加了MAKE程式中的依賴跟蹤函數(dependencytrackingfunction)的工作負擔,從而降低了編譯速度。現在,通過引入先行編譯標頭檔(precompiledheaders,即所有標頭檔和相關的依賴檔案都以一種已被先行編譯的狀態儲存起來)這樣一種技術,許多流行的編譯器已經解決了上述問題。
許多程式員都喜歡建立一個自己的標頭檔,使其包含每個模組所需的每個標頭檔。這是一個標頭檔。
請參見:
5.3 怎樣避免多次包含同一個標頭檔?
5.4 可以用#include指令包含類型名不是“.h”的檔案嗎?
5.12 #include<file~和#include“file”有什麼不同?
5.15 包含檔案最多可以嵌套幾層?
5.15 包含檔案最多可以嵌套幾層?
儘管理論上包含檔案可以嵌套任意多層,但是,如果嵌套層數太多,編譯器就會用光它的堆棧空間。因此,實際的嵌套層數是有限的,它一般取決於你的硬體設定和編譯器的版本。
在編寫程式時,你應該避免過多的嵌套。一般來說,只有在確實需要時才嵌套包含檔案,例如建立一個標頭檔,使其包含每個模組所需的每個標頭檔。
請參見:
5.3 怎樣避免多次包含同一個標頭檔?
5.4 可以用#include指令包含類型名不是“.h”的檔案嗎?
5.12 #include<file>和#include“file”有什麼不同?
5.14 包含檔案可以嵌套嗎?
5.16 串連運算子“##”有什麼作用?
串連運算子“##”可以把兩個獨立的字串串連成一個字串。在C的宏中,經常要用到“##”運算子,請看下例:
#include<stdio.h>
#define SORT(X) sort_function # # X
void main(vOid);
void main(vOid)
{
char *array;
int elements,element_size;.
SORT(3) (array,elements,element_size);
}
在上例中,宏SORT利用“##”運算子把字串sort_function和經參數x傳遞過來的字串串連起來,這意味著語句
SORT(3)(array,elemnts,element_size);
將被預先處理程式轉換為語句
sort_function3(array,elements,element_size);
從宏SORT的用法中你可以看出,如果在運行時才能確定要調用哪個函數,你可以利用“##”運算子動態地構造要調用的函數的名稱。
請參見:
5.1什麼是宏?怎樣使用宏?
5.17怎樣建立對類型敏感的宏?