版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
1高級語言程序設(shè)計(jì)第5章函數(shù)與程序結(jié)構(gòu)
(1-3)2本章主要介紹C/C++語言中與函數(shù)和變量相關(guān)的知識,討論一些程序整體結(jié)構(gòu)有關(guān)的問題。對正確理解C/C++語言/正確書寫C/C++程序都很重要。是學(xué)習(xí)用C/C++程序設(shè)計(jì)時應(yīng)了解的“深層問題”。函數(shù)的定義與使用,函數(shù)原型變量類,作用域與存在期預(yù)處理命令,命名空間,多文件項(xiàng)目開發(fā)3第5章函數(shù)與程序結(jié)構(gòu)5.1函數(shù)的定義與調(diào)用5.1.1對自定義函數(shù)的需求5.1.2函數(shù)定義5.1.3函數(shù)的調(diào)用5.1.4函數(shù)和程序5.1.5局部變量的作用域和生存期5.1.6函數(shù)調(diào)用的參數(shù)傳遞機(jī)制5.2程序的函數(shù)分解 5.3循環(huán)與遞歸5.4外部變量與靜態(tài)局部變量5.5聲明與定義5.6預(yù)處理5.7程序動態(tài)除錯方法(二)4函數(shù)可看作是C/C++語言基本功能的擴(kuò)充。函數(shù)是特定計(jì)算過程的抽象,具有一定通用性,可以按規(guī)定方式對具體數(shù)據(jù)使用。對一個(或一組)具體數(shù)據(jù),函數(shù)執(zhí)行可以計(jì)算出一個結(jié)果,這個結(jié)果可以在后續(xù)計(jì)算中使用。函數(shù)的作用是使人可以把一段計(jì)算抽象出來,封裝(包裝)起來,使之成為程序中的一個獨(dú)立實(shí)體。還有為這樣封裝起的代碼取一個名字,做成一個函數(shù)定義(functiondefinition)。當(dāng)程序中需要做這段計(jì)算時,可以通過一種簡潔的形式要求執(zhí)行這段計(jì)算,這種片段稱為函數(shù)調(diào)用(functioncalling)。5函數(shù)抽象機(jī)制的意義:重復(fù)片段可用唯一的函數(shù)定義和一些形式簡單的函數(shù)調(diào)用取代,使程序更簡短清晰。同樣計(jì)算片段只描述一次,易于修改。函數(shù)定義和使用形成對復(fù)雜程序的分解??瑟?dú)立考慮函數(shù)定義與使用,大大提高工作效率。具有獨(dú)立邏輯意義的函數(shù)可看作高層基本操作,使人可以站在合適的抽象層次上觀察把握程序的意義。6圖5-1函數(shù)的調(diào)用、執(zhí)行與返回t主調(diào)程序被調(diào)函數(shù)函數(shù)調(diào)用點(diǎn):控制權(quán)轉(zhuǎn)移到函數(shù),原程序等待函數(shù)執(zhí)行完畢,控制返回主調(diào)函數(shù),原程序繼續(xù)
認(rèn)識函數(shù)調(diào)用75.1.1對自定義函數(shù)的需求雖然系統(tǒng)提供了大量的標(biāo)準(zhǔn)庫函數(shù)供用戶使用,但是在實(shí)際程序設(shè)計(jì)中,標(biāo)準(zhǔn)庫函數(shù)還不能滿足用戶的需求,存在著許多對特定函數(shù)的需求,這里舉兩個簡單的例子?!纠?-1】哥德巴赫猜想是數(shù)論中的一個著名的難題,它的陳述為“任一大于2的偶數(shù)都可寫成兩個質(zhì)數(shù)之和”。這個難題的嚴(yán)格證明需要高深的數(shù)學(xué)理論,至今還沒有得到徹底解決。請寫程序在小范圍內(nèi)來驗(yàn)證這一猜想:對6到200之間的各個偶數(shù)找出一種質(zhì)數(shù)分解,即找出兩個質(zhì)數(shù),滿足兩者之和等于這個偶數(shù)。8程序主要部分在主體結(jié)構(gòu)上大致上可以寫成這樣:intmain(){intm,n;for(m=6;m<=2000;m+=2)for(n=3;n<=m/2;n+=2){if(n是質(zhì)數(shù)
&&m-n是質(zhì)數(shù)){cout<<m<<"="<<n<<"+"<<m-n<<endl;break;}}return0;}能否把以前寫過的程序片段拿過來使用?9【例5-2】在第4章中有一個簡單猜數(shù)游戲(見“4.4.1編程實(shí)例1:一個簡單猜數(shù)游戲”),整個程序的工作流程是簡潔明了的,但是寫出的程序超過了80行,在閱讀源代碼的時候,讀者可能難以把握整個程序的工作流程。如果把整個程序拆分成幾個不同的部分,就能使主程序變得簡潔明了。而且,在程序中輸入最大值和輸入用戶猜測數(shù)據(jù)時,都分別花了多條語句來處理輸入出錯的情形,而這些語句在功能實(shí)際上是重復(fù)的。10以上例子說明了編程人員有自己定義函數(shù)的需要。也分別說明了需要對自定義函數(shù)需求的兩種場合:1、需要多次重復(fù)使用某個計(jì)算片段,2、把較長的程序進(jìn)行合理拆分,從而使主程序變得簡單易讀,方便把握整個程序的工作流程。為了表述簡單,通常把“編程人員在自己的程序中自己定義的函數(shù)”簡稱為“自定義函數(shù)”,并進(jìn)一步(在不會引起誤解的情形下)簡稱為“函數(shù)”。115.1.2函數(shù)定義要使用自己定義的函數(shù),必須把函數(shù)定義的代碼段包含在整個程序里,這樣的一段代碼稱為一個“函數(shù)定義”。在程序里有了某個函數(shù)的定義后,就可以在程序里任何需要它的地方寫出調(diào)用語句來使用它們。用戶可以在程序中自己定義(define)函數(shù),然后就可以在程序中對函數(shù)進(jìn)行調(diào)用(call)。對函數(shù)的定義和調(diào)用是互相照應(yīng)的:在調(diào)用時需要按照定義時所規(guī)定的語法形式書寫調(diào)用語句,在定義里需要按照調(diào)用時所需的功能進(jìn)行設(shè)計(jì)。定義(define)調(diào)用(call)12聲明參數(shù)的個數(shù)、各參數(shù)的類型和參數(shù)名。參數(shù)名是為了在函數(shù)里使用實(shí)際參數(shù)的值。函數(shù)定義的形式函數(shù)定義的形式: 函數(shù)頭部函數(shù)體返回值類型函數(shù)名(參數(shù)表)描述函數(shù)執(zhí)行結(jié)束時將會返回的值的類型,也可以是void。用標(biāo)識符表示,供以后調(diào)用這個函數(shù)時使用;英語單詞
void:空的,無人的13函數(shù)定義的形式:函數(shù)頭部
函數(shù)體函數(shù)體(body):用{}包括起來的復(fù)合結(jié)構(gòu)。其中定義的變量是本函數(shù)的局部變量。函數(shù)頭部中的參數(shù)也視為局部變量來使用。返回值類型函數(shù)名(參數(shù)表)
{
語句序列;}函數(shù)體里的特殊語句:return(返回)語句。該語句使函數(shù)結(jié)束。返回值類型函數(shù)名(參數(shù)表)
{
語句序列;
return
表達(dá)式;}用法1:語義:先算表達(dá)式,以其值作為函數(shù)返回值。void函數(shù)名(參數(shù)表)
{
語句序列;
return;}用法2:語義:直接從函數(shù)中返回。1415定義函數(shù)時,需要先分析程序中的需求進(jìn)行設(shè)計(jì): 準(zhǔn)備拿幾個什么樣的參數(shù)來進(jìn)行計(jì)算? 計(jì)算完成之后要返回什么樣的值? 然后給函數(shù)起一個合適的名字。按這樣設(shè)計(jì)來寫好函數(shù)頭部之后,就可以在函數(shù)體中編寫相應(yīng)的語句來完成所需的功能。函數(shù)定義的形式:函數(shù)頭部
函數(shù)體返回值類型函數(shù)名(參數(shù)表)
{
語句序列;}16【例5-3】編寫一個函數(shù),用于在給定半徑時計(jì)算圓面積。準(zhǔn)備拿幾個什么樣的參數(shù)來進(jìn)行計(jì)算?→
doubleradius計(jì)算完成之后要返回什么類型的值?→
double然后給函數(shù)起一個合適的名字?!?/p>
scircledoublescircle(doubleradius){//版本1return3.14159265*radius*radius;}doublescircle(doubleradius)
{//版本2doubleerea=3.14159265*radius*radius;returnerea;}函數(shù)頭部中的參數(shù)也視為局部變量來使用函數(shù)體中定義的變量是本函數(shù)的局部變量。17【例5-4】編寫一個函數(shù),用于在給定矩形的長度和寬度時計(jì)算矩形面積。doublesrect(doublea,doubleb)
{//兩個參數(shù)returna*b;}【例5-5】編寫一個函數(shù),在屏幕上輸出20個星號并換行。voidprtStar(){//無參數(shù),無返回值cout<<"********************"<<endl;return; //返回(無返回值)}18doublescircle(doubleradius){…}doublesrect(doublea,doubleb)
{…}voidprtStar(){…}上面三個簡單例子,分別說明了單個參數(shù)/多個參數(shù)/無參數(shù)、有返回值/無返回值的函數(shù)的寫法。當(dāng)然,參數(shù)和返回值的情況可以隨意組合,例如寫出有參數(shù)但無返回值的函數(shù)、或無參數(shù)但是有返回值的函數(shù)。函數(shù)頭部
函數(shù)體195.1.3函數(shù)的調(diào)用已經(jīng)定義好的函數(shù)就可以在程序中進(jìn)行調(diào)用了。在表達(dá)式中使用函數(shù)的形式是:先寫函數(shù)名,然后寫一對圓括號(無參函數(shù)也需要寫),再根據(jù)函數(shù)定義時的函數(shù)頭部中所規(guī)定的參數(shù)類型和參數(shù)個數(shù)寫上單個/多個表達(dá)式(用逗號隔開)。這些表達(dá)式是送給函數(shù)作為計(jì)算對象的,稱為函數(shù)的實(shí)際參數(shù),簡稱實(shí)參。所以,函數(shù)調(diào)用的一般形式為:函數(shù)名(實(shí)際參數(shù))函數(shù)名(實(shí)際參數(shù),實(shí)際參數(shù))函數(shù)名()……20函數(shù)調(diào)用:函數(shù)名(實(shí)際參數(shù)表)多個實(shí)參用逗號分隔。在調(diào)用時,把實(shí)參的值傳遞給形參。函數(shù)體的復(fù)合語句在參數(shù)具有特定實(shí)參值的情況下開始執(zhí)行。函數(shù)定義:返回值類型函數(shù)名(參數(shù)表)
{語句序列;}函數(shù)定義中,參數(shù)表中的參數(shù)稱為形參。21在編寫調(diào)用語句時,應(yīng)該根據(jù)函數(shù)定義時的函數(shù)頭部中的參數(shù)表和返回值進(jìn)行相應(yīng)的書寫:1、參數(shù)表非空,則調(diào)用時必須提供個數(shù)正確、類型合適的實(shí)參。實(shí)參是具體函數(shù)計(jì)算的出發(fā)點(diǎn)。實(shí)參可以是數(shù)值、變量或由數(shù)值和變量構(gòu)成的表達(dá)式。如果函數(shù)的參數(shù)表為空,那么就不需要(而且也不允許)提供參數(shù),只需要寫一對空的圓括號(不可省略)。2、如果提供的實(shí)參類型與形參類型不一致,那么在執(zhí)行時就會發(fā)生類型轉(zhuǎn)換。223、對于具有返回值的函數(shù),在其中執(zhí)行到某一條return表達(dá)式;
語句時,該語句中的表達(dá)式的值被計(jì)算出來并作為該函數(shù)的返回值,這個返回值可供調(diào)用點(diǎn)處后續(xù)使用(也可以閑棄不用)。所以,有返回值的函數(shù)一般出現(xiàn)在表達(dá)式里,用其返回值參與后續(xù)操作(例如給其它變量賦值,或參與后續(xù)計(jì)算,或者直接打印輸出)。無返回值的函數(shù)在執(zhí)行結(jié)束時沒有任何值可供調(diào)用處使用,顯然不能放在表達(dá)式里使用,即不能用于做賦值、計(jì)算或打印之類的操作。23例如,對于scircle和srect函數(shù)(它們分別需要1個和2個參數(shù),都有double類型的返回值),可以寫出如下的調(diào)用語句:doubles;s=scircle(2.4);//直接提供數(shù)值作為參數(shù),返回值用于賦值;s=scircle(2.4+sin(1.57));//含有數(shù)學(xué)函數(shù)的表達(dá)式作為參數(shù),返回值用于賦值;cout<<scircle(1.5+2.4);//算術(shù)表達(dá)式作為參數(shù),返回值用于打印輸出;doubler=1.5;s=scircle(r);//變量作為參數(shù),返回值用于賦值;cout<<scircle(r*2);//算術(shù)表達(dá)式作為參數(shù),返回值用于打印輸出doublelength=3.5,width=4.2;s=srect(3.5,4.2);//直接提供數(shù)值作為參數(shù),返回值用于賦值;s=srect(3*sin(2.),2*cos(5.2));//以表達(dá)式作為參數(shù),返回值用于賦值;s=srect(length,width);//變量作為參數(shù),返回值用于賦值;cout<<srect(length,width);//變量作為參數(shù),返回值用于打印輸出24而對于prtStar函數(shù),由于它不需要參數(shù),所以在調(diào)用時就不需要提供參數(shù),可以這樣調(diào)用(注意,小括號不能省略):prtStar();不需要(不允許)提供參數(shù)給它:prtStar(100);//wrong!沒有返回值可供用于賦值或打?。簊=prtStar();//wrong!cout<<prtStar();//wrong!25【例5-6】把前文的幾個示例函數(shù)寫在同一個程序文件中,并寫一個main函數(shù),在其中調(diào)用這些函數(shù)。#include<iostream>#include<cmath>usingnamespacestd;//doublescircle(doubleradius){//計(jì)算圓面積函數(shù)之版本1//return3.14159265*radius*radius;//}doublescircle(doubleradius){//計(jì)算圓面積函數(shù)之版本2doubleerea=3.14159265*radius*radius;returnerea;}26doublesrect(doublea,doubleb){returna*b;}voidprtStar(){cout<<"********************"<<endl;return;}intmain(){doubles;prtStar();s=scircle(2.4);cout<<"s="<<s<<endl;s=scircle(2.4+sin(1.57));cout<<"s="<<s<<endl;cout<<scircle(1.5+2.4)<<endl;注意:1、每一個函數(shù)都是獨(dú)立的。不能把一個函數(shù)定義寫在另一個函數(shù)定義的內(nèi)部。2、自定義的函數(shù)要寫在main函數(shù)上方;3、函數(shù)之間要寫適當(dāng)?shù)目招?,清晰美觀。(函數(shù)內(nèi)部也可以寫適當(dāng)?shù)目招校?7doubler=1.5;s=scircle(r);cout<<"s="<<s<<endl;cout<<scircle(r*2)<<endl;prtStar();s=srect(3.5,4.2);cout<<"s="<<s<<endl;s=srect(3*sin(2.),2*cos(5.2));cout<<"s="<<s<<endl;doublelength=3.5,width=4.2;s=srect(length,width);cout<<"s="<<s<<endl;cout<<"s="<<srect(length,width)<<endl;prtStar();return0;}這個例題包含了很多知識,下面逐一展開介紹。5.1.4函數(shù)和程序5.1.5局部變量的作用域和生存期5.1.6函數(shù)調(diào)用的參數(shù)傳遞機(jī)制28295.1.4函數(shù)和程序一個完整的程序,必須有且僅有一個名為main的函數(shù)(主函數(shù))。intmain(){
…… return0;}函數(shù)main表示程序的執(zhí)行過程。程序從main的體開始執(zhí)行,直到該復(fù)合結(jié)構(gòu)結(jié)束。其他函數(shù)不經(jīng)調(diào)用就不會執(zhí)行。main在程序啟動時被自動調(diào)用(由運(yùn)行系統(tǒng)調(diào)用)。程序里不允許調(diào)用main。在書寫的形式上,一個程序文件中的每個函數(shù)都是平等的,彼此不能包含,不能把一個函數(shù)的定義寫在另一個函數(shù)內(nèi)部。在習(xí)慣上,人們常把自定義的函數(shù)寫在前面,把main函數(shù)寫在最后。這是為了滿足對函數(shù)的“先定義后使用”規(guī)則。在一個程序中,不允許出現(xiàn)多個自定義函數(shù)具有相同的返回值類型、函數(shù)名和參數(shù)表的情況。30315.1.5局部變量的作用域和生存期一個變量定義,是定義了一個具有特定類型的變量,并給變量命名。同時,一個變量定義還確定了兩個問題:1、在程序中的哪個范圍內(nèi)該變量定義有效。每個變量都有一個確定的作用范圍,稱為該變量的作用域(scope),變量的作用域由變量定義的位置確定。2、變量的實(shí)現(xiàn)基礎(chǔ)是內(nèi)存單元,變量在程序運(yùn)行中建立,并在某個時間撤消。一個變量在程序執(zhí)行中從建立到撤消的存在時期稱為這個變量的生存期(lifetime)或存在期。32作用域和生存期是程序語言中的兩個重要概念,弄清楚它們,許多問題就容易理解了。作用域和生存期有聯(lián)系但又不同,這兩個概念是分別從空間和時間的角度來體現(xiàn)變量的特性。作用域講變量定義的作用范圍,說的是源程序中的一段范圍,可以在代碼中劃清楚,是靜態(tài)概念。生存期則完全是動態(tài)概念,講的是程序執(zhí)行過程中的一段期間。變量在生存期里一直保持著自己的存儲單元,保存于這些存儲單元中的值在被賦新值之前會一直保持不變。33在C/C++程序中的任何復(fù)合語句里的任何位置都可以定義變量。在一個復(fù)合結(jié)構(gòu)里定義的變量可以在該復(fù)合結(jié)構(gòu)的內(nèi)部使用。從作用域的角度來看,在這些語句中所定義的變量只能在相應(yīng)的局部范圍內(nèi)使用。它們的作用域是從該變量定義的語句開始,到復(fù)合語句結(jié)束為止,在這個復(fù)合語句之外該定義無效。因此,這些變量被稱為局部變量(Localvariables)。函數(shù)形參都看作函數(shù)定義的局部變量,其作用域就是這個函數(shù)的函數(shù)體。for語句的小括號中定義的變量,作用域就是整個for語句。34#include<iostream>usingnamespacestd;//doublescircle(doubleradius){//return3.14159265*radius*radius;//}doublescircle(doubleradius){doubleerea=3.14159265*radius*radius;returnerea;}doublesrect(doublea,doubleb){returna*b;}voidprtStar(){cout<<"********************"<<endl;return;}35intmain(){doubles;prtStar();
s=scircle(2.4);cout<<"s="<<s<<endl;doubler=1.5;
s=scircle(r);cout<<"s="<<s<<endl;cout<<scircle(r*2)<<endl;doublelength=3.5,width=4.2;s=srect(length,width);cout<<"s="<<srect(length,width)<<endl;return0;}“函數(shù)調(diào)用的參數(shù)傳遞機(jī)制”見下一節(jié)。36不同作用域內(nèi)的變量名是否允許同名呢?語言對此有如下規(guī)定:(1)同一作用域里不允許定義同名變量:作用域相同的變量的名字不能沖突。否則使用哪個變量的問題就無法確定了。(2)不同作用域容許定義同名變量。也是人們經(jīng)常做的。37【例5-7】寫一個函數(shù)求整數(shù)平方和,然后在main函數(shù)中調(diào)用這個函數(shù)求出給定m的值。intsumsq(intm){intsum=0;for(intn=0;n<m;n++){intk=n*n;sum=sum+k;cout<<"n="<<n<<"sum="<<sum<<endl;}cout<<"m="<<m<<"sum="<<sum<<endl;returnsum;}intmain(){intm;cout<<"inputaninteger:";cin>>m;cout<<"sum="<<sumsq(m)<<endl;return0;}局部變量的作用域,是從該變量定義的語句開始,到復(fù)合語句結(jié)束為止。for語句的小括號中定義的變量的作用域就是整個for語句。函數(shù)形參的作用域是這個函數(shù)的函數(shù)體。38由于在函數(shù)體內(nèi)可以嵌套其它復(fù)合語句,因此產(chǎn)生了作用域的嵌套,在這些嵌套的作用域中是否可以使用同名變量呢?這時,這兩個同名變量雖然同名但作用域不同,所以互不相干,故仍然服從上面第二條規(guī)定:不同作用域中容許定義同名變量。但此時有一個新問題:使用該變量名時到底是在使用哪一個變量?語言對此還有一條規(guī)定:(3)當(dāng)內(nèi)層復(fù)合語句出現(xiàn)同名變量定義時,外層同名定義將被內(nèi)層定義遮蔽。也就是說,在使用該變量名時,實(shí)際上是優(yōu)先使用內(nèi)層定義的變量。39例如,本例的sumsq函數(shù)中,把變量
“k”的名字寫成
“m”也可以:intsumsq(intm){//正確而讓人難懂的版本
intsum=0;for(intn=0;n<m;n++){intm=n*n;//定義了同名變量msum=sum+m;//使用內(nèi)層變量mcout<<"n="<<n<<"sum="<<sum<<endl;}cout<<"m="<<m<<"sum="<<sum<<endl;//使用形參mreturnsum;}這個函數(shù)是正確的,但是寫成這樣顯然很讓人難以讀懂!因此,本書作者覺得對讀者有用的建議是:在程序中盡量避免在嵌套的作用域中出現(xiàn)同名變量!40有時候也需要注意語句的寫法有誤而導(dǎo)致出現(xiàn)嵌套的變量遮蔽現(xiàn)象。例如下面的sumsq函數(shù)就含有功能性錯誤:intsumsq(intm){//含有錯誤的版本
intsum;//定義了函數(shù)內(nèi)的局部變量sumfor(intn=0,sum=0;n<=m;n++){//定義了內(nèi)層局部變量n和sumintk=n*n;sum=sum+k;cout<<"n="<<n<<"sum="<<sum<<endl;}cout<<"m="<<m<<"sum="<<sum<<endl;returnsum;}這個函數(shù)的錯誤原因是:……41上面詳細(xì)介紹了變量的作用域,下面介紹變量的存在期。在復(fù)合語句里定義的局部變量的存在期,就是這個復(fù)合語句的執(zhí)行期間。也就是說,該復(fù)合語句開始執(zhí)行時建立這里面定義的所有變量。它們一直存在到該復(fù)合語句結(jié)束。復(fù)合語句結(jié)束時,內(nèi)部定義的所有變量都撤消。如果執(zhí)行再進(jìn)入這一復(fù)合語句,那么就再次建立這些變量。新建變量與上次執(zhí)行建立的變量毫無關(guān)系,是另一組變量。正是由于在復(fù)合語句里定義的變量被自動建立和撤消的性質(zhì),語言中也把它們稱作自動變量。這幾句話很簡單,但含義很深刻。42以sumsq函數(shù)為例說明:intsumsq(intm){intsum=0;for(intn=0;n<=m;n++){intk=n*n;//for結(jié)構(gòu)每次執(zhí)行循環(huán)體時新建變量ksum=sum+k;cout<<"n="<<n<<"sum="<<sum<<endl;}//for結(jié)構(gòu)每次循環(huán)體執(zhí)行結(jié)束時銷毀變量k//for結(jié)構(gòu)執(zhí)行結(jié)束時銷毀新建變量ncout<<"m="<<m<<"sum="<<sum<<endl;returnsum;}for結(jié)構(gòu)開始執(zhí)行時新建變量n43在編程時應(yīng)當(dāng)注意變量的作用域和存在期,從而理解它們在特定執(zhí)行環(huán)境中的值。如果不能正確理解這一點(diǎn),則所寫的程序可能會含有非常隱蔽的錯誤。假設(shè)有下面程序片段:for(intn=1;n<10;n++){intk;if(n==1)k=5;k=k+n;//循環(huán)執(zhí)行第二次到達(dá)這里時k的值無法確定
cout<<"k="<<k<<endl;}每次循環(huán)體開始執(zhí)行時建立一個名為k的新變量(系統(tǒng)為它分配存儲空間)。第一次循環(huán)時,由于n值為1,k賦值5,執(zhí)行k=k+n;之后,k的值為6,循環(huán)結(jié)束時變量k被撤消。但在第二次及其后的循環(huán)執(zhí)行中條件不成立,相應(yīng)賦值語句不執(zhí)行,這樣到了k=k+n;這一句時,新建立的變量k未經(jīng)過賦值,值不能確定。所以這個程序中含有錯誤。44讀者如果多次運(yùn)行這個程序,通常會發(fā)現(xiàn)所輸出的結(jié)果是一模一樣的,最后輸出的結(jié)果在數(shù)學(xué)上也是正確的(“k=50”),好像這個程序并不含有錯誤似的。這是因?yàn)橄到y(tǒng)在多次新建變量時,偶然地選用了上一次的內(nèi)存空間,上一次的殘留值就被用作初始值來使用。如果系統(tǒng)工作繁忙,內(nèi)存空間的存儲情況頻繁地發(fā)生變化,那么此程序運(yùn)行再次進(jìn)入循環(huán)并新建變量k時,很可能該變量所分配的內(nèi)存空間與上一次并不相同,就會得到一個無法預(yù)料的值作為初始值(用于執(zhí)行k=k+n;語句)。所得的計(jì)算結(jié)果就很可能完全無法預(yù)料了?!虼?,上面的程序片段中確實(shí)含有非常隱蔽的錯誤。定義在復(fù)合結(jié)構(gòu)中的變量,其作用域是局部的,所以稱為局部變量。它們在程序執(zhí)行期間是自動建立和銷毀的,所以從生存期的角度來說,稱為自動變量。45465.1.6函數(shù)調(diào)用的參數(shù)傳遞機(jī)制在調(diào)用函數(shù)時,實(shí)參與形參具體是什么樣的關(guān)系?當(dāng)實(shí)參是變量時,在函數(shù)體內(nèi)改變之后的形參值是否會返回給實(shí)參呢?這就需要我們理解C和C++語言中的參數(shù)機(jī)制。doublesrect(doublea,doubleb){returna*b;}intmain(){doublelength=3.5,width=4.2,s;s=srect(length,width);cout<<"s="<<s<<endl;return0;}47函數(shù)調(diào)用與參數(shù)值的傳遞
nm把實(shí)參值復(fù)制給形參函數(shù)func的內(nèi)部abC和C++語言中的函數(shù)的基本參數(shù)機(jī)制是值參數(shù):函數(shù)調(diào)用時先計(jì)算實(shí)參表達(dá)式的值,把值復(fù)制給對應(yīng)形參,而后執(zhí)行函數(shù)體。函數(shù)內(nèi)對形參的操作與實(shí)參無關(guān)。函數(shù)內(nèi)對形參的賦值與實(shí)參無關(guān)。函數(shù)定義:intfunc(inta,intb){......}函數(shù)調(diào)用:cout<<func(m,n);函數(shù)func的調(diào)用環(huán)境返回值return表達(dá)式;48【例5-8】:#include<iostream>usingnamespacestd;voidswap(inta,intb){ intk=a;a=b;b=k; cout<<"swap:a="<<a<<",b="<<b<<endl;;return;}intmain(){ intm=10,n=25; cout<<"before:m="<<m<<",n="<<n<<endl;
swap(m,n); cout<<"after:m="<<m<<",n="<<n<<endl;}輸出結(jié)果(a和b的值在調(diào)用前后并未改變):before:m=10,n=25swap:a=25,b=10after:m=10,n=25請仔細(xì)體會函數(shù)的參數(shù)值傳遞機(jī)制!49C++中的引用型參數(shù)C++語言中添加了名為“引用(reference)”的新特性,使用這種方式在函數(shù)之間傳遞參數(shù),調(diào)用時在函數(shù)中直接對實(shí)參進(jìn)行操作,可改變調(diào)用處的變量的值。書寫形式上是在形參名稱前面加上
&
字符。nm調(diào)用時直接對實(shí)參進(jìn)行操作函數(shù)swapref的內(nèi)部&a&b函數(shù)定義:voidswapref(int&a,int&b){......}函數(shù)調(diào)用:swapref(m,n);函數(shù)swapref的調(diào)用環(huán)境50舉例:voidswapref(int&a,int&b){//形參a,b都是引用
intk=a;a=b;b=k;
cout<<"swapedinside:a="<<a<<"b="<<b<<
endl;return;}intmain(){ intm=10,n=25;cout<<"beforeswapref:m="<<m<<"n="<<n<<endl;
swapref(m,n);cout<<"afterswapref:m="<<m<<"n="<<n<<endl;}輸出結(jié)果(變量m和n的值在調(diào)用前后發(fā)生改變):beforeswapref:m=10,n=25swapedinside:a=25,b=10afterswapref:m=25,n=1051對比swap和swapref兩個函數(shù),并對比兩個main函數(shù),可以看到差別僅僅是:在swapref的函數(shù)頭部的參數(shù)列表中,在形參前面加上“&”字符。這樣微小的書寫差別產(chǎn)生了巨大的語義差別:調(diào)用函數(shù)時不再是使用傳統(tǒng)的值傳遞機(jī)制了!在調(diào)用時不再是把實(shí)參的值復(fù)制給形參,而是把形參作為實(shí)參的別名,直接對實(shí)參進(jìn)行操作。voidswapref(int&a,int&b){…}voidswap(inta,intb){…}52注意:調(diào)用含有引用型參數(shù)的函數(shù)時,對引用型參數(shù)必須提供一個變量作為實(shí)參,而不能以常量或含有運(yùn)算符的表達(dá)式作為實(shí)參。正確:swapref(m,n);//m和n都是變量錯誤:swapref(10,25);//用常量做實(shí)參swapref(m,m+12);//用含有運(yùn)算符的表達(dá)式做實(shí)參voidswapref(int&a,int&b){…}小練習(xí):下面程序的運(yùn)行結(jié)果如何?#include<iostream>usingnamespacestd;voidmyswap(int&a,intb){//a是引用型參數(shù),b是值參數(shù) intk=a;a=b;b=k;}intmain(){ intm=10,n=25;
myswap(m,n);}5354最后簡單地介紹常參數(shù)。與常變量類似,函數(shù)也可以有常參數(shù)。常參數(shù)同樣由實(shí)參提供初值,但在函數(shù)體里不允許對它們重新賦值。常參數(shù)的定義形式是在參數(shù)的類型描述前加const關(guān)鍵字:intfunc1(constinta,intb){......}//常值參數(shù)intfunc2(constint&a,intb){......}//常引用參數(shù)參數(shù)a被指定為常參數(shù),因此在函數(shù)體內(nèi)不允許對它重新賦值。如果在函數(shù)體寫了任何對a重新賦值的語句,則編譯時就會報(bào)錯。小結(jié)(5.1函數(shù)的定義與調(diào)用)對自定義函數(shù)的需求:1、重復(fù)使用的代碼片段;2、長程序拆分。函數(shù)定義:返回值類型
函數(shù)名(參數(shù)表)
{...}
函數(shù)頭部
函數(shù)體編寫函數(shù)時,先根據(jù)需求設(shè)計(jì)好參數(shù)表和返回值類型,并給函數(shù)命名。然后再寫函數(shù)體。函數(shù)調(diào)用:函數(shù)名(實(shí)參列表)返回值類型為void的函數(shù)沒有返回值可用。程序中必須有而且只能有一個main函數(shù)。程序總是從main函數(shù)開始執(zhí)行。其它函數(shù)未經(jīng)調(diào)用就不會執(zhí)行。不能把一個函數(shù)的定義寫在另一個函數(shù)內(nèi)部。通常把自定義的函數(shù)寫在前面,把main函數(shù)寫在最后。55續(xù)變量的作用域由變量定義的位置確定。局部變量作用域是從該變量定義的語句開始,到復(fù)合語句結(jié)束為止。函數(shù)的形參可當(dāng)作局部變量使用,性質(zhì)與局部變量相同。當(dāng)內(nèi)層復(fù)合語句出現(xiàn)同名變量定義時,外層同名定義將被內(nèi)層定義遮蔽。局部變量的存在期,就是所在的復(fù)合語句的執(zhí)行期間。自動建立和銷毀,也稱為自動變量。值參數(shù)機(jī)制:函數(shù)調(diào)用時先計(jì)算實(shí)參表達(dá)式的值,把值復(fù)制給對應(yīng)形參,而后執(zhí)行函數(shù)體。C++
的引用參數(shù):函數(shù)調(diào)用時在函數(shù)中直接對實(shí)參進(jìn)行操作,可改變調(diào)用處的變量的值。必須提供變量作為實(shí)參。函數(shù)也可以有常參數(shù)。在前面加const關(guān)鍵詞。56續(xù)57第5章函數(shù)與程序結(jié)構(gòu)5.1函數(shù)的定義與調(diào)用5.2程序的函數(shù)分解 5.3循環(huán)與遞歸5.4外部變量與靜態(tài)局部變量5.5聲明與定義5.6預(yù)處理5.7程序動態(tài)除錯方法(二)585.2程序的函數(shù)分解5.2.1程序的函數(shù)分解什么樣的程序片段應(yīng)該定義為函數(shù):重復(fù)出現(xiàn)的相同/相似計(jì)算片段,可設(shè)法抽取共同性的東西,定義為函數(shù)。長計(jì)算過程中有邏輯獨(dú)立性的片段,即使出現(xiàn)一次也可定義為函數(shù),以分解復(fù)雜性。經(jīng)驗(yàn)原則:可以定義為函數(shù)的東西,就應(yīng)該定義為函數(shù);一個函數(shù)一般不超過一頁。往往存在很多可行的分解,尋找合理有效的分解是需要學(xué)習(xí)的東西。59函數(shù)外部關(guān)心的是函數(shù)如何使用:—函數(shù)實(shí)現(xiàn)什么功能—函數(shù)的名字是什么—函數(shù)有幾個參數(shù),類型是什么—函數(shù)返回什么值…
…
函數(shù)內(nèi)部關(guān)心的是函數(shù)應(yīng)當(dāng)如何實(shí)現(xiàn)—采用什么計(jì)算方法—采用什么程序結(jié)構(gòu)—怎樣得到計(jì)算結(jié)果…
…
函數(shù)頭部的說明
5.2.2函數(shù)封裝和兩種視角函數(shù)定義(封裝)把函數(shù)內(nèi)外隔成兩個世界。不同世界形成了對函數(shù)的兩種觀點(diǎn)。函數(shù)頭部規(guī)定了兩個世界的交流方式。60函數(shù)是獨(dú)立的邏輯實(shí)體。定義后可以調(diào)用執(zhí)行。由此形成對函數(shù)的兩種觀察方式:1)從函數(shù)外(以函數(shù)使用者的角度)看函數(shù);2)在函數(shù)內(nèi)(以函數(shù)定義者的角度)看函數(shù)。計(jì)劃函數(shù)時,要同時從兩個觀點(diǎn)看:需要什么函數(shù)/參數(shù)/返回值?分析確定函數(shù)頭部,定好公共規(guī)范。編寫函數(shù)定義時應(yīng)站在內(nèi)部觀點(diǎn)思考/解決問題;調(diào)用函數(shù)時應(yīng)站在外部立場上思考/解決問題。功能描述清楚,接口定義好以后,函數(shù)定義和使用可由不同人做。要求雙方遵循共同規(guī)范,對函數(shù)功能有一致理解。自己寫函數(shù)時也要保證兩種觀點(diǎn)的一致性。615.2.3自定義函數(shù)示例舉例讓讀者體會如何編寫自定義函數(shù)和如何進(jìn)行函數(shù)分解。希望通過這些例題說明在使用函數(shù)進(jìn)行編程的一般技巧。讀者也可以從中體會到函數(shù)分解的一般性經(jīng)驗(yàn)?!纠?-10】以迭代公式求x的立方根【例5-11】寫函數(shù)求sinx的近似值【例5-12】寫函數(shù)判斷變量year的值是否閏年【例5-13】寫謂詞函數(shù)判斷質(zhì)數(shù)【例5-14】用函數(shù)驗(yàn)證歌德巴赫猜想【例5-15】歌德巴赫(Goldbach)猜想+62【例5-10】求x立方根的迭代公式是,寫一個函數(shù),從鍵盤上輸入x值,然后利用這個公式求x的立方根的近似值,要求達(dá)到精度。解:把前文例題求出立方根的程序修改為自定義函數(shù)。根據(jù)“cubicroot”把函數(shù)命名為cbrt,函數(shù)參數(shù)是一個double類型的數(shù)據(jù),函數(shù)返回值即為求出的立方根。doublecbrt(doublex)
{ if(x==0)
return0;//計(jì)算出0的立方根為0作為函數(shù)返回值
doublex1,x2=x; do{ x1=x2; x2=(2.0*x1+x/(x1*x1))/3.0; //cout<<x2<<endl; }while(fabs((x2-x1)/x1)>=1E-6);
returnx2; //計(jì)算得到滿足精度的項(xiàng)作為函數(shù)返回值}63寫一個main函數(shù)調(diào)用cbrt函數(shù)進(jìn)行測試:intmain(){//測試cbrtdoublex;cout<<"Inputxtotestcbrt(Ctrl-ztoend)"<<endl;while((cin>>x)) cout<<"cbrt="<<cbrt(x)<<endl;cout<<"testfinished."<<endl;
return0;}運(yùn)行時選擇合理的測試數(shù)據(jù):0值/非0值,±1,±8,±27,±1000,±1000000前文使用單個main函數(shù)的程序:intmain(){doublex,x1,x2;cout<<"Pleaseinputx:";cin>>x;if(x==0){cout<<"cubicrootof"<<x<<"is:"<<x2;return0;//程序結(jié)束,返回值為0}
x2=x;do{x1=x2;x2=(2.0*x1+x/(x1*x1))/3.0;cout<<x2<<endl;}while(fabs((x2-x1)/x1)>=1E-6);cout<<"cubicrootofxis:"<<x2<<endl;return0;}64這里進(jìn)行函數(shù)分解后寫出的程序:doublecbrt(doublex)
{ if(x==0)
return0;//0的立方根作為函數(shù)返回值
doublex1,x2=x; do{ x1=x2; x2=(2.0*x1+x/(x1*x1))/3.0; //cout<<x2<<endl; }while(fabs((x2-x1)/x1)>=1E-6);
returnx2; //函數(shù)返回值}intmain(){//測試cbrtdoublex;cout<<"Inputxtotestcbrt(Ctrl-ztoend)"<<endl;while((cin>>x)) cout<<"cbrt="<<cbrt(x)<<endl;cout<<"testfinished."<<endl;return0;}對比兩種寫法的差異:把相關(guān)的計(jì)算代碼封裝起來構(gòu)成函數(shù),并合理地設(shè)置形參和局部變量;函數(shù)中用形參接收用于計(jì)算的參數(shù),用return返回計(jì)算結(jié)果;函數(shù)中不要輸出信息,而是由調(diào)用它的主函數(shù)輸出信息。65【例5-11】寫一個函數(shù)利用公式求出sinx的近似值(要求累加項(xiàng)的值小于10?6),并與標(biāo)準(zhǔn)庫中的sin函數(shù)的計(jì)算結(jié)果進(jìn)行比較。把前文例題的源代碼修改為自定義函數(shù)dsin:doubledsin(doublex)
{ //x=fmod(x,2*3.1415926); //此句有何作用?
doublesum=0.0,t=x; intn=0; while(t>=1E-7||t<=-1E-7){ sum=sum+t; n=n+1; t=-t*x*x/(2*n)/(2*n+1);
//cout<<"n="<<n<<"t="<<t<<"sum="<<sum<<endl; }
returnsum;}66前文例題給出的源代碼:intmain(){ doublex,sum,t; cout<<"Pleaseinputx:"; cin>>x; x=fmod(x,2*3.1415927); sum=0.0; t=x; intn=0; while(t>=1E-6||t<=-1E-6){ sum=sum+t; n=n+1; t=-t*x*x/(2*n)/(2*n+1); cout<<"n="<<n<<"t="<<t<<"sum="<<sum<<endl; } cout<<"mysin"<<x<<"is:"<<sum<<endl; cout<<"standardsin"<<x<<"is:"<<sin(x); return0;}把這一部分代碼封裝成函數(shù)定義,用于計(jì)算的變量x做成形參,最后返回計(jì)算結(jié)果(變量sum)。doubledsin(doublex)
{//x=fmod(x,2*3.1415926);doublesum=0.0,t=x;intn=0;while(t>=1E-7||t<=-1E-7){sum=sum+t;n=n+1;t=-t*x*x/(2*n)/(2*n+1);
//cout<<"n="<<n<<"t="<<t<<"sum="<<sum<<endl;}returnsum;}67寫一個main函數(shù)調(diào)用dsin函數(shù)進(jìn)行測試:intmain(){//測試dsin函數(shù)。通過循環(huán)提供參數(shù)自動測試 doublex; cout<<"testdsin:\nx\tdsin(x)\tsin(x)\n";
for(inti=-100;i<=1000;i+=10){ //cout<<"Pleaseinputx:"; //cin>>x; x=i; cout<<x<<'\t'<<dsin(x)<<'\t'<<sin(x)<<'\t'; cout<<dsin(x)-sin(x)<<endl; } return0;}要查看此程序運(yùn)行結(jié)果!是否滿足要求?如果出現(xiàn)偏差該如何解決?總結(jié)上面兩個例題:在編程實(shí)踐中,在按照要求寫一個函數(shù)來實(shí)現(xiàn)某項(xiàng)功能時,通常都需要在寫完該函數(shù)之后,再寫一個main函數(shù),測試所寫的函數(shù)是否能正常工作。測試時可以循環(huán)地用手工輸入一批數(shù)據(jù)作為參數(shù),或者用一個循環(huán)自動地提供一批參數(shù)。6869【例5-12】寫一個函數(shù)判斷變量year的值是否表示一個閏年的年份,然后在main函數(shù)中調(diào)用這個函數(shù),打印輸出1900-2100中的閏年。#include<iostream>usingnamespacestd;intisleapyear(intyear){
return((year%4==0&&year%100!=0)||year%400==0);}intmain(){ for(intyear=1900;year<=2100;year++) if(isleapyear(year)) cout<<year<<""; return0;}注意,這是兩個不同的變量70【例5-13】寫一個謂詞函數(shù),判斷一個整數(shù)(參數(shù))是否為質(zhì)數(shù)。然后在main函數(shù)中判斷并輸出-10--999中的質(zhì)數(shù)。函數(shù)命名為isprime,注意把n<=1時判斷為非質(zhì)數(shù)。把原有例題源程序改寫為謂詞函數(shù)isprime:intisprime(intn){ //版本1 if(n<=1)//n<=1時判斷為非質(zhì)數(shù) return0; //n>1時繼續(xù)分析判斷 intk; for(intk=2;k*k<=n;k++) if(n%k==0) //發(fā)現(xiàn)一個因數(shù)就退出循環(huán)
break; //根據(jù)循環(huán)退出或結(jié)束的情形來判斷
return(k*k<=n&&n%k==0)?0:1;}71函數(shù)可以更巧妙地使用return語句而寫得更簡潔:intisprime(intn){ //版本2 if(n<=1)return0; //非質(zhì)數(shù) for(intk=2;k*k<=n;k++) if(n%k==0)//發(fā)現(xiàn)一個因數(shù)就足以判斷不是質(zhì)數(shù)
return0;
return1;//上面循環(huán)中沒有發(fā)現(xiàn)因數(shù),所以判斷是質(zhì)數(shù)}72有了上面的isprime函數(shù),可以寫出main函數(shù)如下:intmain(){ //輸出-10--999之間的所有質(zhì)數(shù)
for(intn=-10;n<999;n++) if(isprime(n)) cout<<n<<""; return0;}可以把isprime函數(shù)和main函數(shù)拼裝成一個完整的程序。(要加上必要的其它內(nèi)容;isprime函數(shù)的兩個版本只能任選其一)注意在函數(shù)中對n<=1進(jìn)行了特殊處理:
if(n<=1)return0;由此可見,在寫一個程序(或函數(shù))之前,首先應(yīng)該仔細(xì)分析需要考慮的情況。完成之后還應(yīng)該仔細(xì)檢查,看看是否有什么遺漏。如果事先分析周全,應(yīng)該能看到這些問題。73從isleapyear和isprime這兩個函數(shù)可以注意到,謂詞函數(shù)通常只負(fù)責(zé)進(jìn)行某種判斷并返回判斷結(jié)果,不進(jìn)行信息輸出。而由調(diào)用這類函數(shù)的程序根據(jù)自身需求進(jìn)行信息輸出。這是一種合理的函數(shù)功能分解方式。intisprime(intn){ //版本2 if(n<=1)return0; //非質(zhì)數(shù) for(intk=2;k*k<=n;k++) if(n%k==0)//發(fā)現(xiàn)一個因數(shù)就足以判斷不是質(zhì)數(shù)
return0;
return1;//上面循環(huán)中沒有發(fā)現(xiàn)因數(shù),所以判斷是質(zhì)數(shù)}intisleapyear(intyear){
return((year%4==0&&year%100!=0)||year%400==0);}74【例5-14】回到例5-1,使用已有的isprime函數(shù)在小范圍內(nèi)驗(yàn)證歌德巴赫猜想:對6到200之間的各偶數(shù)找出一種質(zhì)數(shù)分解,即找出兩個質(zhì)數(shù),使它們的和等于這個偶數(shù)。把isprime函數(shù)插入已寫出的程序主體結(jié)構(gòu):intmain(){ intm,n; for(m=6;m<=200;m+=2) for(n=3;n<=m/2;n+=2){
//if(n是質(zhì)數(shù)
&&m-n是質(zhì)數(shù))
if(isprime(n)&&isprime(m-n)){ cout<<m<<"="<<n<<"+"<<m-n<<endl; break; } } return0;}請讀者把isprime函數(shù)和這個main函數(shù)拼裝成一個完整的程序文件。75【例5-15】歌德巴赫(Goldbach)猜想“任一大于2的偶數(shù)都可寫成兩個質(zhì)數(shù)之和”對于在計(jì)算機(jī)上已驗(yàn)證過的偶數(shù)都是成立的,而且很多偶數(shù)有多種分解方式,那么,對給定的偶數(shù)是否能找到一種分解方式,使分解得的兩個質(zhì)數(shù)之差小于該偶數(shù)的1/4?請寫一個函數(shù)對給定的偶數(shù)尋找兩個質(zhì)數(shù)之差最小的分解方式,并把這兩個質(zhì)數(shù)返回到主調(diào)函數(shù);而且如果兩個質(zhì)數(shù)之差小于該偶數(shù)的1/4,則函數(shù)的返回值不為0,表示成功;如果兩個質(zhì)數(shù)之差大于該偶數(shù)的1/4,則函數(shù)的返回值為0,表示失敗。然后再寫一個主函數(shù),對6~200中的偶數(shù)進(jìn)行驗(yàn)證是否可以這樣分解。76尋找把偶數(shù)分解成兩個質(zhì)數(shù)之和,可以借鑒上一例題。尋找“兩個質(zhì)數(shù)之差最小”的分解方式,只要從該偶數(shù)的1/2(準(zhǔn)確地說是從“等于或大于該偶數(shù)的1/2的第一個奇數(shù)”)開始往上搜索即可,所找到的第一種分解方式就滿足“兩個質(zhì)數(shù)之差最小”。題目中要求把給定的偶數(shù)進(jìn)行分解成質(zhì)數(shù)并返回“兩個質(zhì)數(shù)之差小于該偶數(shù)的1/4”的判斷結(jié)果,對此函數(shù)可以有多種設(shè)計(jì)方案。先來看一種最直觀的設(shè)計(jì)方案:77(1)題目要求把給定的偶數(shù)分解成兩個質(zhì)數(shù),可以把分解得的兩個質(zhì)數(shù)返回到主調(diào)函數(shù),因此待編寫的函數(shù)的形參設(shè)定為三個整數(shù),用于表示待分解的偶數(shù)和分解而得的兩個質(zhì)數(shù),而且分解而得的兩個質(zhì)數(shù)需要返回到主調(diào)函數(shù)中,所以這兩個形參需要作為
引用型參數(shù)。(2)根據(jù)題目要求,顯然要求函數(shù)的返回值是整數(shù)。(3)按照見名識義的原則,把函數(shù)命名為“goldbach”。按照這一設(shè)計(jì)方案,寫出函數(shù)頭部如下:intgoldbach(intn,int&k1,int&k2)用于分解的偶數(shù)兩個引用參數(shù),表示進(jìn)行分解后得到的兩個質(zhì)數(shù)78intgoldbach(intn,int&k1,int&k2){if(n%2==1||n<6)//奇數(shù)或小于6的偶數(shù)不能分解
return0;
k1=(n/2)%2?n/2:n/2+1;//等于或大于該偶數(shù)的1/2的第一個奇數(shù)
for(k2=n-k1;k1<=n;k1+=2,k2=n-k1)if(isprime(k1)&&isprime(k2))
return(k1-k2<n/4?1:0); return0;}intmain(){intm,m1,m2,found;for(m=6;m<=200;m+=2){
found=goldbach(m,m1,m2);cout<<m<<"="<<m1<<"+"<<m2<<"\t";cout<<(found?"Yes":"NO")<<"\t"<<m1-m2<<endl;}return0;}用函數(shù)的返回值表示函數(shù)的工作狀態(tài)79另一些可行的其它函數(shù)設(shè)計(jì)方案并編寫程序——當(dāng)然每一種函數(shù)設(shè)計(jì)方案都需要相應(yīng)地考慮調(diào)用時如何處理:(1)把形參1也寫成引用:
intgoldbach1(int&n,int&k1,int&k2);
這種寫法也可以。但在調(diào)用時必須對n提供一個變量作為實(shí)參,而不能直接寫某個常數(shù):
goldbach(1000,m1,m2);//okgoldbach1(1000,m1,m2);//wrong!(2)只用&k1就夠了,不用&k2:
intgoldbach2(intn,int&k1);
這種寫法也可以。但在主函數(shù)中調(diào)用后需要另行計(jì)算出第2個質(zhì)數(shù),如下所示:
found=goldbach2(m,m1);cout<<m<<"="<<m1<<"+"<<m-m1<<"\t";80(3)不用引用,直接拿函數(shù)返回值表示一個質(zhì)數(shù)(值為0時表示分解不成功):
intgoldbach3(intn);
這種寫法也可以。但在調(diào)用時需要另想辦法處理這兩個質(zhì)數(shù),例如寫成這樣:
m1=goldbach3(m);cout<<m<<"="<<m1<<"+"<<m-m1<<"\t";cout<<(m1?"Yes":"NO")<<endl;81由上例可知,同對一個問題,通??梢栽O(shè)計(jì)出多種函數(shù)設(shè)計(jì)方案來編程解答,每一種函數(shù)所需的參數(shù)、參數(shù)所代表的數(shù)據(jù)含義可以各不相同,相應(yīng)地也需要在調(diào)用時加以考慮如何提供參數(shù)和使用函數(shù)返回值(或可以返回?cái)?shù)據(jù)的參數(shù))。因此,讀者應(yīng)該理解,各種成熟的函數(shù),常常都是編程人員在面對多種可能的函數(shù)設(shè)計(jì)方案進(jìn)行綜合分析之后有所取舍而編寫出來的。我們在面對一個編程問題時,常常能構(gòu)思出多種函數(shù)設(shè)計(jì)方案,這時需要學(xué)會取舍,選出其中一種自己覺得最為合理的方案來編程實(shí)現(xiàn)之。82小結(jié)(5.2程序的函數(shù)分解)程序要進(jìn)行合理的函數(shù)分解。(1)重復(fù)出現(xiàn)的相同/相似計(jì)算片段;(2)長計(jì)算過程中有邏輯獨(dú)立性的片段。函數(shù)定義(封裝)把函數(shù)內(nèi)外隔成兩個世界。定義函數(shù)和使用函數(shù)時要按不同的視角來看待函數(shù)。把原有的程序片段改寫成函數(shù):把前面的代碼中得到的數(shù)據(jù)改為函數(shù)的形參;把計(jì)算結(jié)果作為函數(shù)的返回值。編寫一個函數(shù)之后,通常需要自行編寫一個main函數(shù),對該函數(shù)進(jìn)行測試。測試數(shù)據(jù)可能是手工輸入,也可以是通過循環(huán)變量來提供。通常不在函數(shù)中向屏幕打印輸出。以便讓調(diào)用處進(jìn)行后續(xù)計(jì)算或屏幕輸出。要根據(jù)調(diào)用時的需求來處理函數(shù)定義中的參數(shù)為值參數(shù)或引用參數(shù)。每個問題都可能有多種函數(shù)分解方案,要選擇最合適的方案進(jìn)行處理。講解了“5.2程序的函數(shù)分解”之后,最好提前講解“5.7程序動態(tài)除錯方法(二)”,然后再講其它節(jié)。8384第5章函數(shù)與程序結(jié)構(gòu)5.1函數(shù)的定義與調(diào)用5.2程序的函數(shù)分解
5.3循環(huán)與遞歸5.4外部變量與靜態(tài)局部變量5.5聲明與定義5.6預(yù)處理5.7程序動態(tài)除錯方法(二)855.3循環(huán)與遞歸程序中有循環(huán)就可能導(dǎo)致很長的計(jì)算。沒有循環(huán)結(jié)構(gòu)也能描述這類計(jì)算。C/C++語言允許遞歸,可在函數(shù)內(nèi)調(diào)用自身,程序常常更簡單清晰。865.3.1階乘和乘冪(循環(huán),遞歸)【例5-16】定義計(jì)算整數(shù)階乘的函數(shù):n!=1×2×…×(n-1)×n乘的次數(shù)依賴于n,定義時未知,每次用可能不同。類型特征可定為:
intfact(int)階乘值增長極快(數(shù)學(xué)),更合適的類型特征:
longlong
fact(int)87可以用循環(huán)定義:intfactloop(intn){ //循環(huán)方式求階乘 intfac=1; for(inti=1;i<=n;++i) fac*=i; returnfac;}程序的典型情況:計(jì)算次數(shù)依賴于某些參數(shù)的值。88省略號不科學(xué)。嚴(yán)格定義需用遞歸形式。遞歸定義的形式也提出了一種計(jì)算方法:如果語言允許遞歸定義函數(shù),就可直接翻譯為程序。遞歸定義:在函數(shù)定義內(nèi)部調(diào)用被定義函數(shù)本身。階乘函數(shù)的遞歸寫法:intfact(intn){//遞歸方式求階乘 returnn==0?1:n*fact(n-1);}比循環(huán)函數(shù)簡潔得多。階乘:n!=1×2×…×(n-1)×n89考慮負(fù)參數(shù)值處理??筛臑椋簄<=1?1:..調(diào)用fact(3)fact(3)3*fact(2)fact(2)2*fact(1)fact(1)1*fact(0)fact(0)返回值6返回值2返回值1返回值1fact(3)的計(jì)算過程遞歸定義導(dǎo)致的計(jì)算過程fact實(shí)現(xiàn)的計(jì)算過程很不簡單。計(jì)算中fact被遞歸調(diào)用的次數(shù)由實(shí)參確定。參數(shù)不同,則遞歸調(diào)用次數(shù)(步數(shù))不同。90【例5-17】遞歸求冪。寫函數(shù)doubledexp(intn)
求e(自然對數(shù)的底)的n次冪。注意到參數(shù)n為負(fù)時乘冪也有定義。先寫一個輔助函數(shù),再寫一個所需的函數(shù)。doubledexp1(intn){ //只處理n>=0 returnn==0?1:2.71828*dexp1(n-1);}doubledexp(intn){ //分為n>=0和n<0處理
returnn>=0?dexp1(n):1/dexp1(-n);}這個問題也可以用循環(huán)寫出(略)計(jì)算乘冪的函數(shù)也可以用循環(huán)的方式寫出:doubledexploop(intn){//循環(huán)方式求e的n次冪
doublex=2.71828183,d=1;
if(n<0){
n=-n;
x=1/x;
}
for(inti=0;i<n,++i)
d*=x;
returnd;}循環(huán)與遞歸的關(guān)系:有些循環(huán)程序也可以用遞歸的形式寫;有些遞歸程序也可以通過循環(huán)寫出。當(dāng)然,遞歸算法必須定義為函數(shù)
(不能在main函數(shù)中寫遞歸)。9192從上面兩個例子可見,遞歸的函數(shù)定義需要使用條件表達(dá)式或條件語句。遞歸函數(shù)必須區(qū)分兩種情況:(1)直接給出結(jié)果的情況。是遞歸的基礎(chǔ)(2)需要遞歸處理的情況。其中把對較復(fù)雜情況的計(jì)算歸結(jié)為對更簡單情況的計(jì)算。intfact(intn){returnn==0?1:n*fact(n-1);}doubledexp1(intn){ //只處理n>=0returnn==0?1:2.71828*dexp1(n-1);}93【5-18】Fibonacci(斐波那契)序列的遞歸定義:遞歸函數(shù)定義:intfib(intn){returnn<=2?1:fib(n-1)+fib(n-2);}負(fù)參數(shù)值也定義為1。這是“合理”處置。問題分析:這個程序好不好?一方面,很好!程序與數(shù)學(xué)定義的關(guān)系很清晰,正確性容易確認(rèn),定義易讀易理解。5.3.2Fibonacci序列(計(jì)算與時間)94有何缺點(diǎn)?寫程序計(jì)時并分析:intmain(){intt0,t1;cout<<"n\tfib(n)\ttime(s)"<<endl;for(intn=10;n<=45;++n){
t0=clock();//調(diào)用fib(n)之前的時刻
cout<<n<<"\t"<<fib(n)<<"\t";//!!!
t1=clock();
////調(diào)用fib(n)結(jié)束之后的時刻
cout<<(double)(t1-t0)/CLOCKS_PER_SEC <<endl;//時間差
}return0;}95nfib(n)time(s)……(略)271964180.001283178110.002295142290.003308320400.0043113462690.0073221783090.013335245780.0163457028870.0253592274650.043614930
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 班級責(zé)任明確與分工計(jì)劃
- 班級新媒體利用教學(xué)活動計(jì)劃
- 農(nóng)畜產(chǎn)品批發(fā)服務(wù)行業(yè)相關(guān)投資計(jì)劃提議
- 《講生命和生命科學(xué)》課件
- 加氫系列產(chǎn)品行業(yè)相關(guān)投資計(jì)劃提議
- 花卉植物運(yùn)輸合同三篇
- 快遞物流行業(yè)保安工作總結(jié)計(jì)劃
- 民風(fēng)民俗和我們的生活
- 【培訓(xùn)課件】財(cái)務(wù)報(bào)賬員培訓(xùn) 法律法規(guī)
- 《項(xiàng)目管理培訓(xùn)課程》課件
- 課內(nèi)文言文閱讀(原卷版)-2024-2025學(xué)年九年級語文上學(xué)期期中試題分類匯編(山東專用)
- 院感課件下載
- 2022幼兒園教師讀書參考心得體會5篇
- 2024年《內(nèi)科護(hù)理學(xué)》考試復(fù)習(xí)題庫(含答案)
- MOOC 機(jī)械基礎(chǔ)實(shí)景教學(xué)(機(jī)械原理)-哈爾濱工業(yè)大學(xué) 中國大學(xué)慕課答案
- 西門子s7_200PLC基本指令
- 特殊學(xué)生成長檔案記錄(精選.)
- 高速公路安全封路施工標(biāo)志標(biāo)牌示意圖
- 計(jì)算機(jī)科學(xué)前沿技術(shù)課心得體會
- 窗玻璃的可見光透射比.遮陽系數(shù)
- 監(jiān)理工作程序流程圖(共24頁)
評論
0/150
提交評論