信息安全案例教程:技術(shù)與應(yīng)用 第2版 課件 第6章 結(jié)構(gòu)化程序設(shè)計(jì)_第1頁
信息安全案例教程:技術(shù)與應(yīng)用 第2版 課件 第6章 結(jié)構(gòu)化程序設(shè)計(jì)_第2頁
信息安全案例教程:技術(shù)與應(yīng)用 第2版 課件 第6章 結(jié)構(gòu)化程序設(shè)計(jì)_第3頁
信息安全案例教程:技術(shù)與應(yīng)用 第2版 課件 第6章 結(jié)構(gòu)化程序設(shè)計(jì)_第4頁
信息安全案例教程:技術(shù)與應(yīng)用 第2版 課件 第6章 結(jié)構(gòu)化程序設(shè)計(jì)_第5頁
已閱讀5頁,還剩70頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

第6章結(jié)構(gòu)化程序設(shè)計(jì)信息學(xué)院2024引言6.1案例:模擬乒乓球比賽6.2函數(shù)6.3模塊6.4自頂向下和自底向上6.5編程實(shí)踐:調(diào)試程序6.6本章小結(jié)6.7習(xí)題在章首案例的指引下,本章將深入學(xué)習(xí)結(jié)構(gòu)化程序設(shè)計(jì)思想。在編程實(shí)踐中,還將學(xué)習(xí)如何調(diào)試程序,發(fā)現(xiàn)和找到程序的錯(cuò)誤并修正它們是成為一個(gè)合格程序員的基本功。6.1案例:模擬乒乓球比賽計(jì)算機(jī)模擬,也稱為計(jì)算機(jī)仿真,是用計(jì)算機(jī)程序來對(duì)現(xiàn)實(shí)世界進(jìn)行建模以解決現(xiàn)實(shí)世界中的問題。本章用來模擬乒乓球比賽。不同實(shí)力的選手對(duì)陣,贏的機(jī)率有多大呢?在現(xiàn)實(shí)世界中,兩位選手對(duì)陣的機(jī)會(huì)是有限的,實(shí)力差距不是很大的選手之間也是有贏有輸,很難確切地說贏的機(jī)率有多大。而用計(jì)算機(jī)模擬就不同了,我們可以用循環(huán)來模擬兩位選手之間的很多場比賽,看看最終各勝負(fù)多少場。6.1案例:模擬乒乓球比賽現(xiàn)實(shí)世界中乒乓球比賽的計(jì)分規(guī)則:一場乒乓球比賽通常采用11分制,即先達(dá)到11分的一方贏得這一局比賽,但如果雙方打成10平,那么需要有2分的差距才能決出勝負(fù),例如12-10或13-11等;比賽雙方輪流發(fā)球,每人連續(xù)發(fā)兩次球后,交換發(fā)球權(quán),但當(dāng)比分達(dá)到10平時(shí),雙方每發(fā)一球便交換發(fā)球權(quán),直至分出勝負(fù)。假設(shè)有兩位選手A和B,且總是A先發(fā)球。一個(gè)選手的技能水平用其發(fā)球時(shí)能得分的可能性來表示,比如0.6表示該選手發(fā)球時(shí)有60%的機(jī)會(huì)能得分。運(yùn)行程序,輸入0.8、0.4、5000,結(jié)果顯示A贏的機(jī)率為98.1%,B贏的機(jī)率僅為1.9%。再次運(yùn)行程序,輸入0.6、0.5、10000,結(jié)果顯示A贏的機(jī)率為68.7%,B贏的機(jī)率為31.3%。第三次運(yùn)行程序,輸入0.7、0.7、20000,結(jié)果顯示A和B贏的機(jī)率為49.8%和50.2%。最后輸入0.1、0.1、30000,結(jié)果顯示雙方輸贏各半。程序還進(jìn)行了錯(cuò)誤處理和數(shù)據(jù)有效性檢驗(yàn),當(dāng)用戶輸入錯(cuò)誤時(shí),程序提示“InputError!”。當(dāng)用戶輸入的數(shù)值不在有效范圍內(nèi)時(shí),程序也會(huì)給出相應(yīng)提示,比如“Theprob.playerBwinsshouldbebetween0and1.”。6.1案例:模擬乒乓球比賽結(jié)構(gòu)化程序設(shè)計(jì)主要是采用“自頂向下、逐步求精”以及“模塊化”的程序設(shè)計(jì)方法。模塊化即是將程序劃分成一個(gè)個(gè)功能模塊,稱為函數(shù)。6.2函數(shù)6.2.1函數(shù)的定義和調(diào)用我們已經(jīng)調(diào)用過很多Python自帶的內(nèi)置函數(shù)以及若干標(biāo)準(zhǔn)庫和第三方庫中提供的函數(shù)。函數(shù)就是一段完成特定功能的程序代碼,這段代碼提供了外部接口供反復(fù)調(diào)用,其內(nèi)部實(shí)現(xiàn)細(xì)節(jié)對(duì)于調(diào)用者來說無需關(guān)心。正如我們之前調(diào)用了那么多函數(shù),其內(nèi)部實(shí)現(xiàn)代碼是如何編寫的我們并不知曉,也無需知曉,我們只需要知道函數(shù)的功能,調(diào)用時(shí)需要的函數(shù)名稱和參數(shù)(接口),以及調(diào)用后會(huì)返回什么樣的值就可以了。6.2.1函數(shù)的定義和調(diào)用那么我們可以自己定義函數(shù)嗎?當(dāng)然可以。即使我們自己定義的函數(shù)沒有那么有用可以供其他人調(diào)用,它們也可以被我們自己調(diào)用。定義自己的函數(shù)并調(diào)用它的好處至少有兩點(diǎn):我們可以多次調(diào)用同一個(gè)函數(shù),而無需重復(fù)寫多次代碼,如果實(shí)現(xiàn)功能的代碼有修改,我們直接在函數(shù)內(nèi)部修改就可以了,只要接口不變,調(diào)用函數(shù)的地方就不用修改;函數(shù)作為模塊化設(shè)計(jì)的基本單元,是實(shí)現(xiàn)結(jié)構(gòu)化程序設(shè)計(jì)的基礎(chǔ),它能使程序的邏輯結(jié)構(gòu)更加清晰,易于閱讀和修改。6.2.1函數(shù)的定義和調(diào)用定義函數(shù)首先要給函數(shù)起個(gè)名字,命名規(guī)則和變量一樣。定義函數(shù)要用到關(guān)鍵詞def,一般形式如下:def<name>(<formal-parameters>):<statements><name>就是給函數(shù)起的名字,<formal-parameters>就是函數(shù)的參數(shù),可以沒有,也可以有多個(gè)。注意:如果函數(shù)只是被定義,而沒有被調(diào)用,它是不會(huì)被執(zhí)行的。6.2.1函數(shù)的定義和調(diào)用調(diào)用自己定義的函數(shù)和我們之前調(diào)用別人定義的函數(shù)一樣,只需要寫上函數(shù)的名字,并將函數(shù)的參數(shù)傳遞給它,一般形式如下:<name>(<actual-parameters>)定義函數(shù)時(shí)的參數(shù)被稱為形式參數(shù)(簡稱形參),調(diào)用函數(shù)時(shí)的參數(shù)被稱為實(shí)際參數(shù)(簡稱實(shí)參),一般來說二者一一對(duì)應(yīng)。此外,使用return語句可以讓函數(shù)返回值,函數(shù)執(zhí)行完畢后將返回值返回到調(diào)用它的地方,一般將返回值賦值給某個(gè)變量。6.2.1函數(shù)的定義和調(diào)用習(xí)慣上,我們會(huì)定義一個(gè)叫做main()的主函數(shù),然后調(diào)用它作為程序的執(zhí)行入口。一般來說,主函數(shù)用來處理輸入和輸出,數(shù)據(jù)處理則可以調(diào)用其他函數(shù)來完成?!纠?-1】定義一個(gè)函數(shù)來判定閏年main()函數(shù)用來處理輸入和輸出,在輸入部分加入了循環(huán)和錯(cuò)誤處理,在調(diào)用leap()函數(shù)得到返回值后,就可以輸出結(jié)果了。leap()函數(shù)有一個(gè)形式參數(shù)year,在判斷它是否為閏年后,return語句將變量isLeapYear的值返回。調(diào)用main()函數(shù)?!纠?-1】定義一個(gè)函數(shù)來判定閏年main()函數(shù)中調(diào)用leap()函數(shù)的過程實(shí)際上分為4步:main()函數(shù)在調(diào)用leap()函數(shù)的位置(第6行)停止執(zhí)行;leap()函數(shù)的形式參數(shù)獲得了main()函數(shù)中實(shí)際參數(shù)的值,注意雖然本例中形參和實(shí)參使用了相同的名字year,但由于作用域不同,它們實(shí)際上是兩個(gè)不同的變量;開始執(zhí)行l(wèi)eap()函數(shù)中的語句;leap()函數(shù)執(zhí)行完畢后,回到main()函數(shù)調(diào)用leap()函數(shù)的位置繼續(xù)執(zhí)行,由于leap()函數(shù)有返回值,可直接用于第6行語句的執(zhí)行?!纠?-1】定義一個(gè)函數(shù)來判定閏年在調(diào)用一個(gè)函數(shù)的時(shí)候,要確保該函數(shù)在調(diào)用之前已經(jīng)被定義,否則程序會(huì)報(bào)錯(cuò),錯(cuò)誤類型為“NameError”(名字錯(cuò)誤)。本例中main()函數(shù)在被調(diào)用之前就已經(jīng)定義好了,如果將調(diào)用main()函數(shù)的語句放在定義之前,程序就會(huì)報(bào)如下錯(cuò)誤:Traceback(mostrecentcalllast):File"C:\Python311\leap_year_func.py",line1,in<module>main()NameError:name'main'isnotdefined.Didyoumean:'min'?【例6-1】定義一個(gè)函數(shù)來判定閏年那么你可能會(huì)問,main()函數(shù)中有對(duì)leap()函數(shù)的調(diào)用,但是對(duì)leap()函數(shù)的定義卻在main()函數(shù)之后,為什么沒有報(bào)錯(cuò)呢?如果把調(diào)用main()的語句放在兩個(gè)函數(shù)定義的中間(即第13行的位置),情況又如何呢?6.2.1函數(shù)的定義和調(diào)用在本章案例中,我們也定義一個(gè)主函數(shù)main()來處理輸入和輸出,模擬比賽的任務(wù)交給另一個(gè)函數(shù)simNGames()。主函數(shù)在接收輸入的過程中,進(jìn)行了錯(cuò)誤處理和數(shù)據(jù)有效性檢驗(yàn)。變量probA和probB表示雙方選手的技能水平,變量n表示模擬的比賽場次。首先,用try-except進(jìn)行“ValueError”(值錯(cuò)誤)的捕獲,無論哪一個(gè)變量輸入錯(cuò)誤,都提示“InputError!”。其次,如果用戶輸入的數(shù)值不在有效范圍內(nèi),也會(huì)導(dǎo)致后面模擬比賽時(shí)發(fā)生錯(cuò)誤,因此,需要進(jìn)行數(shù)據(jù)有效性驗(yàn)證。6.2.1函數(shù)的定義和調(diào)用變量probA和probB都是在0~1之間,考慮到模擬過多場比賽并不會(huì)增加多少準(zhǔn)確度反而會(huì)增加程序運(yùn)行時(shí)間,我們將模擬比賽場次n限定在0~1000000之間。使用三層嵌套if語句來進(jìn)行三個(gè)變量的數(shù)據(jù)有效性檢驗(yàn),只有第一個(gè)變量的值輸入有效,才能輸入第二個(gè)變量的值,只有前兩個(gè)變量的值輸入有效,才能輸入第三個(gè)變量的值,只有三個(gè)變量的值輸入都有效,才能進(jìn)行數(shù)據(jù)的處理和輸出。6.2.1函數(shù)的定義和調(diào)用defmain():print('Thisprogramsimulatesagameoftable-tennisbetweentwoplayers.')print("Theabilitiesofeachplayerisindicatedbyaprobability(between0and1).")print("PlayerAalwayshasthefirstserve.")try:

probA=float(input("Whatistheprob.playerAwinsaserve?"))ifprobA<=0orprobA>=1:print("Theprob.playerAwinsshouldbebetween0and1.")else:

probB=float(input("Whatistheprob.playerBwinsaserve?"))ifprobB<=0orprobB>=1:print("Theprob.playerBwinsshouldbebetween0and1.")else:

n=int(input("Howmanygamestosimulate?"))ifn<=0orn>=1000000:print("Gamestosimulateshouldbebetween0and1000000.")else:

winsA,winsB=simNGames(n,probA,probB)print("\nGamessimulated:",n)print("WinsforA:{0}({1:0.1%})".format(winsA,winsA/n))print("WinsforB:{0}({1:0.1%})".format(winsB,winsB/n))exceptValueError:print("InputError!")6.2.1函數(shù)的定義和調(diào)用主函數(shù)main()中調(diào)用了模擬n場比賽的函數(shù)simNGames(),該函數(shù)有3個(gè)參數(shù),分別是模擬的場次和兩位選手的技能水平,返回值有兩個(gè),分別是兩位選手贏了的場次,即winsA和winsB。該函數(shù)的定義較為復(fù)雜,我們先定義好接口,內(nèi)部實(shí)現(xiàn)細(xì)節(jié)后續(xù)進(jìn)行補(bǔ)充,先用不會(huì)執(zhí)行任何操作的pass語句作為占位符。6.2.1函數(shù)的定義和調(diào)用defsimNGames(n,probA,probB):#模擬n場比賽

winsA=winsB=0#贏的場次passreturnwinsA,winsB#模擬結(jié)束,返回雙方贏的場次main()函數(shù)在調(diào)用simNGames()之后得到了雙方選手各贏了多少場次的結(jié)果,就可以將結(jié)果輸出了。程序的最后再加上對(duì)main()函數(shù)的調(diào)用。至此,本章案例的初步版本形成,將程序文件保存為ch06.py,運(yùn)行程序,如果有錯(cuò)誤則進(jìn)行修正。試一試6.2.2參數(shù)的傳遞函數(shù)定義中的參數(shù)被稱為形參,調(diào)用函數(shù)時(shí)傳遞過去的參數(shù)被稱為實(shí)參。形參和實(shí)參的名字可以不同,也可以相同,即使名字相同,它們也是不同的變量?!纠?-1】中的形式參數(shù)year在leap()函數(shù)內(nèi)有效,而實(shí)際參數(shù)year在main()函數(shù)內(nèi)有效,作用域不同。位置參數(shù)位置參數(shù)要求實(shí)參和形參一一對(duì)應(yīng)且參數(shù)個(gè)數(shù)和順序完全相同,有默認(rèn)值的形參除外。在函數(shù)定義時(shí),要求有默認(rèn)值的形參放在沒有默認(rèn)值的形參后面。在函數(shù)調(diào)用時(shí),可以不給有默認(rèn)值的形參傳遞實(shí)參,如果沒有相應(yīng)實(shí)參,形參將取默認(rèn)值。位置參數(shù)如下代碼定義了一個(gè)計(jì)算終值的函數(shù):deffut_val(principal,year,rate=0.02):

future_value=principal

foriinrange(year):

future_value=future_value*(1+rate)returnfuture_value形式參數(shù)rate被賦予了默認(rèn)值0.02,在定義時(shí)被放在了最后,如果試圖將其放在其他兩個(gè)形參的前面,程序會(huì)出現(xiàn)如下語法錯(cuò)誤:SyntaxError:non-defaultargumentfollowsdefaultargument位置參數(shù)下面來調(diào)用fut_val()函數(shù),假設(shè)本金為1000,想看一下10年以后的終值。輸入fut_val(1000),程序出現(xiàn)“TypeError”(類型錯(cuò)誤),如下所示:Traceback(mostrecentcalllast):File"<pyshell#14>",line1,in<module>fut_val(1000)TypeError:fut_val()missing1requiredpositionalargument:'year'表示調(diào)用fut_val()函數(shù)時(shí)遺漏了一個(gè)必須提供的位置參數(shù)year,1000作為實(shí)參僅傳遞給了第一個(gè)位置參數(shù)principal。輸入fut_val(1000,10),返回利率為默認(rèn)值0.02時(shí)的終值:1218.9944199947574。關(guān)鍵字參數(shù)如果我們?cè)谡{(diào)用fut_val()函數(shù)時(shí)記錯(cuò)了形參的位置,輸入fut_val(10,1000),那么10就成了本金,1000就成了年,結(jié)果自然也就是錯(cuò)誤的。使用關(guān)鍵字參數(shù)可以解決這個(gè)問題,在調(diào)用函數(shù)時(shí)傳遞形參-實(shí)參對(duì),直接將形參和實(shí)參關(guān)聯(lián)起來,無需考慮形參在函數(shù)定義中的位置。以上函數(shù)調(diào)用改為關(guān)鍵字參數(shù)的代碼如下:fut_val(year=10,principal=1000)關(guān)鍵字參數(shù)雖然不要求記住形參的位置,但需要記住形參的名字??勺冮L度的參數(shù)有時(shí)候,我們預(yù)先并不知道函數(shù)需要接收多個(gè)參數(shù),也就是說參數(shù)列表的長度是可變的。這種情況下在定義函數(shù)時(shí)無法確定參數(shù)的個(gè)數(shù),要到調(diào)用函數(shù)的時(shí)候,根據(jù)實(shí)際參數(shù)傳遞的情況來確定。在函數(shù)定義中,可變長度的參數(shù)實(shí)際上是一個(gè)元組,要在參數(shù)名字前加上“*”,并且要放在所有位置參數(shù)的后面,表示位置參數(shù)后面?zhèn)鬟f多少參數(shù)它都接收,如果后面沒有參數(shù),則元組為空。print(*args,sep='',end='\n',file=None,flush=False)可變長度的參數(shù)下面我們來定義一個(gè)帶初值的求最大值的函數(shù)。如果將兩個(gè)形參的位置對(duì)調(diào),即:defmax_init(init=100,*values),結(jié)果會(huì)產(chǎn)生什么樣的變化?為什么?試一試6.2.3變量的作用域變量的作用域是指變量在程序中定義的位置及其能被訪問的范圍。變量的作用域包括局部和全局兩類。在函數(shù)內(nèi)部定義的變量稱為局部變量,其作用范圍僅限于函數(shù)內(nèi)部,而在整個(gè)程序范圍內(nèi)可見的變量稱為全局變量。不同函數(shù)內(nèi)部定義的變量即使同名,也互不干擾,因?yàn)樗鼈冎辉诟髯院瘮?shù)范圍內(nèi)有效。函數(shù)的形式參數(shù)也是函數(shù)內(nèi)部定義的局部變量。6.2.3變量的作用域【例6-2】用函數(shù)實(shí)現(xiàn)課程的錄入和GPA的計(jì)算將【例5-5】和【例5-6】合并成一個(gè)程序文件,用兩個(gè)函數(shù)分別實(shí)現(xiàn)課程的錄入和GPA的計(jì)算,通過主函數(shù)來調(diào)用它們。

course()函數(shù)用來錄入課程的學(xué)分和成績,并寫入CSV文件中。gpa()函數(shù)用來讀取CSV文件,計(jì)算GPA的值并返回。主函數(shù)main()中僅有兩條調(diào)用函數(shù)的語句,由于唯一的參數(shù)有默認(rèn)值,調(diào)用時(shí)可不傳遞任何參數(shù)。這兩個(gè)函數(shù)都只有一個(gè)文件名的參數(shù),且賦予了默認(rèn)值“credit_score.csv”??梢钥闯觯@兩個(gè)函數(shù)有很多局部變量都是重名的,它們互不干擾。6.2.3變量的作用域本章案例中,winsA和winsB這兩個(gè)變量在main()函數(shù)和simNGames()這兩個(gè)函數(shù)中都有,意義也相同,但它們都是局部變量,在各自的函數(shù)內(nèi)起作用。simNGames()函數(shù)的返回值即是這兩個(gè)變量的值,在main()函數(shù)中調(diào)用simNGames()函數(shù)時(shí)用自己內(nèi)部的這兩個(gè)變量來接收返回值,從而使它們的值相同。simNGames()函數(shù)內(nèi)部的這兩個(gè)變量僅在函數(shù)執(zhí)行期間有效,執(zhí)行完畢后即消失。程序文件也被稱為模塊(module),Python自帶的標(biāo)準(zhǔn)庫就是已經(jīng)寫好的程序文件,通過引入模塊就可以使用它們。6.3模塊6.3.1模塊的執(zhí)行和引入模塊是一個(gè)可以被分享的Python代碼,它既可以自己單獨(dú)被執(zhí)行,也可以被其他程序引入。一個(gè)模塊被其他程序引入,其定義的函數(shù)就都可以被其他程序所調(diào)用。如果我們寫的程序都是直接執(zhí)行的代碼,那么被引入時(shí)這些代碼也會(huì)被執(zhí)行。經(jīng)過上一節(jié)的學(xué)習(xí),我們將程序組織劃分成若干個(gè)函數(shù),程序執(zhí)行入口就是最后調(diào)用主函數(shù)main()的語句,如果這個(gè)程序作為模塊被其他程序引入,調(diào)用主函數(shù)main()的語句仍然會(huì)被執(zhí)行。6.3.1模塊的執(zhí)行和引入我們引入一個(gè)模塊的目的,是為了使用其中定義的常量、函數(shù)或者類,而不是為了執(zhí)行其中的所有代碼。為了解決這個(gè)問題,我們可以把程序中最后調(diào)用主函數(shù)main()的語句刪掉。但如果我們想直接執(zhí)行這個(gè)程序,也沒有執(zhí)行入口了。6.3.1模塊的執(zhí)行和引入使用Python的系統(tǒng)變量__name__可以解決這個(gè)問題,當(dāng)一個(gè)程序直接被執(zhí)行的時(shí)候,這個(gè)變量的值是“__main__”,而如果這個(gè)程序作為模塊被其他程序引入,這個(gè)變量的值就是這個(gè)模塊的名字。因此,我們只要將調(diào)用main()函數(shù)的語句改為如下的判斷語句即可:if__name__=='__main__':main()6.3.1模塊的執(zhí)行和引入直接輸入leap(2000),系統(tǒng)報(bào)錯(cuò),出現(xiàn)“NameError”(名字錯(cuò)誤),因?yàn)閘eap()函數(shù)所屬的名空間(namespace)并不是當(dāng)前模塊,而是leap_year_func模塊,需要加上模塊名才能訪問和調(diào)用。6.3.2模塊的結(jié)構(gòu)這些部分都是可選的,(4)~(6)中至少要有一個(gè),其中定義類的部分(5)將在下一章中學(xué)習(xí)。序號(hào)形式說明(1)"Thisisamodel."模塊文檔(2)import<modulename>引入模塊(3)<variablename>=<value>定義全局變量(4)class<classname>:<methoddefinitions>定義類(5)def<functionname>(<parameters>):<statements>定義函數(shù)(6)if__name__=='__main__':<functionname>(parameters)執(zhí)行主體6.3.2模塊的結(jié)構(gòu)模塊的第一行是文檔字符串(docstring),一般是對(duì)模塊的功能介紹。前面我們學(xué)習(xí)過用“#”標(biāo)注的注釋語句,注釋語句也可以對(duì)模塊的功能進(jìn)行說明,以增加程序的可讀性,但注釋的內(nèi)容在程序外部是無法獲取的。文檔字符串則可以通過系統(tǒng)變量__doc__來獲取,也可以通過調(diào)用內(nèi)置函數(shù)help()來獲取。不僅模塊可以有文檔字符串,類、函數(shù)都可以有文檔字符串,都放置在第一行的位置。文檔字符串可以幫助我們從程序外部了解模塊、類、函數(shù)的功能?!纠?-3】用函數(shù)實(shí)現(xiàn)計(jì)算終值和求最大值將計(jì)算終值和求最大值的兩個(gè)函數(shù)寫入一個(gè)程序文件,加入文檔字符串,在IDLE解釋器中引入這個(gè)模塊,查看相關(guān)信息,并調(diào)用這兩個(gè)函數(shù)?!纠?-3】用函數(shù)實(shí)現(xiàn)計(jì)算終值和求最大值本節(jié)通過自頂向下的設(shè)計(jì)方法來解決本章案例這個(gè)復(fù)雜的問題。自頂向下(Top-down)的設(shè)計(jì)就是把復(fù)雜的問題分解為更小、更簡單的問題,如果分解出來的問題仍然比較復(fù)雜,那么繼續(xù)分解,直到分解出來的問題足夠小、足夠簡單。實(shí)施時(shí),則是自底向上(Bottom-up),先解決底層最小、最簡單的問題,然后一層一層向上組裝起來,直到最后形成對(duì)原始復(fù)雜問題的解決方案。6.4自頂向下和自底向上6.4.1自頂向下設(shè)計(jì)simNGames()函數(shù)的功能是模擬n場比賽,可以用一個(gè)指定次數(shù)的for循環(huán)來實(shí)現(xiàn),把winsA和winsB這兩個(gè)變量定義為累加變量。再分解出來一個(gè)模擬一場比賽的simOneGame()函數(shù),根據(jù)模擬比賽結(jié)果,如果A贏,winsA就加1,如果B贏,winsB就加1。那么需要傳遞給simOneGame()函數(shù)哪些參數(shù)呢?顯然還是需要probA和probB,即雙方的技能水平。進(jìn)一步考慮,需不需要simOneGame()函數(shù)返回值呢?需要,根據(jù)返回值能夠確定誰贏。如何能夠確定誰贏呢?那就是誰得的分高,誰就贏了。因此,定義scoreA和scoreB兩個(gè)變量來接收simOneGame()函數(shù)的返回值。6.4.1自頂向下設(shè)計(jì)defsimNGames(n,probA,probB):#模擬n場比賽

winsA=winsB=0#贏的場次foriinrange(n):

scoreA,scoreB=simOneGame(probA,probB)#調(diào)用模擬一場比賽的函數(shù)ifscoreA>scoreB:

winsA+=1else:

winsB+=1returnwinsA,winsB#模擬結(jié)束,返回雙方贏的場次6.4.1自頂向下設(shè)計(jì)6.4.1自頂向下設(shè)計(jì)gameOver()函數(shù)的返回結(jié)果作為while循環(huán)停止的條件,即為True或False,需要的參數(shù)就是雙方的得分scoreA和scoreB。根據(jù)發(fā)球規(guī)則,whoServe()函數(shù)相對(duì)復(fù)雜一些,需要考慮發(fā)球方和雙方發(fā)球的次數(shù),還要考慮雙方的得分(10平之后規(guī)則發(fā)生變化)。6.4.1自頂向下設(shè)計(jì)發(fā)球方和雙方發(fā)球的次數(shù)都與發(fā)球相關(guān),我們定義一個(gè)列表變量serving來存放,調(diào)用whoServe()函數(shù)的時(shí)候作為參數(shù)之一傳遞給它,由于列表是可以修改的數(shù)據(jù)類型,在whoServe()函數(shù)中對(duì)這個(gè)列表進(jìn)行的修改,回到simOneGame()函數(shù)時(shí)變量serving也跟著發(fā)生了變化,而無論whoServe()函數(shù)中相應(yīng)的形參的名字叫什么,因?yàn)槎咧皇侵赶蛲粋€(gè)值的不同的黃色的小便箋而已,即使形參在回到simOneGame()函數(shù)后消失不見,這個(gè)值已經(jīng)被whoServe()函數(shù)修改。6.4.1自頂向下設(shè)計(jì)在確定每次誰發(fā)球后,就可以根據(jù)生成的隨機(jī)數(shù)來判定發(fā)球方是否贏得了這一分,具體判定方法是:如果生成的隨機(jī)數(shù)(0~1)小于發(fā)球方的技能水平,那么發(fā)球方贏得這一分,因?yàn)榘l(fā)球方的技能水平代表了他(她)發(fā)球時(shí)贏的概率,如果生成的隨機(jī)數(shù)(0~1)大于等于發(fā)球方的技能水平,那么對(duì)方贏得這一分。由于要生成隨機(jī)數(shù),引入random模塊中的random()函數(shù),代碼如下:fromrandomimportrandom。6.4.2自底向上實(shí)施實(shí)施過程從底層開始,逐層向上。實(shí)施除寫代碼外,還包括測試。寫代碼先來定義底層最小、最簡單的gameOver()函數(shù):defgameOver(a,b):"Determinewhethergameisoverornot"#判斷比賽是否結(jié)束ifabs(a-b)>=2and(a>=11orb>=11):returnTrueelse:returnFalse寫代碼defwhoServe(s,a,b):"Determinewhohastherighttoserve"#確定誰發(fā)球,參數(shù)s是一個(gè)列表,可以修改其中的值后返回ifabs(a-b)<2and(a>10orb>10):#如果是10平之后ifs[0]=='A':

