C語言編譯中,什么時候應該使用32位編譯程序?
32位編譯程序應該在32位操作系統上使用。由32位編譯程序生成的32位程序比16位程序運行得更快,這正是任何32位的東西都很熱門的原因。有那么多不同版本的Microsoft Windows,它們和哪種編譯程序組成最佳搭配呢?
Windows 3.1和Windows for Workgroups 3.11是16位的操作系統;Microsoft Visual C++1.x是16位編譯程序,它所生成的程序能在Windows 3.1上運行。Microsoft Windows NT和Windows 95是32位操作系統;Microsoft Visual C++2.o是32位編譯程序,它所生成的32位程序能在這兩種操作系統上運行。由Visual C++1.x生成的16位程序不僅能在Windows 3.1上運行,也能在Windows NT和Windows 95上運行。
然而,由Visual 2.O生成的32位程序無法在Windows 3.1上運行。這就給Microsoft提出了一個難題——它希望每個人都使用它的32位編譯程序,但是有6千萬臺計算機所使用的Windows版本無法運行32位程序。為了克服這個障礙,Microsoft設計了一個叫做Win32s的轉換庫,用來完成由32到16位的轉換,從而使由Visual C++生成的32位程序能在
Windows 3.1和Windows for Workgroups上運行。Win32s是專門為在Windows 3.1(和WFW)上運行而設計的,它不能用在Windows NT和Windows 95上,因為它們不需要為運行32位程序作什么轉換。有了Win32s,你就可以用Visual C++2.0生成一個既能在Windows?3.1(和WFW)上運行,又能在Windows NT上運行的程序了。
最后還有一個問題,即編譯程序本身的問題。Visual C++1.x是一個16位Windows程序,它既能在Windows 3.1上運行并生成16位程序,也能在Windows 95和Windows NT上運行并生成同樣的程序。但是,作為一個32位程序,Visual C++2.O無法在Windows 3.1上運行,即使裝上了Win32s也無法運行,因為出于方便的目的,Microsoft認為在啟動Visual C++2.O之前,你一定運行在Windows 95或Windows NT上。
總而言之,在Windows 3.1,Windows for Workgroups,Windows 95或Windows NT上運行Visual c++1.x(更新的版本為1.51)而生成的16位程序能在任何版本的Windows上運行。由Visual C++2.O生成的32位程序在Windows 95或Windows NT上運行得很快,但它在Windows 3.1(和WFW)下也能很好地運行。
對于Borland C/C++,Borland的Turbo C++3.1版是版本較新的16位編譯程序,Borland c++4.5版(注意該名稱中沒有"Turbo一詞)是32位Windows編譯程序。這兩種編譯程序都包含編譯程序、Borland的OWL C++類和一個出色的集成化調試程序。
C語言異常處理和結構化異常處理有什么區別?
總的來說,結構化異常處理和異常處理之間的區別就是Microsoft對異常處理程序在實現上的不同。所謂的“普通”C++異常處理使用了三條附加的c++語句:try,catch和throw。這些語句的作用是,當正在執行的程序出現異常情況時,允許一個程序(異常處理程序)試著找到該程序的一個安全出口。異常處理程序可以捕獲任何數據類型上的異常情況,包括C++類。這三條語句的實現是以針對異常處理的ISO WG21/ANSI X3J16 C++標準為基礎的,Microsoft C++支持基于這個標準的異常處理。注意,這個標準只適用于C++,而不適用于C。結構化異常處理是Microsoft c/c++編譯程序的一種功能擴充,它的最大好處就是它對C和C++都適用。Microsoft的結構化異常處理使用了兩種新的結構:try—except和try-finally。這兩種結構既不是ANSI c++標準的子集,也不是它的父集,而是異常處理的另一種實現(Microsoft會繼續在這方面努力的)。try—except結構被稱為異常處理(exception handling),tryfinally結構被稱為終止處理(termination handling)。try—except語句允許應用程序檢索發生異常情況時的機器狀態,在向用戶顯示出錯信息時,或者在調試程序時,它能帶來很大的方便。在程序的正常執行被中斷時,try—finally語句使應用程序能確保去執行清理程序。盡管結構化異常處理有它的優點,但它也有缺點——它不是一種ANSI標準,因此,與使用ANSI異常處理的程序相比,使用結構化異常處理的程序的可移植性要差一些。如果你想編寫一個真正的C++應用程序,那么你最好使用ANSI異常處理(即使用try,catch和throw語句)。
怎術防止C語言程序用盡內存?
如果你使用了大量的靜態數據,那么你應該考慮使用動態內存分配技術。通過使用動態內存分配技術(即使用malloc()和calloc()函數),你可以在需要時動態地分配內存,在不需要時釋放內存。這種方法有幾個好處:首先,動態內存分配技術會使程序的效率更高,因為程序只在需要時才使用內存,并且只使用所需大小的內存空間。這樣,靜態和全局變量就不會占用大量的空間。其次,你可以通過檢查malloc()和calloc()函數的返回值來掌握內存不足的情況。如果你的程序特別大,你可能要使用覆蓋管理程序或DOS擴展程序,或者使用其它內存分配機制,例如EMS和XMS(有關內容見18.13和18.14)。
連接過程中出現DGROUP:group exceeds 64K消息是怎么回事?
如果在連接時看到這條出錯消息,那是連接程序在指示數據(DGROUP)段中的近程數據(靜態數組元素,全局變量等)超過了64KB。解決這個問題的辦法有以下幾種:- 減少一些全局變量;
- 減少程序的棧;
- 用動態存儲分配技術為數據元素分配動態內態,而不把它們定義為靜態型或全局型;
- 把數據元素說明為遠程型而不是近程型。
減少一些全局變量可能要求對程序的內部結構進行重新設計,但這是值得的。從本質上講,全局變量的維護很可能是一場惡夢,因此只有在確實需要時才能使用全局變量。如果你分配了大量的空間作為棧空間,那么你應該試試減少棧空間,看看是否能增加可用的內存。如果你在程序中使用了大量靜態數據,那么你應該想辦法重新安排這些靜態數據,并且為它們分配動態的而不是靜態的內存。這種技術可以釋放近程堆,并且使你能從遠程堆中分配內存(見18.15中有關近程堆和遠程堆的討論)。
如果一個程序包含多個源文件,怎樣使它們都能正常工作?
編譯程序中包含一個MAKE工具(通常叫做MAKE.EXE,NMAKE.EXE或其它類似的名字),其作用是記錄項目以及組成項目的源文件之間的依賴關系。下面是一個典型的MAKE文件的例子。myapp. ohj :?????? myapp. c?????????????? myapp. h
?????????????????? cl-c myapp. c
utility. obj :???? utility. c??????????????? myapp. h
?????????????????? cl-c utility. c
myapp, exe :?????? myapp. obj?????????????? utility. obj
?????????????????? cl myapp. obj??????????? utility. obj
這個例子表明myapp.obj依賴于myapp.c和myapp.h,utility.obj依賴于utility.c和myapp.h,myapp.exe依賴于myapp.obj和utility.obj。在表示依賴關系的每一行下面,都附有一條對相應的目標進行重新編譯或重新連接的編譯程序命令。例如,myapp.obj是通過執行下面的命令行重新生成的:
??? cl-c myapp.c
在上面的例子中,只有在myapp.c或myapp.h的時間標志晚于myapp.obj的時間標志時,myapp.obj才會被重新編譯。同樣,只有在utility.c或myapp.h的時間標志晚于utility.obj的時間標志時,utility.obj才會被重新編譯;只有在myapp.obj或utility.obj的時間標志晚于myapp.exe的時間標志時,myapp.exe才會被重新連接。
如果一個大型項目包含著源文件依賴關系,那么用MAKE文件來處理是非常方便的。
MAKE工具及其相關的命令和實現因編譯程序的不同而不同——關于如何使用MAKE工具,你可以查閱你的編譯程序文檔。
今天,大多數編譯程序都帶有集成開發環境,你可以在其中用項目文件來管理程序中的多個源文件,如果你有一個集成環境,你就不必去了解MAKE工具的復雜用法,并且可以很方便地管理項目中的源文件,因為集成環境會為你記錄所有的源文件依賴關系。
可以把多個庫函數包含在同一個源文件中嗎?為什么要建立一個庫?
可以把多個庫函數包含在同一個源文件中嗎?
在同一個源文件中,你想要定義多少個函數,就可以定義多個函數,并且可以把它們都包含到一個庫中——然而,在小組開發環境中連接程序和共享源文件時,這種編程風格存在著嚴重的缺陷。當你在一個源文件中包含多個庫函數時,這些函數會被編譯到同一個目標(.obj)文件中。當連接程序要把其中的一個函數連接到程序中去時,目標文件中的所有函數都將被連接進來---不管程序是否用到它們。如果這些函數是無關的(在它們的定義中沒有相互調用),那么會因為把不需要的代碼連接進來而浪費寶貴的程序空間,見18.7中的例子。這就是要把庫函數放到各自的源文件中的原因之一。
另一個原因是為了在小組開發環境下便于進行代碼共享。使用獨立的源文件能使小組程序員上交和收回單獨一個函數,而不必先鎖住源文件中的一些函數,然后才能修改源文件中的其它函數。
為什么要建立一個庫?
建立一個數據庫是為了把可重復使用的函數放在一起,供其它程序員和程序共享。例如,你的幾個程序可能都會用到一些通用的功能函數,你不必在每個程序中都復制這些源代碼,而只需把這些函數集中到一個函數庫中,然后用連接程序把它們連接到你的程序中去。這種方法有利于程序的維護,因為你可以在一個集中的地方而不是幾個分散的地方維護你的函數。如果你在小組環境中工作,那么你應該把你的可重復使用的函數放到一個庫中,這樣其它小組成員就可以把你的函數連接到他們的程序中去,從而節省了他們復制或從頭開始寫這些函數的時間。此外,在一個包含幾個模塊的大項目中,可以把那些自始至終都要用到的“框架”支持函數包含到一個庫中。
編譯程序中包含一個庫管理器(通常叫做LIB.EXE或其它類似的名字),可用來在函數庫中增減目標代碼模塊(.obj)。有些編譯程序允許你在它們的集成開發環境中維護你的庫,而不必人為地啟動庫管理器。無論如何,你都應該參考一下18.7和18.8。其中有一些有關建庫的重要信息和有用的技巧。
當一個庫被連接到目標上時,庫中的所有函數是否都會被加到一個.EXE文件中?
不會。當啟動連接程序時,它會尋找“未定義的外部函數”,也就是說,它將在每一個庫文件中查找源代碼文件中未定義的函數。當它找到一個未定義的外部函數后,它會引入包含該函數定義的目標代碼。(obj)。不幸的是,如果這個函數是在一個包含其它函數定義的源文件中被編譯的話,那么這些函數也會被包含進來,你的可執行代碼中將包含一些不需要的代碼。因此,將庫函數放到各自的源文件中是很重要的——否則會浪費寶貴的程序空間。有些編譯程序包含特殊的“精明的”連接程序,這些連接程序能查出不需要的函數并去掉它們,從而使這些函數不再進入你的程序。
下面舉一個例子:假設有兩個源文件,分別為libfunc1.c和libfunc2.c,它們所包含的函數都要被放到一個庫中。源文件libfunc1.c包含以下兩個函數:
??? void func_one ()
??? {
??? ?? ...
??? }
??? void rune_two()
??? {
??? ?? ...
??? }
源文件libfunc2.c包含以下函數:
??? void func_three()
??? {
?????? ...
??? }
現在假設已經把這兩個源文件編譯到一個名為myfuncs.1ib的庫中。如果一個與myfuncs.lib連接的程序要調用func_one()函數,連接程序就會在myfuncs.lib庫中尋找包含func_one()函數定義的目標代碼,并且把它連接進來。不幸的是,函數func_one()是在包含func_two()函數定義的同一個源文件中被編譯的,因此,即使你的程序不會用到func_two(),連接程序也不得不把它連接進來。當然,這里假設func_one()中并沒有包含對func_two()的調用。如果一個程序包含一個對func_three()的調用,那么只有func_othree()的目標代碼會被連接進來,因為該函數是在它自己的源文件中被編譯的。
一般說來,你應該盡量把庫函數放到各自的源文件中。這種組織方式有助于提高程序的效率,因為程序只會和那些真正需要的函數進行連接,而不會和那些不需要的函數進行連接。這種組織方式在小組開發的情況下也是很有幫助的;在小組開發中,源文件的上交和發放非常頻繁,如果一個程序員要對一個包含在其自身的源文件中的函數進行維護,那么他可以集中維護這個函數;如果這個函數所在的源文件中還包含其它一些需要維護的函數,那么這些函數就無法發放給其它小組成員,因為它們包含在一個源文件中。
C語言連接運算符“##”有什么作用?
連接運算符“##”可以把兩個獨立的字符串連接成一個字符串。在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的用法中你可以看出,如果在運行時才能確定要調用哪個函數,你可以利用“##”運算符動態地構造要調用的函數的名稱。
C語言中的包含文件最多可以嵌套幾層?
盡管理論上包含文件可以嵌套任意多層,但是,如果嵌套層數太多,編譯程序就會用光它的堆棧空間。因此,實際的嵌套層數是有限的,它一般取決于你的硬件設置和編譯程序的版本。在編寫程序時,你應該避免過多的嵌套。一般來說,只有在確實需要時才嵌套包含文件,例如建立一個頭文件,使其包含每個模塊所需的每個頭文件。
C語言中的包含文件可以嵌套嗎?
包含文件可以嵌套,但你應該避免多次包含同一個文件(見5.3)。過去,人們認為頭文件嵌套是一種不可取的編程方法,因為它增加了MAKE程序中的依賴跟蹤函數(dependencytrackingfunction)的工作負擔,從而降低了編譯速度。現在,通過引入預編譯頭文件(precompiledheaders,即所有頭文件和相關的依賴文件都以一種已被預編譯的狀態存儲起來)這樣一種技術,許多流行的編譯程序已經解決了上述問題。
許多程序員都喜歡建立一個自己的頭文件,使其包含每個模塊所需的每個頭文件。這是一個頭文件。
在C語言編譯時,能否指定包含哪一個頭文件嗎?
你可以通過#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頭文件將被包含進來。
#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”語句一般用來包含非標準頭文件,因為這些頭文件一般存放在當前目錄下,你可以經常修改它們,并且要求編譯程序總是使用這些頭文件的最新版本。
在C語言程序中加入注釋的最好方法是什么?
大部分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標準是不兼容的,許多版本較早的編譯程序不支持這種方法。
對于C語言,使用宏更好,還是使用函數更好?
這取決于你的代碼是為哪種情況編寫的。宏與函數相比有一個明顯的優勢,即它比函數效率更高(并且更快),因為宏可以直接在源代碼中展開,而調用函數還需要額外的開銷。但是,宏一般比較小,無法處理大的、復雜的代碼結構,而函數可以。此外,宏需要逐行展開,因此宏每出現一次,宏的代碼就要復制一次,這樣你的程序就會變大,而使用函數不會使程序變大。一般來說,應該用宏去替換小的、可重復的代碼段,這樣可以使程序運行速度更快;當任務比較復雜,需要多行代碼才能實現時,或者要求程序越小越好時,就應該使用函數。
C語言編程技巧—如何使部分程序在演示版中失效?
如果你在為你的程序制作一個演示版,你可以通過預處理指令使你的程序的一部分生效或失效。以下是一個用#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這樣的宏,通過它們指示預處理程序如何根據具體條件將相應的代碼包含到你的程序中去。
與用#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)使程序調試起來更方便,因為某些標識符調試程序能打印枚舉常量的值。這一點在調試程序時是非常用的,因為如果你的程序在使用枚舉常量的一行語句中停住了,你就能馬上檢查出這個常量的值;反之,絕大多數調試程序無法打印標識符常量的值,因此你不得不在頭文件中手工檢查該常量的值。
C語言enum關鍵字—用
C語言中,用#define指令說明常量有什么好處?
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中作更詳細的介紹。
C語言中,用#define指令說明常量有什么好處?
如果用#define指令說明常量,常量只需說明一次,就可多次在程序中使用,而且維護程序時只需修改#define語句,不必一一修改常量的所有實例。例如,如果在程序中要多次使用PI(約3.14159),就可以象下面這樣說明一個常量:?????? #define PI 3.14159
如果想提高PI的精度,只需修改在#define語句中定義的PI值,就不必在程序中到處修改了。通常,最好將#define語句放在一個頭文件中,這樣多個模塊就可以使用同一個常量了。
用#define指令說明常量的另一個好處是占用的內存最少,因為以這種方式定義的常量將直接進入源代碼,不需要再在內存中分配變量空間。
但是,這種方法也有缺點,即大多數調試程序無法檢查用#define說明的常量。
用#define指令說明的常量可以用#under指令取消。這意味著,如果原來定義的標識符(如NULL)不符合你的要求,你可以先取消原來的定義,然后重新按自己的要求定義一個標識符,詳見5.31。
可以用#include指令包含類型名不是".h"的文件嗎?
預處理程序將包含用#include指令指定的任意一個文件。例如,如果程序中有下面這樣一條語句,那么預處理程序就會包含macros.inc文件。??? #include <macros.inc>
不過,最好不要用#include指令包含類型名不是".h"的文件,因為這樣不容易區分哪些文件是用于編譯預處理的。例如,修改或調試你的程序的人可能不知道查看macros.inc文件中的宏定義,而在類型名為".h"的文件中,他卻找不到在macros.inc文件中定義的宏。如果將macros.inc文件改名為macros.h,就可以避免發生這種問題。
C語言中,怎樣避免多次包含同一個頭文件?
通過#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語句;反之,預處理程序就不再對此后的語句進行預處理。
C語言預處理程序(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");
??? }
}
多數編譯程序都提供了一個命令行選項,或者有一個獨立的預處理程序,可以讓你只啟動預處理程序并將源代碼的預處理版本保存到一個文件中。你可以用這種方法查看源代碼的預處理版本,這對調試與宏或其它預處理指令有關的錯誤是比較有用的。
C語言的宏(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"。這個例子說明,宏所使用的“#”運算符是一種非常方便的調試工具。
“##”運算符的作用是將兩個獨立的字符串連接成一個字符串。
C語言編譯預處理概述
本專題集中討論與預處理程序有關的問題。在編譯程序對程序進行通常的編譯之前,要先運行預處理程序。可能你以前沒有見過這個程序,因為它通常在幕后運行,程序員是看不見它的,然而,這個程序非常有用。預處理程序將根據源代碼中的預處理指令來修改你的程序。預處理指令(如#define)為預處理程序提供特定的指令,告訴它應該如何修改你的源代碼。預處理程序讀入所有包含的文件和待編譯的源代碼,經過處理生成源代碼的預處理版本。在該版本中,宏和常量標識符已用相應的代碼和值代替。如果源代碼中包含條件預處理指令(如#if),預處理程序將先判斷條件,然后相應地修改源代碼。
預處理程序有許多非常有用的功能,例如宏定義,條件編譯,在源代碼中插入預定義的環境變量,打開或關閉某個編譯選項,等等。對專業程序員來說,深入了解預處理程序的各種特征,是創建快速和高效的程序的關鍵之一。
在閱讀本專題時,請記住本專題采用的一些技術(以及所提到的一些常見陷阱),以便更好地利用預處理程序的各種功能。