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

下載本文檔

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

文檔簡介

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

future_value=principal

foriinrange(year):

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

course()函數(shù)用來錄入課程的學(xué)分和成績,并寫入CSV文件中。gpa()函數(shù)用來讀取CSV文件,計算GPA的值并返回。主函數(shù)main()中僅有兩條調(diào)用函數(shù)的語句,由于唯一的參數(shù)有默認值,調(diào)用時可不傳遞任何參數(shù)。這兩個函數(shù)都只有一個文件名的參數(shù),且賦予了默認值“credit_score.csv”??梢钥闯觯@兩個函數(shù)有很多局部變量都是重名的,它們互不干擾。6.2.3變量的作用域本章案例中,winsA和winsB這兩個變量在main()函數(shù)和simNGames()這兩個函數(shù)中都有,意義也相同,但它們都是局部變量,在各自的函數(shù)內(nèi)起作用。simNGames()函數(shù)的返回值即是這兩個變量的值,在main()函數(shù)中調(diào)用simNGames()函數(shù)時用自己內(nèi)部的這兩個變量來接收返回值,從而使它們的值相同。simNGames()函數(shù)內(nèi)部的這兩個變量僅在函數(shù)執(zhí)行期間有效,執(zhí)行完畢后即消失。程序文件也被稱為模塊(module),Python自帶的標準庫就是已經(jīng)寫好的程序文件,通過引入模塊就可以使用它們。6.3模塊6.3.1模塊的執(zhí)行和引入模塊是一個可以被分享的Python代碼,它既可以自己單獨被執(zhí)行,也可以被其他程序引入。一個模塊被其他程序引入,其定義的函數(shù)就都可以被其他程序所調(diào)用。如果我們寫的程序都是直接執(zhí)行的代碼,那么被引入時這些代碼也會被執(zhí)行。經(jīng)過上一節(jié)的學(xué)習(xí),我們將程序組織劃分成若干個函數(shù),程序執(zhí)行入口就是最后調(diào)用主函數(shù)main()的語句,如果這個程序作為模塊被其他程序引入,調(diào)用主函數(shù)main()的語句仍然會被執(zhí)行。6.3.1模塊的執(zhí)行和引入我們引入一個模塊的目的,是為了使用其中定義的常量、函數(shù)或者類,而不是為了執(zhí)行其中的所有代碼。為了解決這個問題,我們可以把程序中最后調(diào)用主函數(shù)main()的語句刪掉。但如果我們想直接執(zhí)行這個程序,也沒有執(zhí)行入口了。6.3.1模塊的執(zhí)行和引入使用Python的系統(tǒng)變量__name__可以解決這個問題,當(dāng)一個程序直接被執(zhí)行的時候,這個變量的值是“__main__”,而如果這個程序作為模塊被其他程序引入,這個變量的值就是這個模塊的名字。因此,我們只要將調(diào)用main()函數(shù)的語句改為如下的判斷語句即可:if__name__=='__main__':main()6.3.1模塊的執(zhí)行和引入直接輸入leap(2000),系統(tǒng)報錯,出現(xiàn)“NameError”(名字錯誤),因為leap()函數(shù)所屬的名空間(namespace)并不是當(dāng)前模塊,而是leap_year_func模塊,需要加上模塊名才能訪問和調(diào)用。6.3.2模塊的結(jié)構(gòu)這些部分都是可選的,(4)~(6)中至少要有一個,其中定義類的部分(5)將在下一章中學(xué)習(xí)。序號形式說明(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),一般是對模塊的功能介紹。前面我們學(xué)習(xí)過用“#”標注的注釋語句,注釋語句也可以對模塊的功能進行說明,以增加程序的可讀性,但注釋的內(nèi)容在程序外部是無法獲取的。文檔字符串則可以通過系統(tǒng)變量__doc__來獲取,也可以通過調(diào)用內(nèi)置函數(shù)help()來獲取。不僅模塊可以有文檔字符串,類、函數(shù)都可以有文檔字符串,都放置在第一行的位置。文檔字符串可以幫助我們從程序外部了解模塊、類、函數(shù)的功能。【例6-3】用函數(shù)實現(xiàn)計算終值和求最大值將計算終值和求最大值的兩個函數(shù)寫入一個程序文件,加入文檔字符串,在IDLE解釋器中引入這個模塊,查看相關(guān)信息,并調(diào)用這兩個函數(shù)。【例6-3】用函數(shù)實現(xiàn)計算終值和求最大值本節(jié)通過自頂向下的設(shè)計方法來解決本章案例這個復(fù)雜的問題。自頂向下(Top-down)的設(shè)計就是把復(fù)雜的問題分解為更小、更簡單的問題,如果分解出來的問題仍然比較復(fù)雜,那么繼續(xù)分解,直到分解出來的問題足夠小、足夠簡單。實施時,則是自底向上(Bottom-up),先解決底層最小、最簡單的問題,然后一層一層向上組裝起來,直到最后形成對原始復(fù)雜問題的解決方案。6.4自頂向下和自底向上6.4.1自頂向下設(shè)計simNGames()函數(shù)的功能是模擬n場比賽,可以用一個指定次數(shù)的for循環(huán)來實現(xiàn),把winsA和winsB這兩個變量定義為累加變量。再分解出來一個模擬一場比賽的simOneGame()函數(shù),根據(jù)模擬比賽結(jié)果,如果A贏,winsA就加1,如果B贏,winsB就加1。那么需要傳遞給simOneGame()函數(shù)哪些參數(shù)呢?顯然還是需要probA和probB,即雙方的技能水平。進一步考慮,需不需要simOneGame()函數(shù)返回值呢?需要,根據(jù)返回值能夠確定誰贏。如何能夠確定誰贏呢?那就是誰得的分高,誰就贏了。因此,定義scoreA和scoreB兩個變量來接收simOneGame()函數(shù)的返回值。6.4.1自頂向下設(shè)計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è)計6.4.1自頂向下設(shè)計gameOver()函數(shù)的返回結(jié)果作為while循環(huán)停止的條件,即為True或False,需要的參數(shù)就是雙方的得分scoreA和scoreB。根據(jù)發(fā)球規(guī)則,whoServe()函數(shù)相對復(fù)雜一些,需要考慮發(fā)球方和雙方發(fā)球的次數(shù),還要考慮雙方的得分(10平之后規(guī)則發(fā)生變化)。6.4.1自頂向下設(shè)計發(fā)球方和雙方發(fā)球的次數(shù)都與發(fā)球相關(guān),我們定義一個列表變量serving來存放,調(diào)用whoServe()函數(shù)的時候作為參數(shù)之一傳遞給它,由于列表是可以修改的數(shù)據(jù)類型,在whoServe()函數(shù)中對這個列表進行的修改,回到simOneGame()函數(shù)時變量serving也跟著發(fā)生了變化,而無論whoServe()函數(shù)中相應(yīng)的形參的名字叫什么,因為二者只是指向同一個值的不同的黃色的小便箋而已,即使形參在回到simOneGame()函數(shù)后消失不見,這個值已經(jīng)被whoServe()函數(shù)修改。6.4.1自頂向下設(shè)計在確定每次誰發(fā)球后,就可以根據(jù)生成的隨機數(shù)來判定發(fā)球方是否贏得了這一分,具體判定方法是:如果生成的隨機數(shù)(0~1)小于發(fā)球方的技能水平,那么發(fā)球方贏得這一分,因為發(fā)球方的技能水平代表了他(她)發(fā)球時贏的概率,如果生成的隨機數(shù)(0~1)大于等于發(fā)球方的技能水平,那么對方贏得這一分。由于要生成隨機數(shù),引入random模塊中的random()函數(shù),代碼如下:fromrandomimportrandom。6.4.2自底向上實施實施過程從底層開始,逐層向上。實施除寫代碼外,還包括測試。寫代碼先來定義底層最小、最簡單的gameOver()函數(shù):defgameOver(a,b):"Determinewhethergameisoverornot"#判斷比賽是否結(jié)束ifabs(a-b)>=2and(a>=11orb>=11):returnTrueelse:returnFalse寫代碼defwhoServe(s,a,b):"Determinewhohastherighttoserve"#確定誰發(fā)球,參數(shù)s是一個列表,可以修改其中的值后返回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':#如果上一個球是A發(fā)球ifs[1]%2==0:#如果A的發(fā)球次數(shù)是偶數(shù)

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

s[0]='A's對應(yīng)傳遞過來的serving,這個列表變量包含三個元素,第一個是發(fā)球方,第二個是選手A在這場比賽的累計發(fā)球次數(shù),第三個是選手B的累計發(fā)球次數(shù)。通過累計的發(fā)球次數(shù)是否是偶數(shù)來判定是否已經(jīng)發(fā)了兩個球,如果是,則換發(fā)。寫代碼defsimOneGame(probA,probB):"Simulateonegame"#模擬一場比賽serving=['',0,0]#誰發(fā)球以及雙方發(fā)球的次數(shù)scoreA=scoreB=0#雙方得分whilenotgameOver(scoreA,scoreB):#只要沒有達到比賽結(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é)束,返回雙方得分對serving、scoreA、scoreB賦初值。由于實參serving是列表,whoServe()函數(shù)內(nèi)對相應(yīng)形參s所作的修改也會返回給serving,因此whoServe()函數(shù)不需要返回值。確定誰發(fā)球后,要把他(她)的累計發(fā)球次數(shù)加一,然后用生成的隨機數(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é)果是否正確,如果不正確,找出程序錯誤并進行修正。測試然后測試whoServe()函數(shù),測試比賽剛開始的情況:>>>s=['',0,0]>>>whoServe(s,0,0)whoServe()函數(shù)沒有返回結(jié)果,觀察s的值,結(jié)果是['A',0,0]。下面重點測試10平前后的情況。假設(shè)現(xiàn)在A與B的比分是9比10,B剛發(fā)完一個球,該誰發(fā)球呢?>>>s=['B',10,9]>>>whoServe(s,9,10)觀察s的值,結(jié)果是['B',10,9],即仍然是B發(fā)球。測試這時候讓B累計發(fā)球次數(shù)加一。假設(shè)A贏一分,比分變成10比10,下面該誰發(fā)球呢?>>>s[2]+=1>>>whoServe(s,10,10)觀察s的值,結(jié)果是['A',10,10],即換A發(fā)球。這時候讓A累計發(fā)球次數(shù)加一,假設(shè)B贏得一分,比分變成10比11,下面該誰發(fā)球呢?>>>s[1]+=1>>>whoServe(s,10,11)觀察s的值,結(jié)果是['B',11,10],即換B發(fā)球。測試這時候讓B累計發(fā)球次數(shù)加一,假設(shè)A又贏得一分,比分變成11比11,下面該誰發(fā)球呢?>>>s[2]+=1>>>whoServe(s,11,11)觀察s的值,結(jié)果是['A',11,11],即換A發(fā)球。這時候讓A累計發(fā)球次數(shù)加一,假設(shè)B又贏得一分,比分變成11比12,下面該誰發(fā)球呢?>>>s[1]+=1>>>whoServe(s,11,12)觀察s的值,結(jié)果是['B',12,11],即換B發(fā)球。測試這時候讓B累計發(fā)球次數(shù)加一,假設(shè)B又贏得一分,比分變成11比13,比賽結(jié)束。如果你測試的情況和上面不一致,請找出程序錯誤并進行修正。接下來測試simOneGame()函數(shù),由于這個函數(shù)只返回一場比賽結(jié)束后雙方選手的得分,很難判斷結(jié)果是否正確,因此,我們在每一次循環(huán)迭代后加入一條測試語句:print(serving,scoreA,scoreB),以觀察循環(huán)過程中結(jié)果是否正確。對于已經(jī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%)我們在編寫程序的時候會遇到各種各樣的錯誤,主要可以分為三類。一類是語法錯誤(SyntaxError),典型的錯誤有縮進錯誤、遺漏錯誤等。這類錯誤發(fā)生時程序不能運行,系統(tǒng)直接提示出錯的位置,因此修正起來相對容易。第二類錯誤是運行時錯誤,也就是程序運行起來以后發(fā)生的錯誤。運行時錯誤會導(dǎo)致程序運行后崩潰,系統(tǒng)會給出明確的“Traceback”報錯,幫助程序員了解在什么位置出現(xiàn)了什么類型的錯誤,從而能夠快速定位到錯誤,并進行修正。有些錯誤是由于用戶輸入錯誤造成的,可以通過錯誤處理機制進行捕獲和處理。6.5編程實踐:調(diào)試程序6.5編程實踐:調(diào)試程序第三類錯誤是邏輯錯誤,即程序可以運行且沒有發(fā)生任何錯誤,但運行結(jié)果是錯誤的。這類錯誤最難發(fā)現(xiàn),也最難修正。比如本章案例中,確定誰發(fā)球或者是判斷比賽是否結(jié)束的邏輯錯了,程序照樣可以運行,但結(jié)果就不對了。通過程序測試,可以幫助程序員確定程序有沒有邏輯錯誤,測試過程中需要設(shè)計不同的數(shù)據(jù)來測試不同的情況,保證每一條路徑都是正確的。即

溫馨提示

  • 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)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論