s[0]='B'else:

s[0]='A'else:ifs[0]=='':

s[0]='A'#假設(shè)總是A先發(fā)球elifs[0]=='A':#如果上一個(gè)球是A發(fā)球ifs[1]%2==0:#如果A的發(fā)球次數(shù)是偶數(shù)

s[0]='B'#換成B發(fā)球else:ifs[2]%2==0:

s[0]='A's對(duì)應(yīng)傳遞過來的serving,這個(gè)列表變量包含三個(gè)元素,第一個(gè)是發(fā)球方,第二個(gè)是選手A在這場比賽的累計(jì)發(fā)球次數(shù),第三個(gè)是選手B的累計(jì)發(fā)球次數(shù)。通過累計(jì)的發(fā)球次數(shù)是否是偶數(shù)來判定是否已經(jīng)發(fā)了兩個(gè)球,如果是,則換發(fā)。寫代碼defsimOneGame(probA,probB):"Simulateonegame"#模擬一場比賽serving=['',0,0]#誰發(fā)球以及雙方發(fā)球的次數(shù)scoreA=scoreB=0#雙方得分whilenotgameOver(scoreA,scoreB):#只要沒有達(dá)到比賽結(jié)束的條件

whoServe(serving,scoreA,scoreB)#調(diào)用確定誰發(fā)球的函數(shù)ifserving[0]=="A":serving[1]+=1ifrandom()<probA:scoreA+=1else:scoreB+=1else:serving[2]+=1ifrandom()<probB:scoreB+=1else:scoreA+=1returnscoreA,scoreB#比賽結(jié)束,返回雙方得分對(duì)serving、scoreA、scoreB賦初值。由于實(shí)參serving是列表,whoServe()函數(shù)內(nèi)對(duì)相應(yīng)形參s所作的修改也會(huì)返回給serving,因此whoServe()函數(shù)不需要返回值。確定誰發(fā)球后,要把他(她)的累計(jì)發(fā)球次數(shù)加一,然后用生成的隨機(jī)數(shù)和發(fā)球方的技能水平去比較,來判定誰贏得這一分。測試至此,本章案例的程序已編寫完成(ch06.py),編寫完成的代碼還需要經(jīng)過測試,同樣遵循自底向上的原則。在IDLE解釋器中引入本章案例模塊(ch06),為了測試方便,我們使用如下語句:>>>fromch06import*首先測試底層的gameOver()函數(shù),先后輸入gameOver(0,0)、gameOver(6,5)、gameOver(11,9)、gameOver(10,11)、gameOver(11,13),觀察返回結(jié)果是否正確,如果不正確,找出程序錯(cuò)誤并進(jìn)行修正。測試然后測試whoServe()函數(shù),測試比賽剛開始的情況:>>>s=['',0,0]>>>whoServe(s,0,0)whoServe()函數(shù)沒有返回結(jié)果,觀察s的值,結(jié)果是['A',0,0]。下面重點(diǎn)測試10平前后的情況。假設(shè)現(xiàn)在A與B的比分是9比10,B剛發(fā)完一個(gè)球,該誰發(fā)球呢?>>>s=['B',10,9]>>>whoServe(s,9,10)觀察s的值,結(jié)果是['B',10,9],即仍然是B發(fā)球。測試這時(shí)候讓B累計(jì)發(fā)球次數(shù)加一。假設(shè)A贏一分,比分變成10比10,下面該誰發(fā)球呢?>>>s[2]+=1>>>whoServe(s,10,10)觀察s的值,結(jié)果是['A',10,10],即換A發(fā)球。這時(shí)候讓A累計(jì)發(fā)球次數(shù)加一,假設(shè)B贏得一分,比分變成10比11,下面該誰發(fā)球呢?>>>s[1]+=1>>>whoServe(s,10,11)觀察s的值,結(jié)果是['B',11,10],即換B發(fā)球。測試這時(shí)候讓B累計(jì)發(fā)球次數(shù)加一,假設(shè)A又贏得一分,比分變成11比11,下面該誰發(fā)球呢?>>>s[2]+=1>>>whoServe(s,11,11)觀察s的值,結(jié)果是['A',11,11],即換A發(fā)球。這時(shí)候讓A累計(jì)發(fā)球次數(shù)加一,假設(shè)B又贏得一分,比分變成11比12,下面該誰發(fā)球呢?>>>s[1]+=1>>>whoServe(s,11,12)觀察s的值,結(jié)果是['B',12,11],即換B發(fā)球。測試這時(shí)候讓B累計(jì)發(fā)球次數(shù)加一,假設(shè)B又贏得一分,比分變成11比13,比賽結(jié)束。如果你測試的情況和上面不一致,請(qǐng)找出程序錯(cuò)誤并進(jìn)行修正。接下來測試simOneGame()函數(shù),由于這個(gè)函數(shù)只返回一場比賽結(jié)束后雙方選手的得分,很難判斷結(jié)果是否正確,因此,我們?cè)诿恳淮窝h(huán)迭代后加入一條測試語句:print(serving,scoreA,scoreB),以觀察循環(huán)過程中結(jié)果是否正確。對(duì)于已經(jīng)引入的模塊程序做出修改,要重新啟動(dòng)Shell(Shell菜單下的RestartShell或者按下快捷鍵Ctrl+F6)并重新引入模塊,否則還是按舊程序執(zhí)行。測試>>>simOneGame(0.6,0.5)['A',1,0]10['A',2,0]20['B',2,1]30['B',2,2]40['A',3,2]50['A',4,2]60['B',4,3]70['B',4,4]71['A',5,4]72['A',6,4]73['B',6,5]74['B',6,6]75['A',7,6]85['A',8,6]95['B',8,7]96['B',8,8]97['A',9,8]107['A',10,8]117(11,7)測試>>>simNGames(10000,0.6,0.5)(6893,3107)>>>main()Thisprogramsimulatesagameoftable-tennisbetweentwoplayerscalled"A"and"B".Theabilitiesofeachplayerisindicatedbyaprobability(anumberbetween0and1).PlayerAalwayshasthefirstserve.Whatistheprob.playerAwinsaserve?0.6Whatistheprob.playerBwinsaserve?0.5Howmanygamestosimulate?10000

Gamessimulated:10000WinsforA:6822(68.2%)WinsforB:3178(31.8%)我們?cè)诰帉懗绦虻臅r(shí)候會(huì)遇到各種各樣的錯(cuò)誤,主要可以分為三類。一類是語法錯(cuò)誤(SyntaxError),典型的錯(cuò)誤有縮進(jìn)錯(cuò)誤、遺漏錯(cuò)誤等。這類錯(cuò)誤發(fā)生時(shí)程序不能運(yùn)行,系統(tǒng)直接提示出錯(cuò)的位置,因此修正起來相對(duì)容易。第二類錯(cuò)誤是運(yùn)行時(shí)錯(cuò)誤,也就是程序運(yùn)行起來以后發(fā)生的錯(cuò)誤。運(yùn)行時(shí)錯(cuò)誤會(huì)導(dǎo)致程序運(yùn)行后崩潰,系統(tǒng)會(huì)給出明確的“Traceback”報(bào)錯(cuò),幫助程序員了解在什么位置出現(xiàn)了什么類型的錯(cuò)誤,從而能夠快速定位到錯(cuò)誤,并進(jìn)行修正。有些錯(cuò)誤是由于用戶輸入錯(cuò)誤造成的,可以通過錯(cuò)誤處理機(jī)制進(jìn)行捕獲和處理。6.5編程實(shí)踐:調(diào)試程序6.5編程實(shí)踐:調(diào)試程序第三類錯(cuò)誤是邏輯錯(cuò)誤,即程序可以運(yùn)行且沒有發(fā)生任何錯(cuò)誤,但運(yùn)行結(jié)果是錯(cuò)誤的。這類錯(cuò)誤最難發(fā)現(xiàn),也最難修正。比如本章案例中,確定誰發(fā)球或者是判斷比賽是否結(jié)束的邏輯錯(cuò)了,程序照樣可以運(yùn)行,但結(jié)果就不對(duì)了。通過程序測試,可以幫助程序員確定程序有沒有邏輯錯(cuò)誤,測試過程中需要設(shè)計(jì)不同的數(shù)據(jù)來測試不同的情況,保證每一條路徑都是正確的。即

溫馨提示

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

評(píng)論

0/150

提交評(píng)論