C語言程序設計第六次作業
一:改錯題:
將所給代碼輸入編譯器,執行編譯命令,錯誤如下圖:
(1)錯誤一:

錯誤指向第18行:

所給錯誤信息顯示在printf后缺少‘;’(分號),但是經過檢查,printf后並未缺少符號,推想是printf前的語句有遺漏。
錯誤原因:在第17行的while語句后缺少分號。
改正方法:在while語句后補全“:”(分號)。
(2)錯誤二:
繼續編譯,系統並未報錯,開始進入測試階段。輸入樣例數據“1E-4”,結果如圖:

不符合預期,再次輸入其它數據“0.02”,結果如圖:

可以推想出現了輸出為常值的典型錯誤,初步推斷為數型錯誤,返回檢查。
錯誤原因:發現題中n定義為int型,且n參與除法運算,所以導致數型輸出會出現問題。
改正方法:將計算式“item = 1 / n”改為“item = 1.0 / n”.
(3)錯誤三:
繼續運行程序,樣例數據不變,輸出如下:

發現結果一樣,推想是循環條件有誤。
錯誤原因:while的循環判斷條件有誤,循環的執行條件為最后一項(item)大於精度(eps),而非小於。
改正方法:將while中的“item < eps”改為“item >= eps”。
(4)錯誤四:
繼續運行程序,樣例不變,發現結果如圖:

發現無輸出也未結束,進入無限循環,推想在浮點數的比較部分可能有誤,返回檢查之前的語句。
錯誤原因:輸入精度(eps)的scanf語句中,格式化輸入符號為應為對應double型的“%lf”,而不是“%f”,若不改正,會導致精確度有誤。
改正方法:將輸入單精度浮點數對應的“%f”改為對應雙精度浮點數的“%lf”,再將最后的printf語句中的‘%f’改為“%.6f”,保證輸出的為六位小數。
結果如圖:

符合實驗預期。改錯結束。
另外,在改錯過程中筆者遇到了另外一個問題,第一次修改while中的條件時,筆者推想浮點數比較的話,取等可能會出現誤差,因此並未去取等號,即‘item = eps’,導致結果如下:

可以發現,與樣例所給的數據相差了0.0001,使筆者十分費解,后來通過與老師交流與反復檢查,發現問題正是出在這個“=”等號上,推想所給樣例‘1E-4’正是該等號所對應的邊界值,所以出現了這一預想外的誤差,所以在比較時,應視情況取等。
二:學習總結:
(1)關於while(1)以及for(;;):
首先關於這兩種無限循環的判斷方式。
while()的括號內的判斷方式實際上是判斷括號內的條件是否為真,只要括號內為任意非零數值,均判斷為真,其中可以是任意合法表達式,因此括號內輸入‘1’,即永遠為真,循環無限執行。
而對於for循環,其判斷依據是根據for(;;)的括號內的三個表達式來判斷真假,其中第一個表達式只執行一次,即給用於判斷的值賦初值,第二個表達式則是用來判斷真假,第三個表達式可以是任意合法表達式,通常情況下是輸入關於某變量的自增自減函數,來實現有限次循環,當括號內三個表達式為空時,即判斷條件為空,即條件永遠為真,因此循環無限次執行。
這兩種無限循環的使用方法基本沒有差別,因為判斷條件均永遠為真,所以可以實現一些特定功能,但若要保證其正常執行,最常用的方式是設置某個標記變量,同時配合選擇結構一起使用,當選擇結構中某條件為真時,可以通過break語句跳出循環,實現結束循環的目的。此時可以保證循環的正常執行。
但是應當注意break語句的用法,其本質功能是結束距離break最近的一層循環結構,所以當涉及到循環的嵌套時,break的不正確使用往往會造成一些意料之外的結果,下面給出一段代碼:
#include<stdio.h>
int main(void)
{
int i, j;
for ( i = 0; i < 10; i++ )
{
printf( "外部循環變量: i = %d\n", i );
for ( j = 0; j < 3; j++ )
{
printf( "內部循環變量: j = %d\n", j );
if ( i == 5 )
break; //原意是想在循環執行5次時,主循環體結束
}
}
printf( "循環結束: i = %d\n", i );
Theend: printf( "執行Theend: i = %d\n", i );
}
注意筆者加注釋的部分,使用了break語句,原意是只令主循環執行五次就結束,結果如圖:


但是從圖中i,j的變化過程來看,並未實現預期的效果,這就是剛剛提到的break的實質,因為該程序中存在循環的嵌套,即關於i的循環內還存在關於j的循環,所以當執行break語句時,僅僅結束了內層循環的該次循環。
於是我們修改程序,如下:
#include<stdio.h>
int main(void)
{
int i, j;
for ( i = 0; i < 10; i++ )
{
printf( "外部循環變量: i = %d\n", i );
for ( j = 0; j < 3; j++ )
{
printf( "內部循環變量: j = %d\n", j );
if ( i == 5 )
goto Theend; //此處改用goto
}
}
printf( "循環結束: i = %d\n", i );
Theend: printf( "執行Theend: i = %d\n", i );
}
結果如圖:

觀察最后一句並觀察i的值不難發現,當i的值為5時,整個循環結束,並輸出最后一跳printf語句,同時發現,本應輸出循環結束時i的值的語句消失,實際上這里就涉及到了goto語句,其用途是在程序中,強行將程序轉移到具有goto后相同標號的部分,,又稱為無條件轉移語句,因此觀察代碼,可以發現筆者的標號處於輸出最后一次i的值,即“循環結束”那一語句的后面,因此當i=5時,goto語句執行,因此循環結束,並且執行標號后的語句,最后程序結束。goto語句雖然在多循環嵌套時具有很大作用,但是原則上往往並不推薦使用,因為當涉及大量代碼的編制時,goto語句會使代碼的可讀性變差,且容易造成混亂,這一點需要尤為注意。
(2)關於三種循環:
①在循環次數已知時,應當優先考慮for循環,首先注意一類問題,即給定項數求某數列和或某項的值,這類問題往往會給定某一上限,即已知循環次數,例如循環結構1中的第1(求奇數分之一序列和),7(求交錯序列和)小題,這類問題只要得到適用性較強的通項公式,剩余的結構部分就較好處理。
對於另一類問題,例如統計型,在已知總數的情況下,處理一批數據,這類問題也可以用for循環解決,例如循環結構1中的2(統計學生成績均值以及及格人數),3(奇偶分家),4(到底是不是太胖了),6(統計學生成績)題,均屬於已知數目的問題,此時使用for循環較為簡單穩妥。
②在循環次數未知,但循環條件在進入循環時明確的情況下,應采用while循環,由於循環次數未知,但是已知某些條件,例如所求結果與另一變量間存在某種關系,或是已知某范圍的情況下,均可采用while循環,尤其是涉及到窮舉問題,求極限問題等,都優先考慮該種方法。例如循環結構2(猜數字游戲)中的第一題,即為給定精度,求和的問題,該問題的循環次數實際上為多少我們在不經過計算的情況下並不知道,但是我們知道精度與某項值的關系,因此可以在while中添加此條件,因而取得最佳的效果。
③循環次數未知,且循環條件在進入循環時未知,但在循環體中明確。
針對這種情況,優先采取的結構為do while結構,因為do while與while結構上最大的不同點在於while循環是進入循環前先判斷一次條件,因此循環可能一次都不執行,而對於一些循環條件在進入時就未知的情況,再使用此種方法顯然不太嚴謹,這就需要用到do while循環,因為do while循環的執行方式為先進入循環體執行一次循環體,之后再判斷循環條件是否成立,這也保證了能避免一些極端條件的邊界值問題,例如循環結構2中的第5(蠕蟲)問題,以及第三題(求奇數和),這類問題的條件需要再進入循環體后再執行判斷,因而選用do while結構較為穩妥。
但是相應的也要注意到,有些問題可以相互轉化,例如循環結構2中的第一題,首先給出do while形式的代碼:
#include<stdio.h>
#include<math.h>
int main(void)
{
int i = 1;
double sum = 0.0,eps = 0.0,x = 0.0,t = 1.0;
scanf("%lf",&eps);
do
{
x = t / (3 * i - 2);
sum += x;
t *= -1;
i ++;
}
while(fabs(x) > eps);
printf("sum = %.6f",sum);
return 0;
}
接着給出while結構的代碼:
#include<stdio.h>
#include<math.h>
int main(void)
{
int i = 1;
double sum = 0.0,eps = 0.0,x = 1.0,t = 1.0;
scanf("%lf",&eps);
while(fabs(x) > eps)
{
x = t / (3 * i - 2);
sum += x;
t *= -1;
i ++;
}
printf("sum = %.6f",sum);
return 0;
}
實際上就功能實現上來說兩種結構取得的效果完全相同,但是對於一些極端邊界條件,使用do while較為合理,就上面給出的while形式的代碼,由於筆者給x賦的初始值為1,因此當筆者輸入任意大於等於1的值,均會出現如下結果:

因此,若要使用此循環,應在循環前添加額外的判斷條件,因此較為繁瑣,故不采用。
(3)給定一批成績以-1為結束標志,計算平均成績。
①先給出while循環結構:
#include<stdio.h>
int main(void)
{
double sum = 0.0,average = 0.0,score = 0.0;
int i = 1;
scanf("%lf",&score);
while(score >= 0)
{
sum += score;
scanf("%lf",&score);
average = sum / i;
i++;
}
printf("average = %.2f",average);
}
②接着是do while結構:
#include<stdio.h>
int main(void)
{
double sum = 0.0,average = 0.0,score = 0.0;
int i = 1;
scanf("%lf",&score);
if(score < 0) //判斷用戶輸入的第一個值是否小於0,防止除零
{
average = 0.0;
}
else
{
do
{
sum += score;
scanf("%lf",&score);
average = sum / i;
i++;
}
while(score >= 0);
}
printf("average = %.2f",average);
}
③for循環:
#include<stdio.h>
int main(void)
{
double sum = 0.0,average = 0.0,score = 0.0;
int i = 1;
scanf("%lf",&score);
if(score < 0)
{
average = 0.0;
}
else
{
for(i = 1;score >= 0;i++)
{
sum += score;
scanf("%lf",&score);
average = sum / i;
}
}
printf("average = %.2f",average);
}
④最后是無限循環:
關於無限循環,實際上寫法有多種,在前文也提到了,主要運用的形式為for(;;)以及while(任意非零值),只要注意結束循環的條件,無論哪一種,均可以取得較好的效果。下面給出for(;;)的形式:
#include<stdio.h>
int main(void)
{
int score = 0,sum = 0,count = 0;
for(;;)
{
scanf("%d",&score);
if(score < 0)
{
break;
}
else
{
sum += score;
count++;
}
}
if(count == 0) //防止用戶第一個數輸入小於0的值,防止除0
{
count++;
}
printf("average = %.1f\n",(double)sum / count);
return 0;
}
對於另一種形式,即while(任意非零值),只需將for(;;)改為while(任意非零值)即可,不多贅述。
2)關於本題,筆者認為使用無限循環形式較好,因為對於這類實際問題,學生的實際數量並不是某一定值,而是有一定的浮動范圍,因此使用for或while循環實現雖然並非不可能,但是相對無限循環有些舍近求遠,而對於無限循環,只要指定一個某一特值或標記變量,即可實現隨時退出的目的,靈活性更強。
(4)運行給定代碼並分析:
1)首先運行第一段代碼:
結果如下:

輸入到2時循環結束。
2)接着運行第二段代碼:

程序循環10次后結束,輸出25.
3)原因分析:
首先分析第一段代碼:
①定義三個整型變量n,s,i;
②給s賦初值0;
③i = 1,i <= 10判斷為真,進入循環。
④,輸入n = 1,n % 2 == 0為假。
⑤將n的值加和給s,s = 1。
⑥i = 2,循環判斷為真。
⑦輸入n,此時輸入n = 2,,判斷n % 2 == 0為真,執行break語句,跳出循環。
⑧輸出s的值,此時s = 1;程序結束。
接着分析第二段代碼:
①定義三個整型變量n,s,i;
②給s賦初值0;
③i = 1,i <= 10判斷為真,進入循環。
④,輸入n = 1,n % 2 == 0為假。
⑤將n的值加和給s,s = 1。
⑥i = 2,循環判斷為真。
⑦輸入n,此時輸入n = 2,,判斷n % 2 == 0為真,執行continue語句,但不執行s的加和語句,進入下一次循環。
⑧此時i = 3,循環條件為真,進入循環。
⑨輸入n,n % 2 == 0為假,將s加和。
...................................
⑩1 = 11,循環條件為假,循環結束,此時s = 25,程序結束。
對於這兩種情況的不同,根本原因在break與continue語句的不同,break語句執行時,程序會強制結束距離break語句最近的循環,而continue語句則是結束本次循環,並進入下一次循環的判斷。
結合程序分析,對於程序1,當用戶輸入偶數時,if語句判斷為真,執行break語句,循環結束並輸出s的值,而對於程序2,當用戶輸入奇數時,continue語句不執行,並將該奇數加和給s,當用戶輸入偶數時,if語句判斷為真,執行continue語句,但不執行下面的s的加和語句,即只將奇數加和給s。
因此單論功能的實現而言,程序的本意應為用戶輸入十個數值,並計算其中奇數的總和。
三:實驗總結:
1:求給定精度的交錯數列和:
1)流程圖:



2)源代碼如下:
#include<stdio.h>
#include<math.h>
int main(void)
{
int i = 1;
double sum = 0.0,eps = 0.0,x = 0.0,t = 1.0; //定義變量
scanf("%lf",&eps); //輸入精度
do
{
x = t / (3 * i - 2); //計算x的值
sum += x;
t *= -1; //循環改編t的符號
i ++;
}
while(fabs(x) > eps); //循環條件
printf("sum = %.6f",sum); //輸出sun的值
return 0;
}
3)實驗分析:
本題主要使用do while循環結構,通過計算一次最后一項值的數值進入循環判斷,最終當循環條件為假時結束循環。
先給出出現錯誤的代碼:
#include<stdio.h>
#include<math.h>
int main(void)
{
int i = 1;
double sum = 0.0,eps = 0.0,x = 1.0,t = 1.0;
scanf("%lf",&eps);
while(fabs(x) > eps)
{
x = t / (3 * i - 2);
sum += x;
t *= -1;
i ++;
}
printf("sum = %.6f",sum);
return 0;
}
錯誤1:當筆者第一次寫完時,輸入樣例數據均未出現錯誤,但當筆者輸入1時,卻出現如下結果:

輸出為0,不符合預期與實際,推想為循環判斷部分的邊界值有誤。
錯誤原因:x的初始值有誤,導致當輸入大於等於x的初值的數值時,循環不執行,而實際上精度無論為多大,x的首項均為1,sum也為1,即循環至少執行一次。
改正方法:將while循環改為do while循環。
4)本題提交列表:由於部分測試在編譯器中完成,因此結果可能不符合實際情況。

2:猜數游戲:
1)流程圖:





2)源代碼:
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
int normal = 0,answer = 0,i = 0,N = 0,count = 0;
scanf("%d%d",&normal,&N); //輸入正確的答案以及次數
for(i = 1;i <= N;i++)
{
scanf("%d",&answer); //輸出回答
if(answer < 0) //當輸入小於0的值時,游戲結束
{
break;
}
if(answer == normal) //此if后均為答案正確的情況
{
if(i == 1)
{
printf("Bingo!");
}
else if(i <= 3)
{
printf("Lucky You!");
}
else if(i <= N)
{
printf("Good Guess!");
}
exit(0); //正確時輸出對應語句后直接結束程序
}
else if(answer > normal) //當回答錯誤但處於規定次數內時對用戶輸入數值進行比較
{
printf("Too big\n");
}
else
{
printf("Too small\n");
}
}
printf("Game Over");
return 0;
}
3)實驗分析:
本題主要使用for循環結構,實際上難度並不大,但是由於包含情況分支較多,因此情況的遺漏較為常見,應提起注意:
錯誤一:筆者編譯完畢后,輸入樣例數據“58 4 70 50 56 58”發現雖然輸出了“Good Guess”,但也輸出了不應輸出的結果,如圖:

推想是循環結束后,執行了循環外的不希望執行的語句,返回檢查。
錯誤原因:筆者在對應三種正確情形的條件后,未加上使程序結束的語句,即遺漏‘exit(0)’,如圖:

改正方法:將“exit(0)”補全。
本題提交列表:

3:求奇數和:
1)流程圖:



2)源代碼如下:
#include<stdio.h>
int main(void)
{
int number = 0,sum = 0;
do
{
scanf("%d",&number);
if(number % 2 != 0 && number > 0)
{
sum += number;
}
}
while(number > 0);
printf("%d",sum);
return 0;
}
3)實驗分析:
本題主要使用do while結構以及if條件判斷,難度不高。
所以...真的沒有錯誤...
4)提交列表:

四:博客互評:
1:侯冠達:
http://www.cnblogs.com/HGD980425/p/7837319.html
答案完善,步驟很明確,就是字體需要修改一下,就好看了,整體很清晰明了,沒毛病!!
2:許天笑:
http://www.cnblogs.com/snxtx/p/7824427.html
你的作業做的我覺得很棒,而且大部分的程序你都做了注釋,這一點值得我的學習,希望曉曉同學能在學習上多幫幫我
ps:(你就睡我旁邊的床,倒是來問我...)
3:郭玉霖:
http://www.cnblogs.com/HBQ521/p/7826030.html
作業寫得我感覺很棒,值得學習。
4:劉愷煊:
http://www.cnblogs.com/liukaixuan/p/7855946.html
apoter流程圖太復雜了,我是真做不出來。而且大佬在學習總結環節還把程序每次運行的變量值用一個輸出語句輸出出來來調試程序,太專業了。
(也就那么回事吧...多做做就行)
5:劉暢:
http://www.cnblogs.com/LLIU/p/7846322.html
寫的很快很好,多向你學習!
(來一起學習)
6:胡展業:
http://www.cnblogs.com/SYDneyHZY/p/7824429.html
內容很充實,各方面都很好,學習學習。
7:李志偉:
http://www.cnblogs.com/666888i/p/7858064.html
內容很具體,解釋的很詳細。
8:**(希望下次注明姓名)
http://www.cnblogs.com/myg123/p/7841714.html
每次的博客寫的都很詳盡,而且提交作業的速度難以想象,整體較好,多多學習!
9:2614(同樣希望注明姓名)
http://www.cnblogs.com/Maria2614/p/7860328.html
作業每次都很認真很詳細,值得我學習
10:沐梔*(姓名......)
http://www.cnblogs.com/hbnydx/p/7858643.html
不僅完成了作業,還這么詳細,多補充了這么多知識,我要向你學習!
11:郭展旭:
http://www.cnblogs.com/1234569ss/
通過大量測試數據驗證邊界值減少錯誤,更好的保證了程序的正確。值得學習
