程序員之程序設計知識點九
9.1 宏定義
宏定義有無參數宏定義和帶參數宏定義兩種。
無參數的宏定義的一般形式為
# define 標識符 字符序列
其中# define之后的標識符稱為宏定義名(簡稱宏名),要求宏名與字符序列之間用空格符分隔。這種宏定義要求編譯預處理程序將源程序中隨后所有的定名的出現(注釋與字符串常量中的除外)均用字符序列替換之。前面經常使用的定義符號常量是宏定義的最簡單應用。如有:
# define TRUE 1
# define FALSE 0
則在定義它們的源程序文件中,凡定義之后出現的單詞TRUE將用1替代之;出現單詞FALSE將用0替代之。
在宏定義的#之前可以有若干個空格、制表符,但不允許有其它字符。宏定義在源程序中單獨另起一行,換行符是宏定義的結束標志。如果一個宏定義太長,一行不夠時,可采用續行的方法。續行是在鍵人回車符之前先鍵入符號“\”。注意回車要緊接在符號“\”之后,中間不能插入其它符號。
宏定義的有效范圍稱為宏定義名的轄域,轄域從宏定義的定義結束處開始到其所在的源程序文件末尾。宏定義名的轄域不受分程序結構的影響。可以用預處理命令#undef終止宏定義名的轄域。
在新的宏定義中,可以使用前面已定義的宏名。例如,
# define R 2.5
# define PI 3.1415926
# define Circle 2*PI*R
# define Area PI* R * R
程序中的Circle被展開為2*3.1415926* 2.***rea被展開為3.1415926*2.5*2.5。
如有必要,宏名可被重復定義。被重復定義后,宏名原先的意義被新意義所代替。
通常,無參數的宏定義多用于定義常量。程序中統一用宏名表示常量值,便于程序前后統一,不易出錯,也便于修改,能提高程序的可讀性和可移植性。特別是給數組元素個數一個宏定義,并用宏名定義數組元素個數能部分彌補數組元素個數固定的不足。
注意:預處理程序在處理宏定義時,只作字符序列的替換工作,不作任何語法的檢查。如果宏定義不當,錯誤要到預處理之后的編譯階段才能發現。宏定義以換行結束,不需要分號等符號作分隔符。如有以下定定義:
# define PI 3.1415926;
原希望用PI求圓的周長的語句
c=2*PI*r;
經宏展開后,變成
c=2*3.1415926*r;
這就不能達到希望的要求。
帶參數宏定義進一步擴充了無參數宏定義的能力,在字符序列替換同時還能進行參數替換。帶參數定定義的一般形式為
# define 標識符(參數表)字符序列
其中參數表中的參數之間用逗號分隔,字符序列中應包含參數表中的參數。在定義帶參數的宏時,宏名標識符與左圓括號之間不允許有空白符,應緊接在一起,否則變成了無參數的宏定義。如有宏定義:
# define MAX(A,B) ((A) > (B)?(A):(B))
則代碼 y= MAX( p+q, u+v)將被替換成 y=((p+q) >(u+v)?(p+q):(u+v)。
程序中的宏調用是這樣被替換展開的,分別用宏調用中的實在參數字符序列(如p+q和u+V) 替換宏定義字符序列中對應所有出現的形式參數(如用p+q替代所有形式參數A,用u+V替代所有形式參數B),而宏定義字符序列中的不是形式參數的其它字符則保留。這樣形成的字符序列,即為宏調用的展開替換結果。宏調用提供的實在參數個數必須與宏定義中的形式參數個數相同。
注意:宏調用與函數調用的區別。函數調用在程序運行時實行,而宏展開是在編譯的預處理階段進行;函數調用占用程序運行時間,宏調用只占編譯時間;函數調用對實參有類型要求,而宏調用實在參數與宏定義形式參數之間沒有類型的概念,只有字符序列的對應關系。函數調用可返回一個值,宏調用獲得希望的C代碼。另外,函數調用時,實參表達式分別獨立求值在前,執行函數體在后。宏調用是實在參數字符序列替換形式參數。替換后,實在參數字符序列就與相鄰的字符自然連接,實在參數的獨立性就不一定依舊存在。如下面的宏定義:
# define SQR(x) x*x
希望實現表達式的平方計算。對于宏調用
P=SQR(y)
能得到希望的宏展開p= y*y。但對于宏調用q=SQR(u+v)得到的宏展開是q=u+V*u+V。顯然,后者的展開結果不是程序設計者所希望的。為能保持實在參數替換后的獨立性,應在宏定義中給形式參數加上括號。進一步,為了保證宏調用的獨立性,作為算式的宏定義也應加括
號。如 SQR宏定義改寫成:
# define SQR((x)*(x))
才是正確的宏定義。
對于簡短的表達式計算函數,或為了提高程序的執行效率、避免函數調用時的分配存儲單元、保留現場、參數值傳遞、釋放存儲單元等工作。可將函數定義改寫成宏定義。所以合理使用宏定義,可以使程序更簡潔。
9.2 文件包含
文件包含預處理命令(簡稱文件包含命令)實現將指定文件的內容作為當前源程序的一部分。文件包含預處理命令的一般形式為
# include “文件名”。
或
# include<文件名>
文件包含命令為組裝大程序和程序文件復用提供了一種手段。在編寫程序時,習慣將公共的常量定義、數據類型定義和全局變量的外部說明構成一個源文件。稱這類沒有執行代碼的文件為頭文件,并以“.h”為文件名的后綴。其它程序文件凡要用到頭文件中定義或說明的程序對象時,就用文件包含命令使它成為自己的一部分。這樣編程的好處是各程序文件使用統一的數據結構和常量,能保證程序的一致性,也便于修改程序。頭文件如同標準零件一樣被其它程序文件使用,減少了重復定義的工作量。
9.3 條件編譯
條件編譯是指在編譯一個源程序文件時,其中部分代碼段能根據條件成立與否有選擇地被編譯。即編譯程序只編譯沒有條件或條件成立的代碼段,而不編譯不滿足條件的代碼段。
條件編譯為組裝與環境有關的大程序提供有力的支持,能提高程序的可移植性和可維護性。通常在研制程序系統時,設計者將所有與環境有關的內容編寫成獨立的程序段,并將它們配上相應的條件編譯命令。因環境不同等原因,只要設定相應條件,就能組裝出適應環境要求的新程序。
條件編譯命令主要有三種相似的形式。
(1) #if 表達式
程序段1
# else
程序段2
# endif
其中表達式為常量表達式,其意義是當指定的表達式值為非零時,程序段1參與編譯,程序段2不參與編譯;否則,反之。
這種形式的條件編譯命令使預處理程序能根據給定的條件確定哪個程序段參與編譯,哪個程序段不參與編譯。
在上述一般形式中,當程序段2不出現時,也可簡寫為
#if 表達式
程序段
# endif
條件編譯與if語句有重要區別:條件編譯是在預處理時判定的,不產生判定代碼,其中一個不滿足條件的程序段不參與編譯,不會產生代碼;且語句是在運行時判定的,且編譯產生判
定代碼和兩個分支程序段的代碼。因此,條件編譯可減少目標程序長度,能提高程序執行速度。但條件編譯只能測試常值或常量表達式,而且語句能對表達式作動態測試。
條件編譯預處理命令也可呈嵌套結構。特別是為了便于描述# else后的程序段又是條件編譯情況,引人須處理命令符# elif。它的意思就是# else #if。所以條件編譯須處理命令更一般的形式為
# if 表達式 1
程序段1
# elif 表達式 2
程序段2
# elif 表達式 n
程序段n
# else
程序段 n+l
# endif
(2) # ifdef 標識符
程序段1
# else
程序段2
# endif
其中標識符是一個宏名,上述意義是當宏名已被定義,則程序段里參與編譯,程序段2不參與編譯;否則,反之。這里的程序段可以是任何C代碼行,也可以包含預處理命令行。其中的標識符只要求已定義與否,與標識符被定義成什么是無關的。如標識符只用于這個目的,常用以下形式的宏定義來定義這類標識符:
# define 標識符
即標識符之后為空,直接以換行結束該宏定義命令。
在上述一般形式中,如果程序段2為空,則可簡寫成如下一般形式:
# ifdef 標識符
程序段
# endif
條件編譯主要作用是能提高程序的通用性,應用于不同的計算機系統編譯不同的源程序段。條件編譯另一種應用是在程序中插入調試狀態下輸出中間結果的代碼。如:
# ifdef DEBUG
printf(“a=%d, b=%d \n”, a, b);
# endif
程序在調試狀態下與包含宏定義命令
# define BEBUG
的頭文件一起編譯;在要獲得最終目標程序時,不與包含該宏定義命令的頭文件一起編譯。這樣,在調試通過后,不必再修改程序,已獲得了正確的最終程序。為了日后程序維護,將調試時使用的程序代碼留在源程序中是專業程序員習慣采用的方法。因為這些代碼的存在不影響最終目標碼,但有助于日后修改程序時的調試需要。
(3) # ifndef 標識符
程序段1
# else
程序段2
# endif
這種條件編譯形式與前面介紹的形式的唯一差異是***行的ifdef改為ifndef。其意義是當標識符末被定義時,程序段1參與編譯,程序段2不參與編譯;否則,反之。在上述形式中,當程序段2不出現時,可簡寫成:
# infdef 標識符
程序段
# endif
9.4 帶參數的主函數
在操作系統下執行某個C程序,是環境對C程序的啟動,可以看作是對該程序的main()函數的調用。main()函數執行結束后,控制返回環境。為能從環境向C程序傳遞信息,啟動C程序的命令行可帶有任選的參數。命令行的一般形式為
程序名 參數1 參數2……參數n
其中程序名和各參數之間用空白符分隔。
為能讓main()函數讀取命令行中的參數,環境將多個參數以兩個參數形式傳遞給main()函數、其中***個參數(習慣記作argc) 表示命令行中參數的個數(包括程序名);第二個參數(習慣記作argv)是一個字符指針數組。其中argv[0] 指向程序名字符串的***個字符,argv[1]指向參數1字符串的***個字符,…,argv[argc-1] 指向***一個參數字符串的***個字符。如果利因山等于1,則表示程序名后面沒有參數。下面的例子用于說明main()函數對參數argc
與argv的引用方法。
【例9.l】 回打啟動程序時的命令行各參數。
# include
void main(int argc, char *argv[] /* 或char **argv; */)
{ int k;
for( k=l; k
printf(“%c”,argv[k],k printf(“\n\n”);
}
如上述程序的執行程序名為echopro.exe,執行該程序的命令行為:
echopro Hello world!
則程序將輸出
Hello world!
在以上命令行中,根據約定,main()函數的參數argc的值為3;argv[0],argv[1],argv[2]分別指向字符串“echopro”、“Hello”、“world!”的***個字符。在程序的printf()函數調用中,字符輸出格式%c輸出一個字符,若是已輸出了命令行***一個參數,該格式將輸出一個換行符,若是輸出其它參數,則輸出一個空白符。
因函數的數組參數是指向數組首元素的指針變量,所以在主函數main()中可對argv施行增量運算。例如,在argv[0]指針指向程序名字符串的***個字符情況下,對argv施增量運算++argv后,argv[0](或*argv)就指向參數1的***個字符c利用argv的這一性質,可改寫上述程序為以下形式:
# include
void main(int argc,char **argv)
{ while(--argc>0)
printf(“%s%c”,*++argv,argc>1? '':'\n');
}
這里,++argv使指針argv先加1,讓它一開始就指向參數1;逐次增回,使它遍歷指向各參數。又利用函數printf()的***個格式參數是字符串表達式,上述程序對printf()的調用可改寫成:
printf((argc> l) ? “%s”:“%s\n”,* ++argv);
【例9.2】 假定啟動程序時給出的命令行參數是一串整數,程序將全部整數求和后輸出。
# include
# include
void main( int argc,char **argv)
{ int k,s;
for(s=0, k=l;)
s+=atoi(*++agv); /* 從數字字符串譯出整數 */
printf( “\t%d\n”, s);
}
【編輯推薦】