預(yù)處理
在C語(yǔ)言中,以“#”號(hào) 開頭的是預(yù)處理命令。例如,如包含命令#include <stdio.h>,宏定義 命令#define PI 3.1415926等。在源程序中這些命令都放在函數(shù)之外, 而且一般都放在源文件的前面,它們稱為預(yù)處理部分。無(wú)參宏定義
2.1無(wú)參宏的一般定義形式:#define 標(biāo)識(shí)符 字符串(這里的“#”表示這是一條預(yù)處理命令。凡是以“#”開頭的均為預(yù)處理命令。define 為宏定義命令?!皹?biāo)識(shí)符”為所定義的宏名。“字符串”可以是常數(shù)、表達(dá)式、格式串等。)見(jiàn)例1.
//例1
/#include <stdio.h>
/#define PI 3.1415926 //定義宏P(guān)I為3.1415926
void main()
{
double s;
int r;
printf("Please enter the radius : ");
scanf("%d", &r);
s = PI * r * r;
printf("%g\n", s);
}
//Please enter the radius : 5
78.5398(注:宏定義即為將標(biāo)識(shí)符后面的字符串用標(biāo)識(shí)符替代,如例1中3.1416926就用PI替代,所有要用3.1415926的地方用PI表示就可以。這樣的話方便日后修改,因?yàn)橐桓腜I所代替的數(shù)字則整個(gè)程序中的所有有PI的地方所表示的數(shù)字則全改。)
2.2宏定義注意事項(xiàng):
1)宏定義是用宏名來(lái)表示一個(gè)字符串,在宏展開時(shí)又以該字符串取代宏名,這只是一種簡(jiǎn)單的代換,字符串中可以含任何字符,可以是常數(shù),也可以是表達(dá)式,預(yù)處理程序?qū)λ蛔魅魏螜z查。如有錯(cuò)誤,只能在編譯已被宏展開后的源程序時(shí)發(fā)現(xiàn)。
2)宏定義不是說(shuō)明或語(yǔ)句,在行末不必加分號(hào),如加上分號(hào)則連分號(hào)也一起置換。
3)宏定義必須寫在函數(shù)之外,其作用域?yàn)楹甓x命令起到源程序結(jié)束。如要終止其作用域可使用 # undef 命令。
4)宏名在源程序中若用引號(hào)括起來(lái),則預(yù)處理程序不對(duì)其作宏代換。
5)宏定義允許嵌套,在宏定義的字符串中可以使用已經(jīng)定義的宏名。在宏展開時(shí)由預(yù)處理程序?qū)訉哟鷵Q。見(jiàn)例2.
//例2
/#include <stdio.h>
/#define PI 3.1415926
/#define S PIrr
void main()
{
double s;
int r;
printf("Please enter the radius : ");
scanf("%d", &r);
s = S;
printf("\n\nThe area of the roundness = %g\n\n", s);
}
6)習(xí)慣上宏名用大寫字母表示,以便于與變量區(qū)別。但也允許用小寫字母。
7)可用宏定義表示數(shù)據(jù)類型,使書)寫方便。例如,#define INTEGER int
8) 對(duì)“輸出格式”作宏定義,可以減少書寫麻煩。見(jiàn)例3.
//例3
/#include <stdio.h>
/#define P printf
/#define D "%d\n"
/#define F "%f\n"void main()
{
int a = 5, c = 8, e = 11;
float b = 3.8, d = 9.7, f = 21.08;P(D F, a, b);
P(D F, c, d);
P(D F, e, f);
}
(注意:這樣做雖然會(huì)使得書寫變得方便,但是會(huì)造成程序的可讀性變差,因?yàn)槌绦騿T每次在讀到P時(shí)需要將其翻譯為printf。)
2.3宏定義表示數(shù)據(jù)類型和用 typedef 定義數(shù)據(jù)說(shuō)明符的區(qū)別
宏定義只是簡(jiǎn)單的字符串代換,是在預(yù)處理完成的,而 typedef 是在編譯時(shí)處理的,它不是作簡(jiǎn)單的代換,而是對(duì)類型說(shuō)明符重新命名。被命名的標(biāo)識(shí)符具有類型定義說(shuō)明的功能,見(jiàn)例4.
//例4
/#include <stdio.h>
/
/#define PIN1 char*
/typedef char* PIN2;void main()
{
PIN1 x, y; //這里的實(shí)質(zhì)就是,char *x, y;
PIN2 a, b; //這里的實(shí)質(zhì)就是,char *a, *b;printf("By #define : %d %d\n", sizeof(x), sizeof(y));
printf("By typedef : %d %d\n", sizeof(a), sizeof(b));
}
// By #define : 8 1 (字符指針變量占8個(gè)字節(jié),因?yàn)樗娴氖堑刂?,字符變量?個(gè)字節(jié))
By typedef : 8 8
- 帶參宏定義
3.1 概念:C語(yǔ)言允許宏帶有參數(shù)。在宏定義中的參數(shù)稱為形式參數(shù),在宏調(diào)用中的參數(shù)稱為實(shí)際參數(shù)。對(duì)帶參數(shù)的宏,在調(diào)用中,不僅要宏展開,而且要用實(shí)參去代換形參。
帶參宏定義的一般形式為:#define 宏名 ( 形參表 ) 字符串
帶參宏調(diào)用的一般形式為:宏名 ( 實(shí)參表 ) ;
//例5
/#include <stdio.h>
/#define MAX(a,b) (a>b)?a:bvoid main()
{
int x, y, max;
printf("input two numbers: ");
scanf("%d %d", &x, &y);
max = MAX(x, y); // 這里即將MAX(x, y)替換為(x>y) ? x : y
printf("The max is: %d\n", max);
}
//input two numbers: 6 9
The max is: 9
3.2 帶參宏定義的注意事項(xiàng):
1)帶參宏定義中,宏名和形參表之間不能有空格出現(xiàn)。
2)在帶參宏定義中,形式參數(shù)不分配內(nèi)存單元,因此不必作類型定義。而宏調(diào)用中的實(shí)參有具體的值。要用它們?nèi)ゴ鷵Q形參,因此必須作類型說(shuō)明。(這是與函數(shù)中的情況不同的。在函數(shù)中,形參和實(shí)參是兩個(gè)不同的量,各有自己的作用域,調(diào)用時(shí)要把實(shí)參值賦予形參,進(jìn)行“值傳遞”。而在帶參宏中,只是符號(hào)代換,不存在值傳遞的問(wèn)題。)
3)在宏定義中的形參是標(biāo)識(shí)符,而宏調(diào)用中的實(shí)參可以是表達(dá)式。見(jiàn)例6.
//例6
/#include <stdio.h>
/#define SAY(y) (y)void main()
{
int i = 0;
char say[] = {73, 32, 108, 111, 118, 101, 32, 85, 78, 83, 87, 33, 0};
char say2[] = {72, 31, 107, 110, 117, 100, 31, 84, 77, 82, 86, 32, 0};while( say[i] )
{
say[i] = SAY(say[i]);
say2[i] = SAY(say2[i]+1); //調(diào)用表達(dá)式作為宏的實(shí)參
i++;
}
printf("%s\n", say);
printf("%s\n", say2);
}
//輸出: I LOVE UNSW!
I LOVE UNSW!
4)在宏定義中,字符串內(nèi)的形參通常要用括號(hào)括起來(lái)以避免出錯(cuò)。在例7中的宏定義中 (y)(y) 表達(dá)式的 y 都用括號(hào)括起來(lái),因此結(jié)果是正確的。如果去掉括號(hào),則會(huì)出現(xiàn)錯(cuò)誤。見(jiàn)例7.(但需注意,如果將宏用在與宏最外層具有相同運(yùn)算符優(yōu)先級(jí)的表達(dá)式中,則需將宏所定義的表達(dá)式用括號(hào)將整個(gè)表達(dá)式括起來(lái),如((y)(y)), 見(jiàn)例8.)
//例7
/#include <stdio.h>
/#define SQ(y) (y)*(y) //這里加上括號(hào)是為了防止y為表達(dá)式void main()
{
int a, sq;
printf("input a number: ");
scanf("%d", &a);
sq = SQ(a+1); // sq = (a+1)*(a+1)
printf("sq = %d\n", sq);
}
//input a number: 2
sq = 9
/#include <stdio.h>
/#define SQ(y) y*yvoid main()
{
int a, sq;
printf("input a number: ");
scanf("%d", &a);
sq = SQ(a+1); // sq = a+1a+1, 由于運(yùn)算符優(yōu)先級(jí),因此會(huì)先算,再算+
printf("sq = %d\n", sq);
}
//input a number: 2
sq = 5
input a number: 3
sq = 7
//例8
/#include <stdio.h>
/#define SQ(y) (y)*(y)void main()
{
float a, sq, i;
printf("input a number: ");
scanf("%f", &a);
sq = 160 / SQ(a + 1); //這里等價(jià)于(160 / (a + 1)) * (a + 1)
i = 160 / (a + 1);
printf("%f\n", i);
printf("sq = %f\n", sq);
}
//input a number: 5
輸出:26.666666
輸出:sq = 160.000000
/#include <stdio.h>
/#define SQ(y) ((y)(y)) // yy試試void main()
{
float a, sq, i;
printf("input a number: ");
scanf("%f", &a);
sq = 160 / SQ(a + 1); //這里即為160 / ((a + 1) * (a + 1))
i = 160 / (a + 1);
printf("%f\n", i);
printf("sq = %f\n", sq);
}
//input a number: 5
輸出:26.666666
輸出:sq = 4.444445
5)帶參的宏和帶參函數(shù)很相似,但有本質(zhì)上的不同,除上面已談到的各點(diǎn)外,把同一表達(dá)式用函數(shù)處理與用宏處理兩者的結(jié)果有可能是不同的。
6)宏定義也可用來(lái)定義多個(gè)語(yǔ)句,在宏調(diào)用時(shí),把這些語(yǔ)句又代換到源程序內(nèi)。見(jiàn)例9.
//例9
/#include <stdio.h>
/#include <string.h>/#define STR(s1, s2, s3, sum) strcat( strcat( strcat(sum, s1), s2 ), s3) //定義拼接宏
void main()
{
char str1[] = "I ", str2[] = "love ", str3[] = "UNSW!", str[40] = "";STR(str1, str2, str3, str); // strcat( strcat( strcat(str, str1), str2 ), str3),即為數(shù)組相拼接
printf("str1 = %s\nstr2 = %s\nstr3 = %s\nStr = %s\n\n\n", str1, str2, str3, str);str[0] = 0; //數(shù)組str清零,將“I love UNSW”從數(shù)組str內(nèi)存單元中清除
STR(str2, str1, str3, str); //再次拼接
printf("str1 = %s\nstr2 = %s\nstr3 = %s\nStr = %s\n\n\n", str1, str2, str3, str);
}
//輸出:str1 = I
str2 = love
str3 = UNSW!
Str = I love UNSW!str1 = I
str2 = love
str3 = UNSW!
Str = love I UNSW!
- 文件包含的說(shuō)明:
1)一個(gè) include 命令只能指定一個(gè)被包含文件,若有多個(gè)文件要包含,則需用多個(gè) include 命令。
2)文件包含允許嵌套,即在一個(gè)被包含的文件中又可以包含另一個(gè)文件。
3)包含命令中的文件名可以用雙引號(hào)括起來(lái),也可以用尖括號(hào)括起來(lái)。例如以下寫法都是允許的:
/#include"stdio.h"
/#include<math.h>
(注意:這兩種形式是有區(qū)別的:使用尖括號(hào)表示在包含文件目錄中去查找 ( 包含目錄是由用戶在設(shè)置環(huán)境時(shí)設(shè)置的 ) ,而不在源文件目錄去查找;使用雙引號(hào)則表示首先在當(dāng)前的源文件目錄中查找,若未找到才到包含目錄中去查找。用戶編程時(shí)可根據(jù)自己文件所在的目錄來(lái)選擇某一種命令形式)
- 條件編譯
預(yù)處理程序提供了條件編譯的功能??梢园床煌臈l件去編譯不同的程序部分,因而產(chǎn)生不同的目標(biāo)代碼文件。這對(duì)于程序的移植和調(diào)試是很有用的。 條件編譯有三種規(guī)則:
1)第一種形式:它的功能是,如果標(biāo)識(shí)符已被 #define 命令定義過(guò)則對(duì)程序段 1進(jìn)行編譯;否則對(duì)程序段 2 進(jìn)行編譯。見(jiàn)例10.
//例10
/#ifdef 標(biāo)識(shí)符
程序段 1
/#else
程序段 2
/#endif
如果沒(méi)有程序段 2( 它為空 ) ,本格式中的#else 可以沒(méi)有。見(jiàn)例11.
//例11
/#ifdef 標(biāo)識(shí)符
程序段
/#endif
2)第二種形式:見(jiàn)例12.
/#ifndef 標(biāo)識(shí)符 //“ifndef”表示if not define, 意思是判斷是否已經(jīng)進(jìn)行過(guò)預(yù)編譯
/#define 標(biāo)識(shí)符 字符串
/#endif
//例12
/#include <stdio.h>
void main()
{
char str[40];
int cmp( char *str1, char *str2 );printf("Please enter the website you like the best : ");
scanf("%s", str);/#ifndef CORRECT /*判斷是否已經(jīng)進(jìn)行過(guò)預(yù)編譯,如果沒(méi)有則進(jìn)行下一句語(yǔ)句進(jìn)行預(yù)編譯, 否則跳過(guò)預(yù)編譯語(yǔ)句,其目的是防止重復(fù)編譯,提高效率 */
/#define CORRECT "www.myunsw.edu.au"
/#endif
if( cmp( str, CORRECT ) == 0 )
{
printf("Yes, you inputted a correct website!\n");
}
else
{
printf("No, you inputted an incorrect website!\n");
}
}
int cmp( char *str1, char *str2 )
{
int i = 0, j = 0;
while( str2[j] == str1[i] )
{
i++;
j++;
if( !str2[j] )
{
return 0;
}
}
return -1;
}
//Please enter the website you like the best : www.myunsw.edu.au
Yes, you inputted a correct website!Please enter the website you like the best : www.google.com
No, you inputted an incorrect website!
3)第三種形式:見(jiàn)例13.
/#if 標(biāo)識(shí)符 //這里的標(biāo)志符即所表示的字符串為真,則執(zhí)行程序段1,否則執(zhí)行程序段2
程序段1
/#else
程序段2
/#endif
//例13
/#include <stdio.h>
/#define ROUND 0
/#define PI 3.1415926
void main()
{
int r;
double s;
printf("input a number: ");
scanf("%d", &r);/#if ROUND
s = r * r * PI;
printf("Area of round is: %6.5f\n\n", s);
/#else
s = r * r;
printf("Area of square is: %6.5f\n\n",s);
/#endif
}
//input a number: 2
Area of square is: 4.00000 (因?yàn)檫@里ROUND表示的是0,所以跳過(guò)if后面的子程序,直接執(zhí)行else后面的子程序)
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/#include <stdio.h>
/#define ROUND 1
/#define PI 3.1415926
void main()
{
int r;
double s;
printf("input a number: ");
scanf("%d", &r);/#if ROUND
s = r * r * PI;
printf("Area of round is: %6.5f\n\n", s);
/#else
s = r * r;
printf("Area of square is: %6.5f\n\n",s);
/#endif
}
//input a number: 2
Area of square is: 12.56637 (ROUND表示1,則執(zhí)行if后面的子程序)
- 小結(jié)
1)預(yù)處理功能是C語(yǔ)言特有的功能,它是在對(duì)源程序正式編譯前由預(yù)處理程序完成的。程序員在程序中用預(yù)處理命令來(lái)調(diào)用這些功能。
2)宏定義是用一個(gè)標(biāo)識(shí)符來(lái)表示一個(gè)字符串,這個(gè)字符串可以是常量、變量或表達(dá)式。在宏調(diào)用中將用該字符串代換宏名。
3)宏定義可以帶有參數(shù),宏調(diào)用時(shí)是以實(shí)參代換形參。而不是“值傳送”。
4)為了避免宏代換時(shí)發(fā)生錯(cuò)誤,宏定義中的字符串應(yīng)加括號(hào),字符串中出現(xiàn)的形式參數(shù)兩邊也應(yīng)加括號(hào)。
5)文件包含是預(yù)處理的一個(gè)重要功能,它可用來(lái)把多個(gè)源文件連接成一個(gè)源文件進(jìn)行編譯,結(jié)果將生成一個(gè)目標(biāo)文件。
6)條件編譯允許只編譯源程序中滿足條件的程序段,使生成的目標(biāo)程序較短,從而減少了內(nèi)存的開銷并提高了程序的效率。
7)使用預(yù)處理功能便于程序的修改、閱讀、移植和調(diào)試,也便于實(shí)現(xiàn)模塊化程序設(shè)計(jì)。