編譯預處理和函數區別
A. 預編譯和預處理有什麼區別,他們是一樣的嗎
預編譯(預處理)就是在正式編譯之前的,為正式編譯做准備,處理以#開頭的指令,兩者是一樣的,只是兩種叫法而已
B. 內聯函數和編譯預處理的區別是
預處理只是一種簡單替代,內聯函數比較安全,編譯器會自動判斷是否可以內聯等。
用#define預處理的時候會有一些副作用
比如:
#define ADD(x) (x+1)*(x+1)
如果有代碼想要先做a++,再做ADD,那可能就會有問題
ADD(a++)就成了((a++)+1)*((a++)+1)
然而用內聯函數就沒有問題
所以很多人推薦盡量用內聯函數來代替這種預處理
而用const來代替那些預處理的常量定義
但是預處理也有很多inline實現不了的功能,看一下MFC中大量的宏就可以看到它強大的功能。
C. 什麼是預編譯,何時需要預編譯
預編譯又稱為預處理,是做些代碼文本的替換工作
預編譯又稱為預處理,是做些代碼文本的替換工作
處理#開頭的指令,比如拷貝#include包含的文件代碼,#define宏定義的替換,條件編譯等
就是為編譯做的預備工作的階段
主要處理#開始的預編譯指令
預編譯指令指示了在程序正式編譯前就由編譯器進行的操作,可以放在程序中的任何位置。常見的預編譯指令有:
(1)#include 指令
該指令指示編譯器將xxx.xxx文件的全部內容插入此處。若用<>括起文件則在系統的INCLUDE目錄中尋找文件,若用" "括起文件則在當前目錄中尋找文件。一般來說,該文件是後綴名為"h"或"cpp"的頭文件。
注意:<>不會在當前目錄下搜索頭文件,如果我們不用<>而用""把頭文件名擴起,其意義為在先在當前目錄下搜索頭文件,再在系統默認目錄下搜索。
(2)#define指令
該指令有三種用法:
第一種是定義標識,標識有效范圍為整個程序,形如#define XXX,常與#if配合使用;
第二種是定義常數,如#define max 100,則max代表100(這種情況下使用const定義常數更好,原因見注1);
第三種是定義"函數",如#define get_max(a, b) ((a)>(b)?(a):(b)) 則以後使用get_max(x,y)就可以得到x和y中較大的數(這種方法存在一些弊病,見注2)。
第四種是定義"宏函數",如#define GEN_FUN(type) type max_##type(type a,type b){return a>b?a:b;} ,使用時,用GEN_FUN(int),則此處預編譯後就變成了 max_int(int a,int b){return a>b?a:b;},以後就可以使用max_int(x,y)就可以得到x和y中較大的數.比第三種,增加了類型的說明。
(3)#if、#else和#endif指令
這些指令一般這樣配合使用:
#if defined(標識) //如果定義了標識
要執行的指令
#else
要執行的指令
#endif
在頭文件中為了避免重復調用(比如說兩個頭文件互相包含對方),常採用這樣的結構:
#if !(defined XXX) //XXX為一個在你的程序中唯一的標識符,
//每個頭文件的標識符都不應相同。
//起標識符的常見方法是若頭文件名為"abc.h"
//則標識為"abc_h"
#define XXX
真正的內容,如函數聲明之類
#endif
D. c語言問題 函數與編譯預處理
#include<stdio.h>
intk;
main()
{
intfun(intk);//如果函數定義在使用位置之後要提前聲明函數
k=3;
k=fun(k);//將k初始值傳遞過去再將返回來的函數值重新賦值給k
printf("k=%d ",k);
}
intfun(intk)
{
k=k*k;
returnk;//要返回計算得出的k值
}
建議:為了方便閱讀,建議更改一下
#include<stdio.h>
intk;
main()
{
intn;
intfun(intk0);//如果函數定義在使用位置之後要提前聲明函數,而且函數要有返回值類型
k=3;
n=fun(k);//用n接收返回來的函數值
printf("k=%d ",n);
}
intfun(intk0)
{
intk1;
k1=k0*k0;
returnk1;//要返回計算得出的k值
}
補充:函數定義格式: 返回值類型 函數名(形參);形參可要可不要,形參類型個數和函數返回值類型視情況而定
E. 編譯預處理命令是C語言文本的一部分嗎
(1)首先編譯預處理命令不是C語言文本的內容,是某個編譯工具的功能,不同的編譯工具,功能及命令是可能不同的。
(2)C語言程序的入口函數與開發工具及提供的運行庫有關,通常在鏈接時可以指定,如採用MS VC時,可以在link 時使用 /ENTRY:function 指定,function就是入口函數,但function必須是運行啟動庫中支持的入口函數,如:mainCRTStartup 就是調用會調用主函數 main() 的入口函數,wmainCRTStartup 則是寬字元版本,會調用主函數wmain(),WinMainCRTStartup 則會調用主函數WinMain()。因此,入口函數是運行庫提供的,當然你也能定製自己的運行庫,自己編寫入口函數,入口函數主要是完成內存及運行庫的初始化工作, 在C語言中 main函數是主函數,就是入口函數完成初始化工作後調用的函數。MS VC中,C可以編寫控制台,窗口應用、DLL動態庫等不同的應用類型代碼,並有多種字元集代碼頁的運行庫, 因此有不同的初始化環境,也就有不同的入口函數及主程序。
F. 編譯和預編譯有什麼區別。
預編譯又稱為預處理,是做些代碼文本的替換工作。
處理#開頭的指令,比如拷貝#include包含的文件代碼,#define宏定義的替換,條件編譯等
就是為編譯做的預備工作的階段
主要處理#開始的預編譯指令
編譯(compilation , compile) 1、利用編譯程序從源語言編寫的源程序產生目標程序的過程。 2、用編譯程序產生目標程序的動作。 編譯就是把高級語言變成計算機可以識別的2進制語言,計算機只認識1和0,編譯程序把人們熟悉的語言換成2進制的。
G. C語言知識總結
c語言概要
第一章、 概述
1、 c語言的基本知識
1.1、 c語言的執行步驟
編輯-程序代碼的錄入,生成源程序*.c
編譯-語法分析查錯,翻譯生成目標程序*.obj
(語法或邏輯錯誤,從第一個開始改,變數定義,語句格式,表達式格式等)
鏈接-與其他目標程序或庫鏈接裝配,生成可執行程序*.exe
執行
1.2、 main函數的基本知識
main()函數的位置
c程序總是從main( )函數開始執行
一個c程序可以包含一個主函數,即main()函數;也可以包含一個main()函數和若干其它函數
1.3、 c程序的結構
函數與主函數
程序由一個或多個函數組成
必須有一個且只能有一個主函數main()
程序執行從main開始,在main中結束,其他函數通過嵌套調用得以執行
程序語句
C程序由語句組成
用「;」作為語句終止符
注釋
//
或
/* */ 為注釋,不能嵌套
不產生編譯代碼
1.4、c 程序書寫的規則
習慣用小寫字母,大小寫敏感
不使用行號,無程序行概念:通常一個語句佔一行
可使用空行和空格
常用鋸齒形的書寫格式;同一層次結構的語句上下對齊。
第二章、基本數據類型與運算
2.1、c程序的數據類型
注意類型和變數含義的不同(類型是固定好的名字,變數是自己起的名字)
變數佔用的存儲空間
數據類型
基本類型:整型、字元型、浮點型(單精度型,雙精度型)
構造類型:數組類型、結構體類型
指針類型
空類型
注意基本類型賦初值的方式
基本數據類型的表示形式
整形數據
十進制:以非0數字開頭,如:123,-9,0
八進制;以0數字開頭,如:0123,067
十六進制:以0x開頭,如:0x123,0xff
實型數據
十進制:必須帶小數點,如:123.0,-9.0
指數形式;如:1.23E3,0.9e-2,5e2
字元型數據
普通字元:如:』a』,』2』,』H』,』#』
轉義字元:如:』\n』,』\167』,』\xlf,』\\』
(實現幾列的對齊:指定寬度。如%100\ 『\t』製表位)
(字元串長度。「abc\n\t\\」 strlen 6; sizeof 7)
基本數據類型的存儲長度
整型
Int 位元組數 2 位數 16 數的表示範圍 -32768—32767
Short 2 16 -32768—32767
Long 4 32 -2147483648—2147483647
實型
Float 4 32 3.4e-38---3.4e38
Double 8 64 1.7e-308---1.7e308
字元型
Char 1 8 -128----127
2.2、標識符命名規則
C語言標志符命名規則
標識符有數字,字母,下劃線組成
標識符的首字元必須為字母和下劃線
標識符不能為c語言的保留字(關鍵字)
如:auto extern sizeof float static case for struct char goto switch continue in typedef const if union default long unsigned do register void double return else short while enum signed
算術運算符 + - * / %
關系運算符 > < == >= <= !=
邏輯運算符 ! && ||
位運算符 << >> ~ | ^ &
賦值運算符 = 及其擴展賦值運算符
條件運算符 ? :
逗號運算符 ,
指針運算符 * &
求位元組數運算符 sizeof
強制類型轉換運算符 (類型)
分量運算符 . ->
下標運算符 [ ]
其他 如函數調用運算符()
運算符的優先順序
由高到低:單目運算符,算數運算符,關系運算符,賦值運算符
說明:單目運算符:自增運算符,自減運算符,類型裝換運算符。結合方向:自右至左
如:++--I 先—i.。
算術運算 結合方向自左至右
2.3基本運算和表達式
關系表達式和邏輯表達式
(a>b)&&(x>y) (a==b)||(x==y) !=a||(a>b)
A&&b.a為0.不執行b
A||b a為1.不執行b
在 c 中邏輯運算結果:1代表「真」,0代表「假」;
判斷一個表達式是否真:0代表「假」,非0代表「真」
條件表達式 逗號表達式
如:k=5,k++
逗號值為5;k為6.
表達式1?表達式2 :表達式3
K=5>6 ? 1 : 0
2.4、混合運算的數據類型轉換
2/3+0.5 雙精度浮點型
第三章、順序結構程序設計
3.1、c語句的分類
簡單語句
表達式語句 表達式+分號
空語句 只有分號的語句
復合語句 用花括弧將若干語句括起來
流程式控制制語句
選擇語句 if ,switch
循環語句 while, do while , for
轉移語句 break ,continue ,return goto
3.2、格式輸入函數scanf
一般形式:scanf(「格式控制字元串「,地址列表);
使用scanf函數時,需要注意:
格式字元的個數必須與輸入項的個數相同,數據類型必須一一對應,非格式字元串(說明性的)要原封不動的輸入。
輸入實行數據時,可以不帶小數點,即按整型數據輸入
數值型數據與字元或字元串混合輸入時,需要注意輸入方式。
3.3、格式輸出函數printf
Printf(「格式控制字元串「,輸出列表);
指定輸出格式,由格式字元串和非格式字元串兩種組成,非格式字元串照原樣輸出。
%[標志][輸出最小寬度][.精度][長度]類型
標志:- 左對齊;+ 右對齊;
%f, %d, %c, %s
3.4、其他輸入輸出函數
Putchar getchar puts gets
第四章、選擇結構程序設計
If選擇結構
單分支
If(表達式)
語句
雙分支
If(表達式)
語句1
Else
語句2
多分支
If (表達式1)
語句1
Else if(表達式2)
語句2
。。。
Else if(表達式m)
語句m
Else
語句n
Switch(表達式)
{
Case 常量表達式1:語句1;break;
Case 常量表達式2:語句2;break;
。。。
Case 常量表達式m:語句m;break;
Default:語句n;break;
}
注意break的使用
第五章、循環結構程序設計
循環三要素
初始條件 ;終止條件 ;在初始條件和終止條件間反復做某件事情(循環體)
While(表達式)
語句
Do
語句
While(表達式);
For(循環體變數賦初值;循環條件;循環變數增量)
( for( ) ; // ; 進行時間延遲。在信息交換等時用。如for(i=0,i<100) ; 互相通訊的時間延遲。 Delay )
Break語句 :不能用於循環語句和switch語句之外的任何其他語句;跳出循環。
Continue語句 :跳過循環體中剩餘的語句而強行執行下一次循環;跳出本次循環。
第六章、函數與編譯預處理
6.1、函數的定義和調用
類型標識符 函數名 (形式參數列表)
{ 聲明部分
語句
}
例:
Int max (int x,int y)
{int z;<br>Z=x>y?x:y;<br>Return(z);}
6.2、局部變數和全局變數
注意函數中靜態變數的定義和使用
6.3、變數的存儲類型
局部變數的存儲類型
自動變數(auto) 動態存儲
局部靜態變數(static) 靜態存儲
寄存器變數(register) 靜態存儲
全局變數的存儲類型
自動變數(auto) 動態存儲
外部變數 (extern) 靜態存儲
全局靜態變數(static )靜態存儲
Extern 外部引用
Static 不能用extern 引用。
第七章、數組
7.1、一維數組的定義和使用
特別需要注意循環體的初值,終止條件
例:
Main()
{
Int I,a[10];
For(i=0;i<=9;i++)
A=I;
For(i=9;i>=0;i--)
Printf(「%d」,a);
}
注意下標問題
7.2、二維數組的定義和使用
二維數組的初始化
例如:
Int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
Int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
Int a[ ][4]={1,2,3,4,5,6,7,8,9,10,11,12};
Int a[ ][4]={{1,2,3,4},{5},{9,10,11,12}};
例如:int a[3][3]={{1},{2},{3}};
是對每一行的第一列元素賦值,未賦值的元素取0
7.3、字元數組和 字元串
字元串用字元數組來處理,結束標志符 『\0』
如:char c[ ]={「I am happy」};
用字元串常量使字元數組初值化
Char c[ ]={『I』,』 『,』a』,』m』,』 『,』h』,』a』,』p』,』p』,』y』,』\0』};
第八章、指針
8.1、地址和指針的概念
Int I;
Int *i_point;
8.2、指針變數和變數的地址
操作符:* &
8.3、指針和一維數組
若有定義
Int a[10];
Int *p=a;
分析下面表達式的含義:
A, &a,
*(a+i), a+I,
*(p+i), p+i
A=*(a+i)=*(P+i)
&a=a+i=p+i
8.4、指針與字元串
Main()
{
Char string[ ]=」I love china!」;
Printf(「%s\n」,string);
}
Main()
{ char *string=」I love china!」;
Printf(「%s\n」,string);
}
8.5、指針變數作為函數參數
形參的定義方式;實參的形式;參數的傳遞方式。
第九章、結構體
9.1、結構體類型和變數的定義
Struct 結構體名
{成員列表};
Struct student
{char stuNO[8];<br>Char name[20];<br>Char sex;<br>Int age;<br>Float score;<br>Char addr[30];<br>};
Stuct student
{char stuNO[8];<br>Char name[20];<br>Char sex;<br>Int age;<br>Float score;<br>Char addr[30];<br>};
Struct student stu1, stu2;
9.2、結構體變數的引用
一般形式為:
結構體變數名.成員名
9.3、結構體數組
結構體數組 結構體數組元素.成員名
指向結構體的指針變數
(*p).成員名
p->成員名
其他
Strcpy(字元數組1,字元串2)
Strcat(字元數組1,字元數組2)
Strcmp(字元串1,字元串2)
Strlen(字元數組)
H. C語言:預處理是什麼意思就是看不懂這句話的解釋!越具體越好,也可以通俗一點!
第十一章 預處理概述
在前面各章中,已多次使用過以「#」號開頭的預處理命令。如包含命令# include,宏定義命令# define等。在源程序中這些命令都放在函數之外, 而且一般都放在源文件的前面,它們稱為預處理部分。所謂預處理是指在進行編譯的第一遍掃描(詞法掃描和語法分析)之前所作的工作。預處理是C語言的一個重要功能, 它由預處理程序負責完成。當對一個源文件進行編譯時, 系統將自動引用預處理程序對源程序中的預處理部分作處理, 處理完畢自動進入對源程序的編譯。C語言提供了多種預處理功能,如宏定義、文件包含、 條件編譯等。合理地使用預處理功能編寫的程序便於閱讀、修改、 移植和調試,也有利於模塊化程序設計。本章介紹常用的幾種預處理功能。宏定義
在C語言源程序中允許用一個標識符來表示一個字元串, 稱為「宏」。被定義為「宏」的標識符稱為「宏名」。在編譯預處理時,對程序中所有出現的「宏名」,都用宏定義中的字元串去代換, 這稱為「宏代換」或「宏展開」。宏定義是由源程序中的宏定義命令完成的。 宏代換是由預處理程序自動完成的。在C語言中,「宏」分為有參數和無參數兩種。 下面分別討論這兩種「宏」的定義和調用。 無參宏定義
無參宏的宏名後不帶參數。其定義的一般形式為: #define 標識符 字元串 其中的「#」表示這是一條預處理命令。凡是以「#」開頭的均為預處理命令。「define」為宏定義命令。 「標識符」為所定義的宏名。「字元串」可以是常數、表達式、格式串等。在前面介紹過的符號常量的定義就是一種無參宏定義。 此外,常對程序中反復使用的表達式進行宏定義。例如: # define M (y*y+3*y) 定義M表達式(y*y+3*y)。在編寫源程序時,所有的(y*y+3*y)都可由M代替,而對源程序作編譯時,將先由預處理程序進行宏代換,即用(y*y+3*y)表達式去置換所有的宏名M,然後再進行編譯。
#define M (y*y+3*y)
main(){
int s,y;
printf("input a number: ");
scanf("%d",&y);
s=3*M+4*M+5*M;
printf("s=%d\n",s);
}
上常式序中首先進行宏定義,定義M表達式(y*y+3*y),在s= 3*M+4*M+5* M中作了宏調用。在預處理時經宏展開後該語句變為:s=3*(y*y+3*y)+4(y*y+3*y)+5(y*y+3*y);但要注意的是,在宏定義中表達式(y*y+3*y)兩邊的括弧不能少。否則會發生錯誤。
當作以下定義後: #difine M y*y+3*y在宏展開時將得到下述語句: s=3*y*y+3*y+4*y*y+3*y+5*y*y+3*y;這相當於; 3y�2+3y+4y�2+3y+5y�2+3y;顯然與原題意要求不符。計算結果當然是錯誤的。 因此在作宏定義時必須十分注意。應保證在宏代換之後不發生錯誤。對於宏定義還要說明以下幾點:1. 宏定義是用宏名來表示一個字元串,在宏展開時又以該字元串取代宏名,這只是一種簡單的代換,字元串中可以含任何字元,可以是常數,也可以是表達式,預處理程序對它不作任何檢查。如有錯誤,只能在編譯已被宏展開後的源程序時發現。2. 宏定義不是說明或語句,在行末不必加分號,如加上分號則連分號也一起置換。3. 宏定義必須寫在函數之外,其作用域為宏定義命令起到源程序結 束。如要終止其作用域可使用# undef命令,例如: # define PI 3.14159
main()
{
……
}
# undef PIPI的作用域
f1()
....表示PI只在main函數中有效,在f1中無效。
4. 宏名在源程序中若用引號括起來,則預處理程序不對其作宏代換。
#define OK 100
main()
{
printf("OK");
printf("\n");
}
上例中定義宏名OK表示100,但在printf語句中OK被引號括起來,因此不作宏代換。程序的運行結果為:OK這表示把「OK」當字元串處理。5. 宏定義允許嵌套,在宏定義的字元串中可以使用已經定義的宏名。在宏展開時由預處理程序層層代換。例如: #define PI 3.1415926
#define S PI*y*y /* PI是已定義的宏名*/對語句: printf("%f",s);在宏代換後變為: printf("%f",3.1415926*y*y);6. 習慣上宏名用大寫字母表示,以便於與變數區別。但也允許用小寫字母。7. 可用宏定義表示數據類型,使書寫方便。例如: #define STU struct stu在程序中可用STU作變數說明: STU body[5],*p;#define INTEGER int在程序中即可用INTEGER作整型變數說明: INTEGER a,b; 應注意用宏定義表示數據類型和用typedef定義數據說明符的區別。宏定義只是簡單的字元串代換,是在預處理完成的,而typedef是在編譯時處理的,它不是作簡單的代換, 而是對類型說明符重新命名。被命名的標識符具有類型定義說明的功能。請看下面的例子: #define PIN1 int*typedef (int*) PIN2;從形式上看這兩者相似, 但在實際使用中卻不相同。下面用PIN1,PIN2說明變數時就可以看出它們的區別: PIN1 a,b;在宏代換後變成 int *a,b;表示a是指向整型的指針變數,而b是整型變數。然而:PIN2 a,b;表示a,b都是指向整型的指針變數。因為PIN2是一個類型說明符。由這個例子可見,宏定義雖然也可表示數據類型, 但畢竟是作字元
代換。在使用時要分外小心,以避出錯。8. 對「輸出格式」作宏定義,可以減少書寫麻煩。例9.3 中就採用了這種方法。
#define P printf
#define D "%d\n"
#define F "%f\n"
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);
}帶參宏定義C語言允許宏帶有參數。在宏定義中的參數稱為形式參數, 在宏調用中的參數稱為實際參數。對帶參數的宏,在調用中,不僅要宏展開, 而且要用實參去代換形參。帶參宏定義的一般形式為: #define 宏名(形參表) 字元串 在字元串中含有各個形參。帶參宏調用的一般形式為: 宏名(實參表);
例如:
#define M(y) y*y+3*y /*宏定義*/
:
k=M(5); /*宏調用*/
: 在宏調用時,用實參5去代替形參y, 經預處理宏展開後的語句
為: k=5*5+3*5
#define MAX(a,b) (a>b)?a:b
main(){
int x,y,max;
printf("input two numbers: ");
scanf("%d%d",&x,&y);
max=MAX(x,y);
printf("max=%d\n",max);
}
上常式序的第一行進行帶參宏定義,用宏名MAX表示條件表達式(a>b)?a:b,形參a,b均出現在條件表達式中。程序第七行max=MAX(x,
y)為宏調用,實參x,y,將代換形參a,b。宏展開後該語句為: max=(x>y)?x:y;用於計算x,y中的大數。對於帶參的宏定義有以下問題需要說明:1. 帶參宏定義中,宏名和形參表之間不能有空格出現。
例如把: #define MAX(a,b) (a>b)?a:b寫為: #define MAX (a,b) (a>b)?a:b 將被認為是無參宏定義,宏名MAX代表字元串 (a,b)(a>b)?a:b。
宏展開時,宏調用語句: max=MAX(x,y);將變為: max=(a,b)(a>b)?a:b(x,y);這顯然是錯誤的。2. 在帶參宏定義中,形式參數不分配內存單元,因此不必作類型定義。而宏調用中的實參有具體的值。要用它們去代換形參,因此必須作類型說明。這是與函數中的情況不同的。在函數中,形參和實參是兩個不同的量,各有自己的作用域,調用時要把實參值賦予形參,進行「值傳遞」。而在帶參宏中,只是符號代換,不存在值傳遞的問題。3. 在宏定義中的形參是標識符,而宏調用中的實參可以是表達式。
#define SQ(y) (y)*(y)
main(){
int a,sq;
printf("input a number: ");
scanf("%d",&a);
sq=SQ(a+1);
printf("sq=%d\n",sq);
}
上例中第一行為宏定義,形參為y。程序第七行宏調用中實參為a+1,是一個表達式,在宏展開時,用a+1代換y,再用(y)*(y) 代換SQ,得到如下語句: sq=(a+1)*(a+1); 這與函數的調用是不同的, 函數調用時要把實參表達式的值求出來再賦予形參。 而宏代換中對實參表達式不作計算直接地照原樣代換。4. 在宏定義中,字元串內的形參通常要用括弧括起來以避免出錯。 在上例中的宏定義中(y)*(y)表達式的y都用括弧括起來,因此結果是正確的。如果去掉括弧,把程序改為以下形式:
#define SQ(y) y*y
main(){
int a,sq;
printf("input a number: ");
scanf("%d",&a);
sq=SQ(a+1);
printf("sq=%d\n",sq);
}
運行結果為:input a number:3
sq=7 同樣輸入3,但結果卻是不一樣的。問題在哪裡呢? 這是由於代換只作符號代換而不作其它處理而造成的。 宏代換後將得到以下語句: sq=a+1*a+1; 由於a為3故sq的值為7。這顯然與題意相違,因此參數兩邊的括弧是不能少的。即使在參數兩邊加括弧還是不夠的,請看下面程序:
#define SQ(y) (y)*(y)
main(){
int a,sq;
printf("input a number: ");
scanf("%d",&a);
sq=160/SQ(a+1);
printf("sq=%d\n",sq);
}
本程序與前例相比,只把宏調用語句改為: sq=160/SQ(a+1); 運行本程序如輸入值仍為3時,希望結果為10。但實際運行的結果如下:input a number:3sq=160為什麼會得這樣的結果呢?分析宏調用語句,在宏代換之後變為: sq=160/(a+1)*(a+1);a為3時,由於「/」和「*」運算符優先順序和結合性相同, 則先作160/(3+1)得40,再作40*(3+1)最後得160。為了得到正確答案應在宏定義中的整個字元串外加括弧, 程序修改如下
#define SQ(y) ((y)*(y))
main(){
int a,sq;
printf("input a number: ");
scanf("%d",&a);
sq=160/SQ(a+1);
printf("sq=%d\n",sq);
}
以上討論說明,對於宏定義不僅應在參數兩側加括弧, 也應在整個字元串外加括弧。5. 帶參的宏和帶參函數很相似,但有本質上的不同,除上面已談到的各點外,把同一表達式用函數處理與用宏處理兩者的結果有可能是不同的。main(){
int i=1;
while(i<=5)
printf("%d\n",SQ(i++));
}
SQ(int y)
{
return((y)*(y));
}#define SQ(y) ((y)*(y))
main(){
int i=1;
while(i<=5)
printf("%d\n",SQ(i++));
}
在上例中函數名為SQ,形參為Y,函數體表達式為((y)*(y))。在例9.6中宏名為SQ,形參也為y,字元串表達式為(y)*(y))。 兩例是相同的。例9.6的函數調用為SQ(i++),例9.7的宏調用為SQ(i++),實參也是相同的。從輸出結果來看,卻大不相同。分析如下:在例9.6中,函數調用是把實參i值傳給形參y後自增1。 然後輸出函數值。因而要循環5次。輸出1~5的平方值。而在例9.7中宏調用時,只作代換。SQ(i++)被代換為((i++)*(i++))。在第一次循環時,由於i等於1,其計算過程為:表達式中前一個i初值為1,然後i自增1變為2,因此表達式中第2個i初值為2,兩相乘的結果也為2,然後i值再自增1,得3。在第二次循環時,i值已有初值為3,因此表達式中前一個i為3,後一個i為4, 乘積為12,然後i再自增1變為5。進入第三次循環,由於i 值已為5,所以這將是最後一次循環。計算表達式的值為5*6等於30。i值再自增1變為6,不再滿足循環條件,停止循環。從以上分析可以看出函數調用和宏調用二者在形式上相似, 在本質上是完全不同的。6. 宏定義也可用來定義多個語句,在宏調用時,把這些語句又代換到源程序內。看下面的例子。
#define SSSV(s1,s2,s3,v) s1=l*w;s2=l*h;s3=w*h;v=w*l*h;
main(){
int l=3,w=4,h=5,sa,sb,sc,vv;
SSSV(sa,sb,sc,vv);
printf("sa=%d\nsb=%d\nsc=%d\nvv=%d\n",sa,sb,sc,vv);
}
程序第一行為宏定義,用宏名SSSV表示4個賦值語句,4 個形參分別為4個賦值符左部的變數。在宏調用時,把4 個語句展開並用實參代替形參。使計算結果送入實參之中。文件包含文件包含是C預處理程序的另一個重要功能。文件包含命令行的一般形式為: #include"文件名" 在前面我們已多次用此命令包含過庫函數的頭文件。例如:
#include"stdio.h"
#include"math.h"
文件包含命令的功能是把指定的文件插入該命令行位置取代該命令行, 從而把指定的文件和當前的源程序文件連成一個源文件。在程序設計中,文件包含是很有用的。 一個大的程序可以分為多個模塊,由多個程序員分別編程。 有些公用的符號常量或宏定義等可單獨組成一個文件, 在其它文件的開頭用包含命令包含該文件即可使用。這樣,可避免在每個文件開頭都去書寫那些公用量, 從而節省時間,並減少出錯。對文件包含命令還要說明以下幾點:
1. 包含命令中的文件名可以用雙引號括起來,也可以用尖括弧括起來。例如以下寫法都是允許的: #include"stdio.h"#include<math.h> 但是這兩種形式是有區別的:使用尖括弧表示在包含文件目錄中去查找(包含目錄是由用戶在設置環境時設置的), 而不在源文件目錄去查找; 使用雙引號則表示首先在當前的源文件目錄中查找,若未找到才到包含目錄中去查找。 用戶編程時可根據自己文件所在的目錄來選擇某一種命令形式。2. 一個include命令只能指定一個被包含文件, 若有多個文件要包含,則需用多個include命令。3. 文件包含允許嵌套,即在一個被包含的文件中又可以包含另一個文件。條件編譯預處理程序提供了條件編譯的功能。 可以按不同的條件去編譯不同的程序部分,因而產生不同的目標代碼文件。 這對於程序的移植和調試是很有用的。 條件編譯有三種形式,下面分別介紹:
1. 第一種形式:
#ifdef 標識符
程序段1
#else
程序段2
#endif
它的功能是,如果標識符已被 #define命令定義過則對程序段1進行編譯;否則對程序段2進行編譯。如果沒有程序段2(它為空),本格式中的#else可以沒有, 即可以寫為:
#ifdef 標識符
程序段 #endif
#define NUM ok
main(){
struct stu
{
int num;
char *name;
char sex;
float score;
} *ps;
ps=(struct stu*)malloc(sizeof(struct stu));
ps->num=102;
ps->name="Zhang ping";
ps->sex='M';
ps->score=62.5;
#ifdef NUM
printf("Number=%d\nScore=%f\n",ps->num,ps->score);
#else
printf("Name=%s\nSex=%c\n",ps->name,ps->sex);
#endif
free(ps);
}
由於在程序的第16行插入了條件編譯預處理命令, 因此要根據NUM是否被定義過來決定編譯那一個printf語句。而在程序的第一行已對NUM作過宏定義,因此應對第一個printf語句作編譯故運行結果是輸出了學號和成績。在程序的第一行宏定義中,定義NUM表示字元串OK,其實也可以為任何字元串,甚至不給出任何字元串,寫為: #define NUM 也具有同樣的意義。 只有取消程序的第一行才會去編譯第二個printf語句。讀者可上機試作。2. 第二種形式:
#ifndef 標識符
程序段1
#else
程序段2
#endif
與第一種形式的區別是將「ifdef」改為「ifndef」。它的功能是,如果標識符未被#define命令定義過則對程序段1進行編譯, 否則對程序段2進行編譯。這與第一種形式的功能正相反。3. 第三種形式:
#if 常量表達式
程序段1
#else
程序段2
#endif
它的功能是,如常量表達式的值為真(非0),則對程序段1 進行編譯,否則對程序段2進行編譯。因此可以使程序在不同條件下,完成不同的功能
#define R 1
main(){
float c,r,s;
printf ("input a number: ");
scanf("%f",&c);
#if R
r=3.14159*c*c;
printf("area of round is: %f\n",r);
#else
s=c*c;
printf("area of square is: %f\n",s);
#endif
}
本例中採用了第三種形式的條件編譯。在程序第一行宏定義中,定義R為1,因此在條件編譯時,常量表達式的值為真, 故計算並輸出圓面積。上面介紹的條件編譯當然也可以用條件語句來實現。 但是用條件語句將會對整個源程序進行編譯,生成的目標代碼程序很長,而採用條件編譯,則根據條件只編譯其中的程序段1或程序段2, 生成的目標程序較短。如果條件選擇的程序段很長, 採用條件編譯的方法是十分必要的。