(Preprocessor) (Macro)
C++
(#)
備註:可依進度點選小節 9-1
9-2 9-3 9-4
9-1 前置處理器 前置處理器 前置處理器(Preprocessor) 前置處理器
9-1 前置處理器 前置處理器 前置處理器(Preprocessor) 前置處理器
在上圖中,為了開發一個大型的應用程式 所需的include檔案、原始檔案都不一定只有 一個。而在連結過程當中所加入的其它目 的檔案也可以是0到多個,但它的副檔名不 一定只有.obj,也可以是.lib、.dll等形式。
在前置處理的過程中,最常用的兩個前置 命令為#include與#define,分別說明如下。
前置處理器 前置處理器 前置處理器
前置處理器-#define與範例 與範例 與範例 與範例
#define最基本的功能就是使用有意義的名最基本的功能就是使用有意義的名最基本的功能就是使用有意義的名最基本的功能就是使用有意義的名 詞定義某些常數
詞定義某些常數 詞定義某些常數 詞定義某些常數。。。。
問題描述問題描述問題描述問題描述::::計算一個圓的面積與周長,此 時都會用到圓周率,希望用使個#define方 式完成。
前置處理器 前置處理器 前置處理器
前置處理器-#define與範例 與範例 與範例 與範例
#include "stdafx.h"
#include "stdio.h"
#include "stdlib.h"
#define PI 3.14159
//#define的使用範例的使用範例的使用範例的使用範例
int main()
{
float r;
printf("請輸入圓的半徑請輸入圓的半徑請輸入圓的半徑請輸入圓的半徑::::");
scanf("%f",&r);
printf("圓周長為圓周長為圓周長為圓周長為::::%f\n",2*PI*r);
printf("圓面積為圓面積為圓面積為圓面積為::::%f\n",PI*r*r);
system("pause");
}
前置處理器 前置處理器 前置處理器
前置處理器-#define
使用使用使用使用#define來定義一個名詞的語法示意圖來定義一個名詞的語法示意圖來定義一個名詞的語法示意圖來定義一個名詞的語法示意圖 如下
如下 如下 如下::::
前置處理器 前置處理器 前置處理器
前置處理器-#define
事實上事實上事實上事實上,,,,使用使用使用使用#define PI之後之後之後之後,,,,前置處理器會搜尋程式中前置處理器會搜尋程式中前置處理器會搜尋程式中前置處理器會搜尋程式中 所有的所有的
所有的所有的PI,,,,然後將它們替換成然後將它們替換成然後將它們替換成然後將它們替換成3.14159。。。。這也就是說這也就是說這也就是說這也就是說,,,,在編在編在編在編 譯之前主程式就會被前置處理器處理成如下的程式碼
譯之前主程式就會被前置處理器處理成如下的程式碼 譯之前主程式就會被前置處理器處理成如下的程式碼 譯之前主程式就會被前置處理器處理成如下的程式碼::::
int main()
{
float r;
printf("請輸入圓的半徑請輸入圓的半徑請輸入圓的半徑:請輸入圓的半徑::");:
scanf("%f",&r);
printf("圓周長為圓周長為圓周長為:圓周長為::%f\n",2*3.14159*r);:
printf("圓面積為圓面積為圓面積為:圓面積為::%f\n",3.14159*r*r);:
system("pause");
}
前置處理器 前置處理器 前置處理器
前置處理器-#define
雖然前置處理器在此只做簡單的文字取代雖然前置處理器在此只做簡單的文字取代雖然前置處理器在此只做簡單的文字取代雖然前置處理器在此只做簡單的文字取代 工作
工作 工作
工作,,,,但仍需要注意幾件事但仍需要注意幾件事但仍需要注意幾件事但仍需要注意幾件事::::
前置處理器並不屬於前置處理器並不屬於前置處理器並不屬於前置處理器並不屬於C/C ++編譯器的一部份編譯器的一部份編譯器的一部份編譯器的一部份,,,, 所以它不是用
所以它不是用 所以它不是用
所以它不是用C/C++的語法來撰寫的語法來撰寫的語法來撰寫。的語法來撰寫。。。
前置處理器敘述可寫在程式中的任何地方前置處理器敘述可寫在程式中的任何地方前置處理器敘述可寫在程式中的任何地方前置處理器敘述可寫在程式中的任何地方,,,,但但但但 為了能讓程式設計人員容易了解與維護
為了能讓程式設計人員容易了解與維護 為了能讓程式設計人員容易了解與維護
為了能讓程式設計人員容易了解與維護,,,,因此因此因此因此 建議寫在檔頭的地方
建議寫在檔頭的地方 建議寫在檔頭的地方 建議寫在檔頭的地方。。。。
前置處理器是以行為單位並非如前置處理器是以行為單位並非如前置處理器是以行為單位並非如前置處理器是以行為單位並非如C/C++的敘述的敘述的敘述的敘述 是用分號
是用分號 是用分號
是用分號((((;)))來結束)來結束來結束來結束。。。。
前置處理器 前置處理器 前置處理器
前置處理器-#define
雖然使用雖然使用雖然使用雖然使用#define來定義一個數值必須使用來定義一個數值必須使用來定義一個數值必須使用來定義一個數值必須使用 前置處理器的語法來撰寫
前置處理器的語法來撰寫 前置處理器的語法來撰寫
前置處理器的語法來撰寫,,,,但是簡單的四但是簡單的四但是簡單的四但是簡單的四 則運算仍然接受
則運算仍然接受 則運算仍然接受
則運算仍然接受,,,,因此如果定義的數值就因此如果定義的數值就因此如果定義的數值就因此如果定義的數值就 可以是某種運算式
可以是某種運算式 可以是某種運算式
可以是某種運算式,,,,例如例如例如例如::::
#define WIDTH 80
#define LENGTH WIDTH + 20
#define 與 與 與 與 const
C/C++會立即檢查會立即檢查會立即檢查會立即檢查const 敘述的語法敘述的語法敘述的語法敘述的語法;;;;而而而而#define指指指指 示要到使用巨集
示要到使用巨集 示要到使用巨集
示要到使用巨集(請參考請參考請參考請參考9-2)時才會檢查時才會檢查時才會檢查時才會檢查。。。。
const 是採用是採用是採用是採用C/C++ 語法語法語法,語法,,,#define則有自己的語則有自己的語則有自己的語則有自己的語 法
法 法 法
const 適用適用適用適用C/C++ 變數的有效範圍法則變數的有效範圍法則變數的有效範圍法則,變數的有效範圍法則,,而用,而用而用而用
#define所定義的常數會延伸到整個程式所定義的常數會延伸到整個程式所定義的常數會延伸到整個程式。所定義的常數會延伸到整個程式。。。
#define只能定義簡單的數值常數只能定義簡單的數值常數只能定義簡單的數值常數,只能定義簡單的數值常數,,而,而而cons t可定義而 可定義可定義可定義 每一種
每一種 每一種
每一種C/C++的資料型別的資料型別的資料型別的資料型別,,,,包含複雜的陣列包含複雜的陣列包含複雜的陣列包含複雜的陣列(請參請參請參請參 考考
考考10章章章章)、、、、結構結構結構結構(請參考第請參考第請參考第請參考第13章章章章)以及類別以及類別以及類別以及類別(請參考第請參考第請參考第請參考第 15章章章章)等等等等。。。。
前置處理器 前置處理器 前置處理器
前置處理器-#include
#include敘述之主要目的是讓我們將某個程敘述之主要目的是讓我們將某個程敘述之主要目的是讓我們將某個程敘述之主要目的是讓我們將某個程 式檔或標頭檔引入到目前的程式內
式檔或標頭檔引入到目前的程式內 式檔或標頭檔引入到目前的程式內
式檔或標頭檔引入到目前的程式內,,,,這樣這樣這樣這樣 一來目前所設計的程式就可以使用該檔案 一來目前所設計的程式就可以使用該檔案 一來目前所設計的程式就可以使用該檔案 一來目前所設計的程式就可以使用該檔案 內的資料或函式
內的資料或函式 內的資料或函式
內的資料或函式,,,,語法如下語法如下語法如下語法如下::::
<1> #include "檔案名稱檔案名稱檔案名稱" 檔案名稱
<2> #include <檔案名稱檔案名稱檔案名稱>檔案名稱
前置處理器 前置處理器 前置處理器
前置處理器-#include
無論使用上述的那一種方法無論使用上述的那一種方法無論使用上述的那一種方法無論使用上述的那一種方法,,,,系統都將會系統都將會系統都將會系統都將會 在目前的目錄下尋找所指定的檔案
在目前的目錄下尋找所指定的檔案 在目前的目錄下尋找所指定的檔案
在目前的目錄下尋找所指定的檔案。。。。如果如果如果如果 找不到找不到
找不到找不到,,,,則會再去系統所設定的目錄底下則會再去系統所設定的目錄底下則會再去系統所設定的目錄底下則會再去系統所設定的目錄底下 尋找
尋找 尋找
尋找。。。。如果您想為自己的專案設定特殊的如果您想為自己的專案設定特殊的如果您想為自己的專案設定特殊的如果您想為自己的專案設定特殊的 include檔案目錄檔案目錄檔案目錄檔案目錄,,,,則請先在則請先在則請先在則請先在「「「「方案總管方案總管方案總管方案總管 中中
中中」」」」點選專案名稱點選專案名稱點選專案名稱點選專案名稱,,,,然後在點選然後在點選然後在點選然後在點選IDE上的上的上的上的 [檢視檢視檢視檢視]→→→→[屬性頁屬性頁屬性頁]選項屬性頁 選項選項,選項,,,此時可看到如圖此時可看到如圖此時可看到如圖9-3此時可看到如圖 的畫面的畫面
的畫面的畫面。。。。
前置處理器 前置處理器 前置處理器
前置處理器-#include
1.加入其它的 include目錄
2.加入
3.瀏覽目錄 include
前置處理器 前置處理器 前置處理器
前置處理器-#include
事實上前置處理器處理事實上前置處理器處理事實上前置處理器處理事實上前置處理器處理#include的方法跟處的方法跟處的方法跟處的方法跟處 理
理
理理#define的方法一樣的方法一樣的方法一樣的方法一樣,,,,就是把所要引用的就是把所要引用的就是把所要引用的就是把所要引用的 檔案內容加到目前的檔案當中
檔案內容加到目前的檔案當中 檔案內容加到目前的檔案當中
檔案內容加到目前的檔案當中 。。。。
前置處理器 前置處理器 前置處理器
前置處理器-#include範例 範例 範例 範例
在在在在myheader.h檔案當中只有一行如下的定義檔案當中只有一行如下的定義檔案當中只有一行如下的定義:檔案當中只有一行如下的定義:::
#define PI 3.14159
而程式設計時使用而程式設計時使用而程式設計時使用而程式設計時使用
#include "myheader.h"
int main()
{
…
}
前置處理器 前置處理器 前置處理器
前置處理器-#include範例 範例 範例 範例
經過前置處理的的處理之後程式就會變經過前置處理的的處理之後程式就會變經過前置處理的的處理之後程式就會變經過前置處理的的處理之後程式就會變 成
成 成 成::::
#define PI 3.14159
int main()
{
…
}
前置處理器 前置處理器 前置處理器
前置處理器-#include範例 範例 範例 範例
如果只是簡單的數值定義如果只是簡單的數值定義如果只是簡單的數值定義如果只是簡單的數值定義,,,,那麼使用那麼使用那麼使用那麼使用const 或許還要比使用
或許還要比使用 或許還要比使用
或許還要比使用#define來的好用來的好用來的好用來的好用,,,,除非您除非您除非您除非您 想要將很多的
想要將很多的 想要將很多的
想要將很多的#define放在標頭檔之中放在標頭檔之中放在標頭檔之中放在標頭檔之中。。。。
9-2 巨集 巨集 巨集(Macro) 巨集
#define的功能不是只有定義簡單的數值而的功能不是只有定義簡單的數值而的功能不是只有定義簡單的數值而的功能不是只有定義簡單的數值而 已
已 已
已,,,,它還具有可以定義更複雜的功能它還具有可以定義更複雜的功能它還具有可以定義更複雜的功能它還具有可以定義更複雜的功能,,,,稱稱稱稱 之為巨集之為巨集
之為巨集之為巨集。。。。巨集的定義可以是簡單的字串巨集的定義可以是簡單的字串巨集的定義可以是簡單的字串巨集的定義可以是簡單的字串 或是功能更複雜的函式
或是功能更複雜的函式 或是功能更複雜的函式 或是功能更複雜的函式。。。。
巨集 巨集 巨集
巨集(Macro)範例 範例 範例 1 範例
#define Ierror printf("輸入錯誤,請輸入合理的整數範圍!
\n");
如此一來,在程式中就可以使用Ierror來取代printf("輸入錯 誤,請輸入合理的整數範圍\n");這一行程式。例如:
if (d<0 || d>1000)
Ierror
依據先前使用過#define的經驗可以得知,經過前置處理器 之後的程式就會被展開成為:
if (d<0 || d>1000)
printf("輸入錯誤,請輸入合理的整數範圍!\n");
巨集 巨集 巨集
巨集(Macro)範例 範例 範例 2 範例
問題描述問題描述問題描述
問題描述::::定義一個巨集來計算圓的面積與 周長,必須可以傳入圓的半徑。
執行畫面 執行畫面執行畫面 執行畫面:
巨集 巨集 巨集
巨集(Macro)範例 範例 範例 2 範例
#include "stdafx.h"
#include "stdio.h"
#include "stdlib.h"
#define PI 3.14159
//----定義巨集
#define AreaOfCircle(r) PI*r*r
#define Perimeter(r) (2*PI*r)
int main()
{
float r;
printf("請輸入圓的半徑:");
scanf("%f", &r);
//---使用巨集
printf("圓周長為:%f\n", Perimeter(r));
printf("圓面積為:%f\n", AreaOfCircle(r));
system("pause");
}
巨集 巨集 巨集
巨集(Macro)範例 範例 範例 2 範例
在上述的程式中可以看到定義巨集與呼叫 巨集的寫法,經過前置處理器的處理之 後,事實上程式碼
printf("圓周長為:%f\n",Perimeter(r));
將會被取代為
printf("圓周長為:%f\n",(2*PI*r));
而其中的PI又會被展開成3.14159。所以最後 展開的程式碼即為:
printf("圓周長為:%f\n",(2*3.14159*r));
9-2 巨集 巨集 巨集(Macro) 巨集
在定義巨集時有一些注意事項,那就是小括號與 運算優先權問題,例如,上述程式定義圓面積的 巨集程式碼:
#define AreaOfCircle(r) PI*r*r
傳入的參數規劃,需使用小 括號,亦可使用多個參數
以空白隔開
巨集名稱
定義巨集的內容,
使用C/C++語法
9-2 巨集 巨集 巨集(Macro) 巨集
如果想要使用多參數,則與函式相同,在 參數之間使用逗號分開(不需要為參數宣告 資料型別,因為它的功能只是做文字的替 換而已)。
所有的程式看起來都很正常,但是為何還 說需要注意小括號與運算優先權問題呢?
以求得圓面積的巨集為例,如果在使用巨 集時所使用的程式碼為:
AreaOfCircle(8+2);
請問,計算後的結果為何?
9-2 巨集 巨集 巨集(Macro) 巨集
感覺上應該為PI*10*10=314.159, 但實際 上卻不然,因為巨集的使用方式並不是函 式,傳入的引數並不會自動相加,而只是
「替換」而已,因此程式碼即為:
PI*8+2*8+2;
顯而易見地,它與原意PI*(8+2)*(8+2)相去 甚遠。想要解決這樣的問題,只要改成下 列方式即可:
#define AreaOfCircle(r) PI*(r)*(r)
9-2 巨集 巨集 巨集(Macro) 巨集
上述是設計傳入一個參數的巨集範例,如 果想要傳入2個甚至更多個參數時該如何設 計呢?事實上是同樣的種格式,例如一個 簡單的加法巨集:
#define ADD(x,y) ((x)+(y))
另外,由前置處理器是以每一行而非以分 號來分行,因此對於過長的敘述想要分成 兩行以上來撰寫時則可以使用\(Back Slash) 來連接。
巨集 巨集 巨集
巨集(Macro)範例 範例 範例 3 範例
問題描述問題描述問題描述問題描述::::定義一個巨集比較傳入兩個數 的大小並列印其關係。
執行畫面執行畫面執行畫面執行畫面:
巨集 巨集 巨集
巨集(Macro)範例 範例 範例 3 範例
#include "stdafx.h"
#include "stdio.h"
#include "stdlib.h"
#define compare(a,b) \
if (a>b) \
printf("a > b\n"); \
else if(a<b) \
printf("a < b\n"); \
else \
printf("a = b\n");
int main()
{ int a,b;
printf("請輸入請輸入請輸入請輸入a與與與與b兩整數兩整數兩整數兩整數::::");
scanf("%d %d",&a,&b);
compare(a,b);
system("pause");
}
必須使用\符號,
否則會發生錯誤
巨集 巨集 巨集
巨集(Macro) 、 、 、 、函式何時使用 函式何時使用 函式何時使用? 函式何時使用
使用#define的方式會讓程式碼變的較長,
當然編譯的時間也相對會變多,但真正執 行時的速度會較快。
相反地,使用常數或函式的程式較短,但 是在執行時可能需要處理引數、控制權的 轉換,因而執行速度較慢。
換句話說,巨集與函式使用的抉擇就是空 間與時間。
9-3 使用標頭檔 使用標頭檔 使用標頭檔 使用標頭檔
打從第2章開始就一直使用#include來引入 標頭檔,在加上本章所介紹的相關知識之 後是否對為何使用標頭檔有更深入的了 解,而在本節中將要介紹如何使用自行設 計的標頭檔。
為何需要使用自行設計的標頭檔呢?這跟 我們使用已設計好的標頭檔是一樣的道 理。例如:在一個較大型的應用程式開發 當中,有一些定義、巨集想要共用時就可 以透過標頭檔的引入解決這樣的問題。
使用標頭檔範例 使用標頭檔範例 使用標頭檔範例 使用標頭檔範例
問題描述問題描述問題描述問題描述::::定義一個巨集來計算圓的面積 與周長,必須可以傳入圓的半徑,並將這 些定義放在標頭檔中。
為了要自行建立標頭檔,因此請點選IDE 上的[檔案]→[新增]→[檔案]選項,然後選 取[標頭檔(.h)]項目,請參考圖9-6。
使用標頭檔範例 使用標頭檔範例 使用標頭檔範例 使用標頭檔範例
圖9-6 新增標頭檔的畫面
按下[開啟]按鈕之後即可在程式編輯器中看到新 增的檔案,只要此處編輯其定義即可,請參考 圖9-7。
使用標頭檔範例 使用標頭檔範例 使用標頭檔範例
使用標頭檔範例 -圖 圖 圖9-7 圖
預設的標頭檔 名稱
使用標頭檔範例 使用標頭檔範例 使用標頭檔範例
使用標頭檔範例 -[另存 另存 另存 另存
Header1.h為 為 為]選項 為 選項 選項 選項
使用標頭檔範例 使用標頭檔範例 使用標頭檔範例
使用標頭檔範例- Header1.h
定義PI、巨集以及函式的原型如下:
#include "stdio.h"
#include "stdlib.h"
#define PI 3.14159
//----定義巨集
#define AreaOfCircle(r) PI*(r)*(r)
#define Perimeter(r) (2*PI*(r))
//---定義函式原型
float testfun1(float r);
void testfun2();
使用標頭檔範例 使用標頭檔範例 使用標頭檔範例 使用標頭檔範例
可以一樣透過#include來引用,例如:
#include "stdafx.h"
#include "MyHeader.h"
int main()
{ float r;
printf("請輸入圓的半徑:");
scanf("%f",&r);
printf("圓周長為:%f\n",Perimeter(r));
printf("圓面積為:%f\n",AreaOfCircle(r));
system("pause");
}
使用標頭檔範例 使用標頭檔範例 使用標頭檔範例 使用標頭檔範例
可以一樣透過#include來引用,例如:
//---以下設計函式---
float testfun1(float r)
{
//--在函式中使用巨集
return (Perimeter(r));
}
9-4 條件式編譯 條件式編譯 條件式編譯( 條件式編譯 ( (Conditional ( Compilation) ) ) )
所謂的條件是編譯就是可以透過前置指令 來引導編譯器作某一個部分程式碼的編 譯。例如:您想要開發一個Linux/
Windows 平台上都可以執行的C程式(就是 有跨平台的問題),雖然理論上C的可攜性 很高,只要將原始碼拿到不同平台上重新 編譯就可以了。
但實際上由於作業系統或C編譯器上的些 許差異存在,因此可在同一個原始檔案中 標示編譯的條件讓編譯器可以選擇正確的 程式碼做編譯。
9-4 條件式編譯 條件式編譯 條件式編譯( 條件式編譯 ( (Conditional ( Compilation) ) ) )
條件式編譯的成員:#if、#endif、#else、
#elif、#ifdef 以及#ifndef。
各種指示詞的用法說明如下:
每一個#if都必須搭配一個#endif。
#elif可以出現任意個在#if與#endif中,但是
#else最多就只能有一個。
#if、#elif、#else以及 #endif 指示詞可以行成 巢狀結構。
#ifdef用來檢查某一名詞是否有經過#define定 義;而#ifndef則用來檢查一名詞是否沒有被
#define定義
9-4 條件式編譯 條件式編譯 條件式編譯( 條件式編譯 ( (Conditional ( Compilation) ) ) )
#ifdef的用法為:
#ifdef identifier 用來檢查是否有#define
identifier這樣的敘述存在,事實上也可以用#if 來完成,如下列所示:
#if defined identifier同理,#ifndef identifier也 就與#if !defined identifier是相同的意思。
條件式編譯 條件式編譯 條件式編譯
條件式編譯( ( (Conditional ( Compilation) ) ) ) 範例 範例 範例 範例
問題描述問題描述問題描述問題描述::::使用條件式編譯的指令來展示如何 運用。
條件式編譯 條件式編譯 條件式編譯
條件式編譯( ( (Conditional ( Compilation) ) ) ) 範例 範例 範例 範例
#include "stdafx.h"
#include "stdlib.h"
#include "stdio.h"
#define OS //---定義OS
int main()
{
#if defined OS //--檢查是否有定義OS
#define STACK 200 //---如果有定義OS,則STACK定為
printf("OS有定義\n"); //--可以撰寫C程式碼
#else
#define STACK 100 //---否則STACK定為
printf("OS未定義\n");
#endif
printf("STACK=%d\n",STACK);
system("pause");
條件式編譯 條件式編譯 條件式編譯
條件式編譯( ( (Conditional ( Compilation) ) ) ) 範例 範例 範例 範例
#undef OS //--使用#undef取消#define對OS的定義
#undef STACK //--使用#undef取消#define對STACK的定義
#if defined OS //---OS已被#undef取消定義了
#define STACK 200
printf("OS有定義\n");
#else
#define STACK 100
printf("OS未定義\n");
#endif
printf("STACK=%d\n",STACK);
system("pause");
}
有傳回值的函式設計與使用
宣告時必須指定傳回的資料型態,例如:
int getsum()
使用return敘述傳回資料時可以使用小括號,也 可以不使用,例如: return(ret_data) 或是return
ret_data來回傳資料。其中的ret_data必須是函式
規劃的傳回資料型別,或是允許隱含資料轉換 的相容資料型別,否則將會發生錯誤。極建議程式設計時能統一函式宣告與回傳的資 料型別,因為如果回傳的值過大而造成溢位
(Overflow)的錯誤發生時並不會引發編譯時期或
執行時期的錯誤。但是執行的結果就會不符合 預期。有傳回值的函式設計與使用
在主程式中呼叫有傳回值的函式一樣是直接使 用函式的名稱即可,但是因為所呼叫的函式有 傳回值,因此可以使用一個同型別的資料變數 來接收(如果型別不同也可以透過隱含或是強制 型別轉換的方式),例如上述程式中:
int sum=getsum();
這一行程式就是在宣告整數變數sum時順便給予 初始值,而初始值的來源就是getsum()函式的回 傳值。事實上,如果傳回的值無其他作用而只 是要簡單的顯示,那麼本範例的程式也可以簡 化成一行: