《C語言程序設計》課件第04章_第1頁
《C語言程序設計》課件第04章_第2頁
《C語言程序設計》課件第04章_第3頁
《C語言程序設計》課件第04章_第4頁
《C語言程序設計》課件第04章_第5頁
已閱讀5頁,還剩102頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

第4章函數與編譯預處理語言程序設計第四章函數與編譯預處理C4.1函數的概念及其分類4.2函數的定義4.3函數的聲明與調用4.4變量的作用域和存儲類別4.5內部函數與外部函數4.6遞歸函數4.7編譯預處理4.8綜合范例4.9本章小結4.10問與答內容簡介4.1函數的概念及其分類語言程序設計第四章函數與編譯預處理C在第一章中已經介紹過,C源程序是由函數組成的。雖然在前面各章的程序中都只有一個主函數main(),但真正實用的程序往往由大量的小函數而不是由少量大函數構成的,即所謂“小函數構成大程序”。這樣的好處是讓各部分相對簡單獨立,并且任務單一。

所謂函數就是一個具有一定功能、且相對獨立的、可供其它程序調用的程序模塊。它是C源程序的基本模塊,通過對函數模塊的調用實現特定的功能。C語言中的函數相當于其它高級語言的子程序(或過程)。4.1函數的概念及其分類語言程序設計第四章函數與編譯預處理CC語言不僅提供了極為豐富的庫函數(如TurboC,MSC都提供了三百多個庫函數),還允許用戶建立自己定義的函數。用戶可把自己的算法編成一個個相對獨立的函數模塊,然后采取調用的方法來使用函數??梢哉fC程序的全部工作都是由各式各樣的函數完成的,所以也有人把C語言稱為函數式語言。由于采用了函數模塊式的結構,C語言易于實現結構化程序設計。使程序的層次結構清晰,便于程序的編寫、閱讀、調試。4.1函數的概念及其分類語言程序設計第四章函數與編譯預處理C在C語言中可從不同的角度對函數分類。1.從函數定義的角度看,函數可分為庫函數和用戶定義函數兩種。1)庫函數由C系統(tǒng)提供,用戶無須定義,只需在程序前用#include宏命令包含要調用的函數原型的頭文件,即可在程序中直接調用。在前面各章的例題中反復用到printf

、scanf

、gets、puts等函數均屬此類。2)用戶自定義函數自定義函數是由程序設計者根據問題的需要自己定義的。

語言程序設計第四章函數與編譯預處理C2.C語言的函數兼有其它語言中的函數和過程兩種功能,從這個角度看,又可把函數分為有返回值函數和無返回值函數兩種。(1)有返回值函數此類函數被調用執(zhí)行完成之后,將向調用者返回一個執(zhí)行結果,稱為函數返回值。

(2)無返回值函數此類函數用于完成某項特定的處理任務,執(zhí)行完成之后不向調用者返回函數值。4.1函數的概念及其分類語言程序設計第四章函數與編譯預處理C3.從主調函數和被調函數之間數據傳送的角度看又可分為無參函數和有參函數兩種。(1)無參函數函數定義、函數聲明及函數調用中均不帶參數。主調函數和被調函數之間不進行參數傳送。此類函數通常用來完成一些特定的功能。(2)有參函數也稱為帶參函數。在函數定義及函數聲明時都有參數,定義時指定的參數稱為形式參數(簡稱為形參)。在函數調用時也必須給出參數值或有值的表達式,調用時給出的參數稱為實際參數(簡稱為實參)。進行函數調用時,主調函數將把實參的值傳送給形參,供被調函數使用。4.1函數的概念及其分類4.2函數的定義語言程序設計第四章函數與編譯預處理C函數定義的一般形式1.現代定義形式:[存儲類別][返回值類型]函數名稱([類型標識1形參名稱1[,類型標識2形參名稱2…..]]){[變量定義;…]語句1;……語句n;}

4.2函數的定義語言程序設計第四章函數與編譯預處理C函數定義的一般形式2.傳統(tǒng)的定義形式[存儲類別][返回值類型]函數名([形參名稱1[,形參名稱2…..]])[類型標識1形參名稱1;[類型標識2形參名稱2;….]]{[變量定義;…]語句1;……語句n;}

4.2函數的定義語言程序設計第四章函數與編譯預處理C【例4-1】編寫程序求解任意兩個數的最大公約數,并輸出結果。int

Maxcd(int

X,intY)/*利用輾轉相除法求整數X與Y的最大公約數函數*/{intR;R=X;

if(X<Y){X=Y;Y=R;}

while(R=X%Y){X=Y;Y=R;}returnY;}voidTellMessage(){printf("Inputvaluesintoa&b,please!");}Ex4_1.c演示4.2函數的定義語言程序設計第四章函數與編譯預處理Cvoidmain(){int

a,b,R;

TellMessage();/*調用TellMessage()函數,提示輸入數據*/

scanf("%d%d",&a,&b);R=Maxcd(a,b);/*調用Maxcd(a,b)函數,求出a和b最大公約數并賦給R*/

printf("%d和%d的最大公約數是%d\n",a,b,R);}

程序說明:以上Maxcd(int

X,intY)函數的功能是專門求解兩數的最大公約數。其中的X和Y是形式參數。該函數有返回值(就是X和Y的最大公約數),返回值是整型,因此也被稱之為整型函數。TellMessage()函數的功能只是在屏幕上給出提示信息:Inputvaluesintoa&b,please!它既是一個無參的,也是一個無返回值的函數。語言程序設計第四章函數與編譯預處理C關于函數定義的幾點說明:(1)[存儲類別]指明函數的有效范圍,此類型標識符只有兩種:static和extern。static規(guī)定了當前定義的函數,只能被定義它的那個文件中的其它函數調用,不能被其它文件中的函數調用,因此,這類函數又叫作內部函數;extern說明了當前定義的函數可供所有文件中的函數調用,因而又稱為外部函數。默認(不明確指定)的是extern,即外部函數。(2)[返回值類型]規(guī)定函數有無返回值或規(guī)定函數執(zhí)行結束時,返回值的數據類型,默認的是整型(int)。如果函數不需要或沒有返回值,應當明確用void說明。4.2函數的定義語言程序設計第四章函數與編譯預處理C(3)函數名稱必須是合法的標識符,其與變量命名規(guī)則相同。正規(guī)的程序設計,變量、函數與符號常量的取名最好要見名知義,這樣利于提高程序的可讀性和可維護性。良好的命名習慣是優(yōu)秀程序員的標志之一。(4)[存儲類別][返回值類型]函數名稱([類型標識1形參名稱1[,類型標識2形參名稱2…..]])或者[存儲類別][返回值類型]函數名([形參名稱1[,形參名稱2…..]])[類型標識1形參名稱1;[類型標識2形參名稱2;….]]被稱之為函數頭,緊接其后的一對花括號”{“和“}”及其中的語句稱之為函數體。4.2函數的定義語言程序設計第四章函數與編譯預處理C(5)[]中的說明符是可有可無的,例如[形參名稱1[,形參名稱2…..]]表示函數定義中的形式參數個數≥0。(6)函數體中表示函數結束的三種情況:㈠函數有返回值時,則函數體中表示函數結束的返回語句形式是:return表達式;或return(表達式);㈡函數無返回值時,則函數體中函數結束的返回語句形式是:return;㈢函數無返回值,且函數體中最后一條語句就表示函數的結束,則函數體中無需返回語句return;㈣函數體中可以有多條返回語句。但是,只能有其中的一個返回語句可以被執(zhí)行。4.2函數的定義語言程序設計第四章函數與編譯預處理C(7)有返回值的函數,對它的調用可以出現在表達式中,對無返回值的函數調用只能作為一條單獨的語句。(8)函數調用語句中每個實參的數據類型,必須保證與函數定義中的每個形參一一對應相容或相同。(9)函數有返回值時,如果其返回值的類型與定義函數指定的數據類型不一致,則系統(tǒng)會自動轉換為定義函數指定的數據類型。10)函數定義不能嵌套,即函數體中不能再包含任何函數的定義。4.2函數的定義語言程序設計第四章函數與編譯預處理C【例4-2】編寫函數實現從N個整數中找出其中的素數,并且按照從小到大的順序輸出這些素數以及這些素數的平均值(平均值保留兩位小數)。#include"stdio.h"#defineNum10#defineTrue1#defineFalse0/*以下是利用選擇排序法把數組中N個元素的值從小到大排序的函數*/voidNsort(intA[],intN){intI,J,K,T;for(I=0;I<N-1;I++){K=I;for(J=I+1;J<N;J++)if(A[K]>A[J])K=J;if(I!=K){T=A[I];A[I]=A[K];A[K]=T;}}}4.2函數的定義Ex4_2.c演示語言程序設計第四章函數與編譯預處理CvoidAcceptINum(intA[],intN);/*為了使得主函數main能夠調用它而對其進行聲明*/voidmain(){int

I,IArray[Num],Pn=0;floatfAverage=0;

printf("Input10NumbersintoArray,please!");

AcceptINum(IArray,Num);/*調用AcceptINum函數接收鍵盤輸入Num個數*/Nsort(IArray,Num);/*調用Nsort

函數,對IArray

中的元素值進行排序*/printf("ThesortedNumbersAre:\n");/*從小到大輸出各個數*/for(I=0;I<Num;I++)printf(I==Num-1?"%d\n":"%d,",IArray[I]);4.2函數的定義語言程序設計第四章函數與編譯預處理Cprintf("\nThePrimesare:");for(I=0;I<Num;I++)/*從小到大輸出各個素數及素數平均值*/if(IsPrime(IArray[I])){printf("%d,",IArray[I]);fAverrage+=IArray[I];Pn++;}if(Pn!=0)printf("The

AverrageofPrimesis:%.2f\n",fAverrage/Pn);elseprintf("ThereisnoneofPrimes!\n");}/*以下是接收鍵盤輸入N個整數函數*/voidAcceptINum(intA[],intN){intK;

for(K=0;K<N;K++)

scanf("%d",&A[K]);return;/*因為該函數沒有返回值,所以此處的

return可以刪去*/}4.2函數的定義語言程序設計第四章函數與編譯預處理C/*以下是判斷一個整數是否是素數,若是素數,1返回給調用者,否則0返回給調用者*/int

IsPrime(X)intX;{intI,N;

for(I=2;I<=X/2;I++)if(X%I==0)returnFalsereturnTrue;}4.2函數的定義語言程序設計第四章函數與編譯預處理C程序說明:以上的例子中①函數Nsort(intA[],intN)沒有返回值,因此可以不需要返回語句。②函數IsPrime(intX)雖然只有一個返回值,但可以有兩個返回語句。③函數AcceptINum(int

A[],intN)雖然沒有返回值,也可以用return語句表示函數結束(當然此處的return語句也可以不需要)。④本例中用到了一維數組(數組的概念以及數組的應用要到下一章才學到),為了便于理解,在此對本例中用到的數組,作簡單的說明:這里主函數main中定義的數組IArray[Num],其中的Num是一個符號常量10,即Num與整數10是等價的。元素的引用形式是數組名稱[整型表達式],并且整型表達式的值是0~Num-1。因此,可以把數組元素看成是一個普通的變量。

4.2函數的定義語言程序設計第四章函數與編譯預處理C一個函數能被另一個函數調用需要以下幾個條件:(1)該函數必須已經存在,無論是庫函數還是用戶自定義函數。(2)調用系統(tǒng)提供的庫函數時,與被調用函數相關的頭文件,必須在源程序的開頭用#include命令把它包含進來。(3)用戶自定義函數,在被調用之前,需要對自定義函數進行聲明。

4.3函數的聲明與調用

4.3.1函數能被使用的條件語言程序設計第四章函數與編譯預處理C被調用的用戶自定義函數,同時具備下列情況,需要對它進行聲明。(1)與調用它的主調函數在同一文件中,(2)它的定義位置在調用它的那個主調函數之后,(3)它的返回值類型不是整型,也不是字符型的;(4)在調用它的那個主調函數之前沒有被聲明4.3函數的聲明與調用

4.3.2函數聲明的場合語言程序設計第四章函數與編譯預處理C(1)[返回值類型]函數名稱([類型標識1形參名稱1[,類型標識2形參名稱2…..]]);或者:(2)[返回值類型]函數名稱([類型標識1[,類型標識2…..]]);最簡單的方法就是把被調用函數的函數頭,復制到調用它的主調函數之前,然后再加一個分號;就一切搞定了!其實函數聲明格式中的形參部分只需給出每個參數的數據類型就可以了,無須給出形參的名稱,即使給出形參的名稱,其名稱可以隨意,只要名稱符合標識符命名規(guī)則即可。4.3函數的聲明與調用

4.3.3函數聲明的格式語言程序設計第四章函數與編譯預處理C(1)在所有函數的最前面;如【例4-3】所示。(2)在所有函數的外部,在調用它的主調函數之前;如【例4-4】所示的f2,f3,f4函數的聲明?!纠?-4】(3)在調用它的主調函數內部,此時,可以把它看作定義變量那樣對待;如【例4-4】所示的f1函數的聲明和【例4-5】所示的f1,f2,f3,f4函數的聲明

4.3函數的聲明與調用

4.3.4函數聲明的位置語言程序設計第四章函數與編譯預處理C【例4-3】/*在程序的最前面聲明*/floatf1(floatx,floaty);floatf2(float,float);floatf3(floata,floatb);floatf4(float,float);main(){floata=78.68,b=36.43;

clrscr();

printf("sum=%f\n",f1(a,b));}floatf1(floatx,floaty){return(f2(x,y)*f3(x,y));}floatf2(floats,floatr){returns+r;}floatf3(floatu,floatv){return(u-v)*f4(u,v);}floatf4(floatu,floatv){returnu/v;}4.3函數的聲明與調用Ex4_3.c演示語言程序設計第四章函數與編譯預處理C4.3函數的聲明與調用【例4-4】main()/*在調用函數的前面聲明*/{floatf1(floatx,floaty);floata=78.68,b=36.43;

clrscr();printf("sum=%f\n",f1(a,b));}floatf2(float,float),f3(floata,floatb),f4(float,float);floatf1(floatx,floaty){return(f2(x,y)*f3(x,y));}floatf2(floats,floatr){returns+r;}floatf3(floatu,floatv){return(u-v)*f4(u,v);}floatf4(floatu,floatv){returnu/v;}Ex4_4.c演示語言程序設計第四章函數與編譯預處理C【例4-5】main()/*在main函數中的聲明*/{floatf1(floatx,floaty),f2(float,float),f3(floata,floatb),f4(float,float);floata=78.68,b=36.43;

clrscr();

printf("sum=%f\n",f1(a,b));}floatf1(floatx,floaty){return(f2(x,y)*f3(x,y));}floatf2(floats,floatr){returns+r;}floatf3(floatu,floatv){return(u-v)*f4(u,v);}floatf4(floatu,floatv){returnu/v;}4.3函數的聲明與調用Ex4_5.c演示語言程序設計第四章函數與編譯預處理C注意!以下三種情況可以省略對被調函數的聲明:(1)如果被調函數的定義出現在主調函數之前,則可以省略聲明。如【例4-2】的Nsort函數。(2)如果在所有函數定義之前,在函數的外部已作了函數聲明,則在以后的各主調函數中,可以不必對被調函數作聲明。如【例4-3】的f1、f2、f3、f4函數。(3)如果函數類型為整型或字符型,則在主調函數中可以不要聲明。如【例4-2】中的IsPrime(X)函數。但使用這種方法時,系統(tǒng)無法對參數的類型作檢查,因此在調用時若參數使用不當,編譯時也不報錯。為了程序的安全,建議都加以聲明為好。4.3函數的聲明與調用

4.3.4函數聲明的位置語言程序設計第四章函數與編譯預處理C一個函數被定義之后,不被別的函數使用,即使功能再強大,也發(fā)揮不了任何作用。通過函數調用語句來達到使用函數的目的。函數調用語句有以下兩種形式:(1)無返回值的函數調用語句格式:函數名稱([實參列表]);(2)有返回值的函數調用語句格式:變量名=函數名稱([實參列表]);此處的變量名的數據類型必須與函數返回值的數據類型相容。無論是上述哪種調用語句,都會去執(zhí)行被調用函數體中的語句,函數執(zhí)行完畢,會返回到函數調用語句之后,繼續(xù)執(zhí)行調用函數語句之后的其它語句。注意:前面說過函數不可以嵌套定義。但是函數可以嵌套調用。

4.3函數的聲明與調用

4.3.5函數的調用、參數與返回值語言程序設計第四章函數與編譯預處理C【例4-6】編寫程序求5個整數中的最大數,并輸出。floatmax(intX,floatY)/*求兩數中最大值的函數*/{if(X>=Y)returnX;elsereturnY;}voidmain(){int

a,b,c,d,e,Maxvalue;

printf("Intputvaluesintoa,b,c,d&e,please!\n");

scanf("%d%d%d%d%d",&a,&b,&c,&d,&e);

Maxvalue=max(a,max(max(max(b,c),d),e));/*函數max多重嵌套調用自己*//*也可改成maxvalue=max(max(max(a,b),max(c,d)),e);*/

printf("Maxvalue=%d\n",Maxvalue); }4.3函數的聲明與調用

4.3.5函數的調用、參數與返回值Ex4_6.c演示語言程序設計第四章函數與編譯預處理C注意!(1)函數返回值的類型取決于函數定義中的函數名稱之前的數據類型,與return后表達式值的類型無關。如果return后表達式值的類型與函數的定義中函數名稱之前的數據類型不一致,系統(tǒng)會自動轉換。例如函數max返回值的類型是實型,函數體中的if(X>=Y)returnX;elsereturnY;語句,不論返回值表達式是X還是Y,它返回值的類型總是實型。(2)函數調用語句中的實參數量與形參數量必須相等,與形參相對應的實參數據類型必須相容。4.3函數的聲明與調用

4.3.5函數的調用、參數與返回值語言程序設計第四章函數與編譯預處理C函數調用中,如果函數有參數,則系統(tǒng)會按照函數形式參數的個數和對應的數據類型,自動申請內存區(qū)域,用于存放函數調用語句中對應的實際參數的值。被調用的函數中形參的值變化與否,都不影響函數調用語句中實際參數的值。即函數的參數值傳遞是單向的,在函數調用時,系統(tǒng)只是把實際參數的值復制到對應形式參數的內存區(qū)域。

4.3函數的聲明與調用

4.3.6函數的傳值方式語言程序設計第四章函數與編譯預處理C【例4-7】voidExchange(float

X,floatY){floatT;T=X;X=Y;Y=T;printf("Innerexchange:X=%3.0f,Y=%3.0f\n",X,Y);}main(){floatX=5,Y=8;printf("Beforeexchange:X=%3.0f,Y=%3.0f\n",X,Y);Exchange(X,Y);printf("Afterexchange:X=%3.0f,Y=%3.0f\n",X,Y);

}4.3函數的聲明與調用

4.3.6函數的傳值方式Ex4_7.c演示語言程序設計第四章函數與編譯預處理C程序說明:【例4-7】中主函數main執(zhí)行到調用語句Exchange(X,Y);時,此后的程序執(zhí)行過程如下:1、系統(tǒng)會把此處實參X、Y的值5和8分別賦值給函數Exchange中的形參變量X和Y,2、然后,開始執(zhí)行Exchange函數,3、在Exchange函數中,通過T=X;X=Y;Y=T;語句序列的執(zhí)行,實現了Exchange函數中對形參X和Y中值的交換,4、因此,執(zhí)行到printf("Innerexchange:X=%3.0f,Y=%3.0f\n”,X,Y);語句時,其輸出結果是:Innerexchange:X=8,Y=5,說明在Exchange函數體內,確實實現了X與Y的交換。5、Exchange函數執(zhí)行完成,返回到主函數main中調用Exchange函數語句之后,繼續(xù)執(zhí)行下一條語句:printf("Afterexchange:X=%3.0f,Y=%3.0f\n",X,Y);6、該語句輸出的結果是:Afterexchange:X=5,Y=84.3函數的聲明與調用

4.3.6函數的傳值方式語言程序設計第四章函數與編譯預處理C局部變量:所謂局部變量,就是在函數或復合語句內定義的變量。函數體內定義的變量,其有效范圍只局限于定義它的函數體內。也就是只能在定義它的那個函數體內可以使用它,在函數體外部,它就無效了。同理,復合語句中定義的變量,只能在定義它的那個復合語句中使用它,出了復合語句,就不能引用它了!此外,形參只限于它所在的函數內部使用,因此形參是局部變量。全局變量:所謂全局變量,就是在函數外面定義的變量,因此又稱之為外部變量。它的有效范圍是從它的定義之點起到定義它的那個文件結束。與它在同一個文件中的所有函數都可以引用它。4.4變量的作用域和存儲類別

4.4.1變量的作用域語言程序設計第四章函數與編譯預處理C【例4-8】程序及其運行的結果intX=5;/*此處X是全局變量,在它后面的函數都可以引用它*/intAdd(intX,intY){intZ;/*此函數中的X,Y,Z均是局部變量*/Z=X+Y;returnZ

;}intSubX(intY){returnY-X;}/*此函數中的Y是局部變量,X是全局變量*/voidmain(){intX=8,Y=20,Z;/*此函數中的X,Y,Z均是局部變量*/Z=Add(X,Y);

printf(“%d+%d=%d\n”,X,Y,Z);Z=SubX(Y);printf(“%d-5=%d\n”,Y,Z);}

4.4變量的作用域和存儲類別Ex4_8.c演示語言程序設計第四章函數與編譯預處理C全局變量與局部變量的關系,如同中央政府頒布的國家法令與地方政府頒布的地方法令之間的關系。如果某省政府沒有頒布治安處罰條例,那么在該省內處理治安事件的依據,引用的就是國家治安處罰條例;如果該省政府頒布了該省的治安處罰條例,那么在該省內處理治安事件的依據,如果沒特別說明,引用的就是指該省的治安處罰條例。就是說局部變量掩蓋了同名的全局變量。

4.4變量的作用域和存儲類別

4.4.1變量的作用域語言程序設計第四章函數與編譯預處理C變量的存儲類別是指變量的存儲屬性,即變量被分配在內存的那種區(qū)域內。計算機內存一般被劃分成系統(tǒng)區(qū)和用戶區(qū)兩種,系統(tǒng)區(qū)是指系統(tǒng)程序駐留和工作的區(qū)域;用戶區(qū)就是用戶程序駐留和工作的場所。為了便于操作系統(tǒng)為程序分配存儲區(qū)域和控制用戶程序的運行,把程序分成執(zhí)行命令的操作(代碼)和被操作的數據兩部分進行存儲。其中數據區(qū)又被分成靜態(tài)存儲區(qū)和動態(tài)存儲區(qū)。因此用于存放用戶程序的用戶區(qū),被對應地分成代碼區(qū)和靜態(tài)存儲區(qū)和動態(tài)存儲區(qū)。一般來說,內存的動態(tài)區(qū)用于存放程序的局部變量,靜態(tài)區(qū)用于存放程序的靜態(tài)局部變量和全局變量。4.4變量的作用域和存儲類別

4.4.2變量的存儲類別語言程序設計第四章函數與編譯預處理C4.4變量的作用域和存儲類別

4.4.2變量的存儲類別系統(tǒng)區(qū)用戶區(qū)················動態(tài)存儲區(qū)靜態(tài)存儲區(qū)數據區(qū)程序區(qū)代碼區(qū)語言程序設計第四章函數與編譯預處理C變量的存儲類別有auto、register、static和exturn四種。auto是局部變量的默認存儲類別。register指定為變量分配寄存器,而不是內存區(qū)域,就是使用CPU的寄存器存放變量的值。auto、register只能用于定義局部變量。exturn是外部(全局)變量的默認存儲類別。static既可以用于定義局部變量,也可以用于定義全局變量。static用于定義局部變量時,出了定義該變量的那個程序塊(函數體或復合語句)其變量值仍然保留在內存中;如果被定義的變量未賦初值,系統(tǒng)自動予以置0。static用于定義全局變量時,其變量只能被定義它的那個文件中引用,其它文件不可使用它。此時定義的變量也稱之為內部變量。4.4變量的作用域和存儲類別

4.4.2變量的存儲類別語言程序設計第四章函數與編譯預處理C4.4.2.1靜態(tài)存儲的局部變量運用舉例【例4-9】分別求出1、2、3、4和5的階乘并輸出。其程序及其運行結果如下:voidfact(intN){staticlongs=1;/*s是靜態(tài)存儲的局部變量*/s=s*N;

printf("%d!=%ld\n",N,s);}voidmain(){intk;

for(k=1;k<=5;k++)fact(k);}4.4變量的作用域和存儲類別

4.4.2變量的存儲類別Ex4_9.c演示語言程序設計第四章函數與編譯預處理C4.4.2.2動態(tài)存儲的局部變量運用舉例【例4-10】把上述程序函數fact()中的staticlongs=1;語句,改成longs=1;則程序及其運行結果如下:voidfact(intN){longs=1;/*s是動態(tài)存儲的局部變量*/s=s*N;

printf("%d!=%ld\n",N,s);}voidmain(){intk;

for(k=1;k<=5;k++)fact(k);}4.4變量的作用域和存儲類別

4.4.2變量的存儲類別Ex4_10.c演示語言程序設計第四章函數與編譯預處理C在TurboC系統(tǒng)大型程序開發(fā)中傳統(tǒng)的做法有兩種辦法:一是在主函數文件(即main函數所在的文件)的首部,利用文件包含宏命令#include指定要包含的其它各個程序文件。然后編譯連接主函數文件,生成可執(zhí)行的程序文件(擴展名為.EXE)。二是編寫一個工程文件(其擴展名為.prj),在工程文件中指定組成工程文件的各個程序文件位置與名稱,然后編譯連接這個工程文件,生成可執(zhí)行的程序文件(擴展名為.EXE);具體方法請看下面的例子。4.4變量的作用域和存儲類別

4.4.2變量的存儲類別語言程序設計第四章函數與編譯預處理C【例4-11】把計算AN的函數fpow存放在一個文件(如Ex4_11_1.c)中,供其它函數調用。調用它的程序存放在另一個文件(如Ex4_11_2.c)中,其程序如下:/*Ex4_11_1.c文件內容*/#include"stdio.h"externintN;/*聲明外部變量N*/externfloatS,A;/*聲明外部變量S和A*/voidfpow()/*計算A的N次冪并存放到全局變量S中,即S=AN*/{intk;S=1;for(k=1;k<=N;k++)S=S*A;}4.4變量的作用域和存儲類別

4.4.2變量的存儲類別Ex4_11_2.c演示語言程序設計第四章函數與編譯預處理C4.4變量的作用域和存儲類別

4.4.2變量的存儲類別/*Ex4_11_2.c文件內容*/#include"d:\Clessons\Example\Ex4_11_1.c

"intN;/*定義外部變量N*/floatA;/*定義外部變量A*/voidmain(){externfloatS;/*聲明外部變量S*/printf("InputvaluesintoA&N,please!");

scanf("%f%d",&A,&N); fpow();printf("\nS=%f\n",S); }floatS;/*定義外部變量S*/語言程序設計第四章函數與編譯預處理C由以上兩個程序文件構成的工程文件(Myproj.prj)如下:/*工程文件Myproj.prj內容*/d:\Clessons\Example\Ex4_11_1.cd:\Clessons\Example\Ex4_11_2.c工程文件Myproj.prj的編譯選項中的PrimaryCfile必須指定為main函數所在文件的名稱。如圖4.6所示,指定為D盤下的Ex4_11_2.c即d:\Clessons\Example\Ex4_11_2.c4.4變量的作用域和存儲類別

4.4.2變量的存儲類別

語言程序設計第四章函數與編譯預處理C4.4.2.4靜態(tài)外部變量(文件內部變量)的應用舉例如果把【例4-11】程序改寫成如【例4-12】所示:【例4-12】/*Ex4_12_1.c文件內容*/#include"stdio.h"externintN;/*聲明外部變量N*/externfloatS,A;/*聲明外部變量S和A*/voidfpow()/*計算A的N次冪并存放到全局變量S中,即S=AN*/{intk;S=1;for(k=1;k<=N;k++)S=S*A;}4.4變量的作用域和存儲類別

4.4.2變量的存儲類別Myroj1.prj演示語言程序設計第四章函數與編譯預處理C4.4變量的作用域和存儲類別

4.4.2變量的存儲類別/*Ex4_12_2.c文件內容*/staticintN;/*定義內部變量N*/floatS;/*定義外部變量S*/floatA;/*定義外部變量A*/voidmain(){printf("InputvaluesintoA&N,please!");

scanf("%f%d",&A,&N); fpow();printf("\nS=%f\n",S); }當編譯該程序文件件Myproj1.prj時,系統(tǒng)會給出如下的錯誤:LinkerError:Undefinedsymbol'_N'inmoduleEX4_12_1.C!語言程序設計第四章函數與編譯預處理C4.4變量的作用域和存儲類別4.4.2變量的存儲類別但是如果把Ex4_12_2.c文件內容改成如【例4-13】所示【例4-13】利用文件包含的方法實現全局變量的共享。/*Ex4_13_1.c文件內容*/#include"stdio.h"externintN;/*聲明外部變量N*/externfloatS,A;/*聲明外部變量S和A*/voidfpow()/*計算A的N次冪并存放到全局變量S中,即

S=AN*/{intk;S=1;for(k=1;k<=N;k++)S=S*A;}Ex4_13_2.c演示語言程序設計第四章函數與編譯預處理C4.4變量的作用域和存儲類別

4.4.2變量的存儲類別/*Ex4_13_2.c文件內容*/#include“d:\Clessons\Example\ex4_13_1.c"/*在文件頭部把c:\Ex4_13_1.c包含進來*/staticintN;/*定義外部變量N*/floatS;/*定義外部變量S*/floatA;/*定義外部變量A*/voidmain(){printf("InputvaluesintoA&N,please!");

scanf("%f%d",&A,&N); fpow();printf("\nS=%f\n",S); }語言程序設計第四章函數與編譯預處理C4.4變量的作用域和存儲類別

4.4.2變量的存儲類別在文件Ex4_13_2.c的頭部,把c:\Ex4_13_1.c包含進來,相當于把c:\Ex4_13_1.c文件的內容放在Ex4_13_2.c文件的前面,即把c:\Ex4_13_1.c文件的內容合并到文件Ex4_13_2.c中,這樣,只需對Ex4_13_2.c進行編譯連接,就會生成與Ex4_13_2同名的可執(zhí)行文件Ex4_13_2.exe。既不需要建立工程文件,盡管在Ex4_13_2.c文件中把N定義成靜態(tài)整型變量,也不會像【例4-12】那樣出現錯誤!這是為什么呢?請同學思考。語言程序設計第四章函數與編譯預處理C4.5內部函數與外部函數與指定外部變量的靜態(tài)存儲類別類似,函數也有文件內部函數和可供其它文件調用的外部函數兩種類型。如果在一個源文件中定義的函數只能被本文件中的函數調用,而不能被同一源程序其它文件中的函數調用,這種函數稱為內部函數。定義內部函數的一般形式是:static類型說明符函數名(形參表)例如:staticint

f(int

a,intb)語言程序設計第四章函數與編譯預處理C4.5內部函數與外部函數內部函數也稱為靜態(tài)函數。但此處靜態(tài)static的含義已不是指存儲方式,而是指對函數的調用范圍只局限于定義它的文件中。因此在不同的源文件中定義同名的靜態(tài)函數不會引起混淆。外部函數是指在定義函數時未指定關鍵字static。這樣的函數可供任一C文件中的函數對它調用。定義外部函數的一般形式為:[extern]類型說明符函數名(形參表)例如:externint

f(int

a,intb)如在函數定義中沒有指明extern或static則隱含為extern。如果一個文件中要調用外部函數,必須在主調文件中要對被調用的函數進行顯式的extern說明。語言程序設計第四章函數與編譯預處理C4.5內部函數與外部函數【例4-14】:

1externint

func();2voidmain()3{4func();5}6intmax(inta,intb)7{8return(a>b?a:b);9}prg1.c文件的內容如下:

1externintmax(inta,intb);2staticint

func()3{4intx,y,z;5scanf(“%d%d”,&x,&y);6z=max(x,y);7return(z);8}

prg2.c文件的內容如下:

語言程序設計第四章函數與編譯預處理C4.5內部函數與外部函數由于func()被定義成prg2.c文件的內部函數,該函數只能在prg2.c文件的內部使用,不能被其它文件的函數調用,因此prg1.c文件的第1行對函數func()的聲明是錯誤的。而在prg1.c文件中的max(inta,intb)被定義成外部函數,不僅在定義它的文件prg1.c中可以調用它,而且其它文件中也可以調用它。為了使得prg2.c文件的第6行能夠調用外部函數max,還必須在第6行之前對它進行聲明。prg2.c文件的第1行就是起著這樣的作用。語言程序設計第四章函數與編譯預處理C4.6遞歸函數C語言中的函數既可以像4.3.5節(jié)所述的那樣可以嵌套調用,也可以遞歸調用。函數體內出現調用自身的語句的函數,稱之為遞歸函數。函數的遞歸調用是指一個函數在它的函數體內,直接或間接地調用它自身。其間接遞歸調用如圖所示main(){………Func1(x,y);………}Func1(intx,inty){………

Func2(a+2);………}Func2(intw){………Func1(w,u);………}第1步調用

func1第2步調用

func2第3~N步調用func1第N+1步返回到func1第N+2步返回到main語言程序設計第四章函數與編譯預處理C4.6遞歸函數直接遞歸調用如圖所示main(){………Func(x,y);………}Func(intx,inty){………Func(a+2,y-b);………}第2~N步調用自身第1步調用Funct第N+1步返回main語言程序設計第四章函數與編譯預處理C4.6遞歸函數在遞歸調用中,主調用函數又是被調用函數(如圖4.9中的函數Func1調用Func2調用,函數Func2又調用Func1),即函數Func1既是調用者也是被調用者)。執(zhí)行遞歸函數將反復調用其自身。每調用一次就進入新的一層。例如有函數f如下:intfun(inta){intb=0,c;b++;c=f(b);returnc;}這個函數是一個遞歸函數。但是運行該函數將無休止地調用其自身,這當然是不正確的。為了防止遞歸調用無休止地進行,必須在函數內有終止遞歸調用的手段。常用的辦法是加條件判斷,滿足某種條件后就不再作遞歸調用,然后逐層返回。語言程序設計第四章函數與編譯預處理C4.6遞歸函數【例4-15】用遞歸法計算N!可用下述公式表示

N!=1當N≤1(遞歸結束條件)N*(N-1)!當N>1為了便于編寫程序,我們設計一個變量N的函數F(N)如下:F(N)=1當N≤1(遞歸結束條件)N*F(N-1)當N>1語言程序設計第四章函數與編譯預處理C4.6遞歸函數【例4-15】用遞歸法計算N!可用下述公式表示

按上述公式對應的程序如下:longF(intN){if(N<=1)return1;elsereturnN*F(N-1);}main(){intN;longS;printf("\ninputainteagernumber:\n");scanf("%d",&N);if(N<0){printf("Nmustbenotlessthanzero!\n");exit(1);}S=F(N);printf("%d!=%ld",N,S);getch();}

Ex4_15.c演示語言程序設計第四章函數與編譯預處理C4.6遞歸函數程序說明:程序中給出的函數F(intN)是一個遞歸函數。主函數調用F(N)后即進入函數F(N)執(zhí)行,如果N=0或N=1時都將結束函數的執(zhí)行,否則就遞歸調用函數自身。由于每次遞歸調用的實參為N-1,即把N-1的值賦予形參N,最后當N-1的值為1時再作遞歸調用,形參N的值也為1,將使遞歸終止。然后可逐層退回。設執(zhí)行本程序時輸入為5,即求5!。在主函數中的調用語句即為S=F(N);,進入F函數后,由于N=5,執(zhí)行N*F(N-1),即5*F(5-1)。該語句對F作遞歸調用即F(4)。逐次遞歸展開F(5)=5*F(4)=5*4*F(3)=5*4*3*F(2)=5*4*3*2*F(1)=5*4*3*2*1=120。進行五次遞歸調用后,F函數形參取得的值變?yōu)?,故不再繼續(xù)遞歸調用而開始逐層返回主調函數。F(5)的函數調用計算過程如下圖所示。圖中實線表示調用函數,虛線表示函數結束返回。語言程序設計第四章函數與編譯預處理C4.6遞歸函數voidmain(){…L=factn(5);…}longfactn(5){longL;…L=5*factn(4);return(L);}longfactn(3){longL;…L=3*factn(2);return(L);}longfactn(4){longL;…L=4*factn(3);return(L);}longfactn(1){longL;…return(1);}longfactn(2){longL;…L=2*factn(1);return(L);}函數調用計算過程語言程序設計第四章函數與編譯預處理C4.6遞歸函數一個問題的求解能否設計成遞歸函數,往往取決于對問題本身描述。如果問題的描述具有如下的三個條件,就可用遞歸函數求解。1、問題的描述分成兩部分,其中前一部分描述問題求解的結束條件,后一部分與原始的描述相似或相同;2、后一部分的描述是原始問題的簡化;3、后一部分的描述趨于問題求解的結束。N階乘問題的求解過程(算法),用自然語言可以描述如下:1、N=0或1時,其階乘的值是1,2、N>1時,N的階乘值是N與(N-1)階乘的積,3、因為N的值經過若干次減1的運算,使得N趨于結束條件0或1。顯然,這是對問題的遞歸描述,且滿上述的三個條件,因此可以設計成遞歸函數來求解。語言程序設計第四章函數與編譯預處理C4.6遞歸函數【例4-16】編寫遞歸函數求解斐波那契數列的第N項的值。斐波那契數列的組成規(guī)律是:第一項是0,第二項是1,從第三項起每一項都等于緊鄰的前兩項之和。即數列的組成如下:0,1,1,2,3,5,8……。根據問題的描述,建立如下的數學模型

Fibo(N)=0N=11N=2Fibo(N-1)+Fibo(N-2)N>2語言程序設計第四章函數與編譯預處理C4.6遞歸函數有了以上數學模型,不難寫出如下遞歸函數:longFibo(intN){if(N==1)return0;

if(N==2)return1;returnFibo(N-1)+Fibo(N-2);}或者定義成:longFibo(intN){return(N==1||N==2)?N-1:Fibo(N-1)+Fibo(N-2);}voidmain(){intN;

printf("InputavalueofN,please!\n");

scanf("%d",&N);if(N>0) printf("F(%d)=%ld\n",N,Fibo(N));

elseprintf("Nmustbegreaterthanzero!\n");}Ex4_16.c演示語言程序設計第四章函數與編譯預處理C4.6遞歸函數【例4-17】求兩個整數M和N的最大公約數,按照中國古代的九章算術記載的輾轉相除法描述:令R=M%N;當R=0時,則N就是M,N兩數的最大公約數(N即為所求),終止計算;否則(即R≠0),求N和R的最大公約數(即求N和M%N的最大公約數)。根據上述,可以建立相應的數學模型:Gcd(M,N)=N當M%N=0Gcd(N,M%N)當M%N≠0語言程序設計第四章函數與編譯預處理C4.6遞歸函數根據上述的數學模型,我們既可以用遞歸函數實現求解,也可以用迭代的方法編寫函數求解。下面我們分寫出各自的函數:遞歸法求解函數:longgcd1(longM,longN){return(M%N==0)?N:gcd1(N,M%N);}循環(huán)迭代法求解函數:longgcd2(longM,longN){longR=M%N;while(R){M=N;N=R;R=M%N;}returnN;}語言程序設計第四章函數與編譯預處理C4.6遞歸函數voidmain(){longM,N,t;

printf("InputavalueofN,please!\n");

scanf("%ld%ld",&M,&N);if(M<N){t=M;M=N;N=t;} /*保證M≥N*/printf("GreatestCommondividerof%ld&%ldis%ld!\n",M,N,gcd1(M,N));printf("GreatestCommondividerof%ld&%ldis%ld!\n",M,N,gcd2(M,N));}Ex4_17.c演示語言程序設計第四章函數與編譯預處理C4.6遞歸函數【例4-18】猩猩剝花生問題。動物園管理員第一天給了大猩猩一堆花生。當天這些猩猩撥了三分之二多一只,第二天在剩下的花生中又撥了三分之二多一只。此后每天都撥了前一天剩下的三分之二多一只。到了第10天早上,只剩了一只花生。求動物園管理員給了大猩猩多少只花生?Penut(day)=1day=10Penut(day)=3*(Penut(day+1)+1)0<day<10語言程序設計第四章函數與編譯預處理C4.6遞歸函數根據遞推公式Xn-1=3*(Xn+1)編寫函數如下:longPenut1(intday){longX=1;

for(;day>1;day--)X=3*(X+1);returnX;}longPenut(intday){if(day==10)return1;return(3*(Penut(day+1)+1));}voidmain(){

printf("The

penutnumber=%ld!\n",Penut(1));printf("The

penutnumber=%ld!\n",Penut1(10));}Ex4_18.c演示語言程序設計第四章函數與編譯預處理C4.6遞歸函數由【例4-17】和【例4-18】我們可以看出,一個問題的求解算法往往有多種。究竟采用什么算法更好,這不是本書討論的話題。但是有一點,在既可以采用遞歸也可以采用非遞歸算法的情況下,盡量不用遞歸方法。因為遞歸深度過大,用于存放返回信息的堆棧耗用大量的內存,可能會造成內存不夠用的困境。在遞歸深度不大,采用遞歸使得問題求解的描述更易理解和實現的情況下,采用遞歸是一種有效的方法。語言程序設計第四章函數與編譯預處理C4.6遞歸函數【例4-19】編寫程序求漢諾塔盤子移動的步驟。本題算法分析如下,設A上有n個盤子。移動的過程可分解為三個步驟:第一步借助于C座,把A上的n-1個圓盤移到B上;第二步把A上的最后一個圓盤移到C上;第三步借助于A座,把B上的n-1個圓盤移到C上;其中第一步和第三步是類同的。當n=3時,第一步和第三步又分解為類同的三步,即把n-1個圓盤從一個座移到另一個座上,這里的n=n-1。顯然這是一個遞歸過程,據此可把算法表述成:Move(N,A,B,C)

=Move(N-1,A,C,B)當N>1Move(N-1,B,A,C)當N>1A→C當N=1語言程序設計第四章函數與編譯預處理C4.6遞歸函數根據上述算法模型可編程如下:#include"stdio.h"voidmove(int

n,char

A,char

B,charC)/*將A座上的n個圓盤移到C座的函數*/{

if(n==1)/*當n=1時,直接將A座上的盤子移到C座上*/printf("%c-->%c\n",A,C);

else{/*當n>1時,借助于C座,將A座上的n-1個盤子移到B座上*/move(n-1,A,C,B);

printf("%c-->%c\n",A,C);/*直接將A座上最后一個盤子,移到C座上*/move(n-1,B,A,C);/*借助于A座,將B座上的n-1個盤子移到C座上*/}}Ex4_19.c演示語言程序設計第四章函數與編譯預處理C4.6遞歸函數main(){intn;printf("\ninputnumber:\n");scanf("%d",&n);printf("thesteptomoving%2ddiskes:\n",n);move(n,'A','B','C');}語言程序設計第四章函數與編譯預處理C4.6遞歸函數程序說明:move函數是一個遞歸函數,它有四個形參n,A,B,C。n表示圓盤數,A,B,C分別表示三個座。move函數的功能是把A上的n個圓盤移動到C上。當n==1時,直接把A上的圓盤移至C上,輸出A→C。如n≠1則分為三步:遞歸調用move函數,把n-1個圓盤從A移到B;輸出A→C;遞歸調用move函數,把n-1個圓盤從B移到C。在遞歸調用過程中n=n-1,故n的值逐次遞減,最后n=1時,終止遞歸,逐層返回。語言程序設計第四章函數與編譯預處理C4.7編譯預處理ANSIC規(guī)定可以在C源程序中插入一些傳給編譯程序的預處理命令,由于這些命令不是C語言的組成部分,不能直接進行編譯,通常在編譯之前預先進行處理,所以在C語言源程序中,凡是以“#”號開頭的一律作為編譯預處理命令對待。例如:"#definePAI3.14159",預處理時將程序中所有的PAI置換為指定的字符串3.14159。"#include<math.h>",預處理時就用math.h文件中的實際內容代替該行命令,然后將處理結果和源程序一起進行編譯,這就擴充了程序的設計環(huán)境,提高了編程效率。語言程序設計第四章函數與編譯預處理C4.7編譯預處理編譯預處理是編譯系統(tǒng)的一個組成部分,主要有三類命令:宏定義#define、#undef預處理文件包含#include

條件編譯#if、#ifdef、#ifndef、#else、#elif、#endif預處理命令的特點:(1)命令以#開頭,表示編譯預處理命令行的開始標志;(2)每條命令獨占一行,即每一行只能有一條編譯預處理命令;(3)行末不能有";"號。語言程序設計第四章函數與編譯預處理C4.7編譯預處理1.不帶參數的宏定義用一個指定的名字代表一個字符串,一般形式為:#define宏名字符串功能:編譯預處理時,將程序中所有的該宏名(標識符)用該字符串替換。其中:(1)#define是宏定義命令。(2)宏名是用戶定義的標識符,不得與程序中其他標識符同名。宏名中不能含空格,宏名與字符串之間用空格分隔開。字符串兩側沒有雙引號時,串中不能含空格。(3)如字符串加了雙引號,雙引號將一起參與替換。(4)預處理時用字符串替換宏名的過程稱為宏替換,或宏代換、宏展開。

4.7.1宏定義與宏替換語言程序設計第四章函數與編譯預處理C4.7編譯預處理【例4-20】通過鍵盤輸入半徑的值,求圓的周長、面積和球的表面積、體積。#definePAI3.1415926#defineMESSAGE1"這是一個宏定義示例"#defineMESSAGE2"請輸入半徑"main(){floatS,R,L,V,T;printf(MESSAGE1);printf("%s",MESSAGE2);scanf("%f",&R);L=2*PAI*R;S=PAI*R*R;T=4*S;V=4/3.0*PAI*R*R*R;printf("L=%10.3f\nS=10.3\nT=%10.3f\nV=%10.3f\n",L,S,T,V);}Ex4_20.c演示語言程序設計第四章函數與編譯預處理C4.7編譯預處理上述程序經過預處理(即宏替換)之后,源程序的內容如下:main(){floatS,R,L,V,T;printf("這是一個宏定義示例");printf("%s","請輸入半徑");scanf("%f",&R);L=2*3.1415926*R;S=3.1415926*R*R;T=4*S;V=4/3.0*3.1415926*R*R*R;printf("L=%10.3f\nS=10.3\nT=%10.3f\nV=%10.3f\n",L,S,T,V);}預處理后,宏名PAI、MESSAGE1和MESSAGE2不見了,相應處分別被替換為3.1415926、"這是一個宏定義示例"和"請輸入半徑"。語言程序設計第四章函數與編譯預處理

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論