C語言程序設計第五次作業
標簽(空格分隔): C語言
新的一周又到來了,而隨着新的一周的到來而到來的當然還有新的作業。
(一)改錯題
題目要求:輸入兩個整數lower和upper,輸出一張華氏攝氏溫度轉換表,華氏溫度的取值范圍是{lower,upper},每次增加2℉。計算公式如下:
c = 5×(f-32)/9
含錯誤源代碼如下:
#include <stdio.h>
int main(void)
{
int fahr , lower, upper; /* fahr表示華氏度 */
double celsius; /* celsius表示攝氏度 */
printf("Enter lower:");
scanf("%d",&lower);
printf("Enter upper:");
scanf("%d",&upper);
printf("fahr celsius\n"); /* 顯示表頭 */
/* 溫度轉換 */
for (fahr = lower , fahr <= upper, fahr ++) ;
celsius = 5 /9 * (fahr - 32.0);
printf("%3.0f %6.1f\n", fahr, celsius);
return 0;
}
將源代碼復制至IDE中運行報錯,報錯信息如下:
--------------------配置: mingw5 - CUI Debug, 編譯器類型: MinGW--------------------
檢查文件依賴性...
正在編譯 D:\C語言\預習\main.c...
[Error] D:\C語言\預習\main.c:14: error: syntax error before ')' token(定位於第14行)
構建中止 main: 1 個錯誤, 0 個警告
錯誤信息翻譯:“在')'標記之前的語法錯誤”我們找到第14行。檢查for語句,括號中的表達式沒有用分號分開而是用逗號分開。這是一個很明顯的錯誤,在for語句的括號中可以沒有表達式,但一定要有兩個分號。
就如以下例子,for可以沒有參數,但一定要有分號:
#include <stdio.h>
#include <ctype.h>
int main(void)
{
char answer = 0;
for(;;)
{
printf("你還想輸入更多的嗎?用Y和N來表示:\n");
scanf("%c",&answer);
if(tolower(answer) == 'n')
break;
}
}
在for語句中使用break即可結束循環,以此來代替判斷語句。
修改意見:將for語句括號中的,
改為;
。即將第14行修改如下:
for (fahr = lower ; fahr <= upper; fahr ++);
將上面問題修改后再次嘗試運行,程序成功編譯,按照輸入輸出樣例輸入后的結果如下:
輸入32、35:
輸入40、30:
很明顯,輸出結果不符合輸入輸出樣例,判斷原因可能出在輸入、運算、輸出三個步驟中的一個。依次檢查源代的這三個步驟,發現第15行的celsius = 5 /9 * (fahr - 32.0);
的運算不符合題目給出的c = 5×(f-32)/9。
在第16行的輸出語句中fahr用的是%f,但fahr變量卻聲明為int類型。現在有兩種修改方法:1.將%f改為整數型輸出%d。2.將fahr聲明為double類型。
根據輸入輸出案例可知fahr變量又需要含有小數位。
修改意見:1.將celsius = 5 /9 * (fahr - 32.0);
修改為celsius = 5.0 * (fahr-32.0) / 9.0
2.將fahr聲明為double類型而不是int類型。
將上述問題修改后再次運行的結果如下:
輸入32、35:
輸入40、30:
輸出結果明顯不符合要求,我們已經檢查過輸入、賦值、輸出語句確認沒有問題,我們再檢查最新學的for語句是否有問題。
檢查后發現for后沒有花括號,2個語句雖然縮進了但是編譯器不會識別。每次增加的華氏溫度也有問題fahr++
每次只會增加一度,而題目要求每次增加2度。並且for語句后不能有分號。
並且發現之前檢查輸出語句是沒有檢查出來的錯誤:fahr在輸出時用的%f而數據也有使用double類型的需要,但是聲明時卻將fahr變量聲明成了int類型。
修改意見:1.將第15、16行用花括號括起來使其成為一個代碼塊。2.將for語句后的分號刪除。
將修改后的源代碼運行並運行輸入輸出案例的結果如下:
輸入32、35:
輸入40、30:
輸出結果符合題目要求,程序完成修改后的源代碼如下:
#include <stdio.h>
int main(void)
{
int lower = 0, upper = 0; /* fahr表示華氏度 */
double celsius = 0.0,fahr = 0.0; /* celsius表示攝氏度 */
printf("Enter lower:");
scanf("%d",&lower);
printf("Enter upper:");
scanf("%d",&upper);
printf("fahr celsius\n"); /* 顯示表頭 */
/* 溫度轉換 */
for (fahr = lower ; fahr <= upper; fahr = fahr + 2)
{
celsius = 5.0 * (fahr-32.0) / 9.0;
printf("%3.0f %6.1f\n", fahr, celsius);
}
return 0;
}
我認為這個程序的修改其實到目前為止還沒有完善,就如第二組輸入輸出案例是最小值大於最大值,這時這個程序的輸出為空,但我認為更應該輸出錯誤信息,我將這一點擅自更改后的源代碼如下:
#include <stdio.h>
int main(void)
{
int lower = 0, upper = 0; /* fahr表示華氏度 */
double celsius = 0.0,fahr = 0.0; /* celsius表示攝氏度 */
printf("Enter lower:");
scanf("%d",&lower);
printf("Enter upper:");
scanf("%d",&upper);
//檢驗最小值一定小於最大值
if(lower >= upper)
{
printf("ERROR:輸入的最小值大於最大值。\n");
}
/* 溫度轉換 */
else
{
printf("fahr celsius\n"); /* 顯示表頭 */
for (fahr = lower ; fahr <= upper; fahr = fahr + 2)
{
celsius = 5.0 * (fahr-32.0) / 9.0;
printf("%3.0f %6.1f\n", fahr, celsius);
}
}
return 0;
}
測試輸入輸出案例結果如下:
輸入32、35:
輸入40、30:
(二)學習總結
1. 代碼分析:
分析以下源代碼:
#include <stdio.h>
int main(void)
{
int i = 0,num = 0,odd = 0,even = 0;
//初始化循環需要用到的變量。
odd = 0;
even = 0;
for(i = 1; i <= 10;i++) //循環10次
{
scanf("%d",&num);
if(num % 2 == 0)
{
even++;
}
else
{
odd++;
}
}
//循環完成,輸出結果。
printf("%d %d",even,odd);
return 0;
}
我們知道C語言的執行一般是按照行數來執行的。程序已經載入了頭文件,並且使用了不返回數據的main函數。然后程序會因為聲明語句為i、num、odd、even這幾個變量分配與int類型相適宜的內存,並且給這幾個變量賦初值。
注釋會被忽略,第7、8行是賦值語句,作用是給odd和even這兩個變量賦初值0。這是必須的,否則循環語句將不能正常運行。
然后就是我們的重頭戲for循環語句,for循環的一般形式如下:
for(starting_condition; continuation_condition ; action_per_iteration)
loop_statement;
next_statement;
執行順序為:先執行starting_condition語句。然后判斷continuation_condition語句。如果為真,執行loop_statement,為假就執行next_statement語句。當然,在每次loop_statement語句執行后都要執行一遍action_per_iteration語句。
流程圖如下:
詳細解釋如下:
根據以上結論,我們的這個for循環第一步是將i初始化為1。然后判斷i是否小於等於10,結果為true,執行循環體。在執行完循環體后再執行i++
,因為是單獨的i++所以也可以使用++i來代替。當判斷語句結果為假時退出循環,執行循環后的第一條語句。
而我們的循環體運行的每個步驟如此:
此迭代i的值 | 表達式2判斷結果 | 表達式3造成的結果 |
---|---|---|
i=1 | true | i=1+1=2 |
i=2 | true | i=2+1=3 |
i=3 | true | i=3+1=4 |
i=4 | true | i=4+1=5 |
i=5 | true | i=5+1=6 |
i=6 | true | i=6+1=7 |
i=7 | true | i=7+1=8 |
i=8 | true | i=8+1=9 |
i=9 | true | i=9+1=10 |
i=10 | true | i=10+1=11 |
i=11 | false | 循環結束,不再執行i++ |
此循環循環10次,沒此讓用戶輸入一個數,在for循環中還嵌套了一個if語句來判斷用戶輸入的數是奇數還是偶數,當時奇數時執行odd++
當為偶數時執行even++
而程序的編寫者用了num%2==0
來判斷用戶輸入的數據是奇數還是偶數,當判斷結果為真時數為偶數,反之則為奇數。
在結束循環后,程序執行循環語句后的第一個語句,即printf("%d %d",even,odd);
(注釋已忽略)和return 0;
即輸出結果(先偶再奇)並結束程序。在return 0;后,這個程序也就徹底的結束了。
最后,程序的運行順序為:
- 載入頭文件
stdio.h
。- 構建main函數。
- 聲明i、num、odd、even為整數型變量。
- 初始化even和odd為0.
- 進行for循環,循環中的每一步如上列表所示。
- 輸出結果,奇數和偶數的數量。
- 返回結果,結束程序。
此程序目的是判斷輸入的十組數據中奇數和偶數的個數,如果想要判斷不同數量的數據可以將for語句中第二個表達式更改或者讓用戶輸入一個數,當i<=此數據時運行循環。程序測試結果如下:
輸入1、3、5、7、9、11、13、15、17、19:
輸入2、4、6、8、10、12、14、16、18、20:
輸入1、2、3、4、5、6、7、8、9、10:
2.增量運算符
- 運行程序一(源代碼如下)
#include <stdio.h>
int main(void)
{
int i = 0,j = 0;
//給i、j賦值。
i = 2;
j = 2;
//使用i++和++j。
i++;
++j;
printf("i = %d\n",i);
printf("j = %d\n",j);
return 0;
}
運行結果如下:
我們可以從運行結果看出i++和++j的結果相同,都是將其加一。而增量運算符在單獨運用時前綴和后綴並沒有明顯的區別,但是前綴和后綴的區別是非常的大的,在寫代碼時如果混淆他們兩個會造成數據的運算錯誤。而詳細區分他們兩個的方法因為格式要求放在了下一題。
- 前綴和后綴的區別
題目給出的源代碼如下:
#include <stdio.h>
int main(void)
{
int i = 0,j = 0;
//給i和j統一賦值為2.
i = 2;
j = 2;
//輸出,i為后綴形式,j為前綴形式。
printf("%d\n",i++);
printf("%d\n",++j);
return 0;
}
運行結果如下:
我們看到了輸出的結果產生了差別,在輸出語句中i++的值為2。而++j卻是3,原因是什么呢?
外部資料詳細的解釋了前綴和后綴的區別和其區別產生的原因。i++輸出為2的原因是因為后綴形式是先使用變量的值再給變量加一,下面我添加一個新的語句來證明此說法:
#include <stdio.h>
int main(void)
{
int i = 0,j = 0;
//給i和j統一賦值為2.
i = 2;
j = 2;
//輸出,i為后綴形式,j為前綴形式。
printf("%d\n",i++);
printf("%d\n",++j);
printf("i = %d,j = %d",i,j);
return 0;
}
輸出結果為:
可以看見在運行上個輸出語句后i和j變量的值都為3。可以證明后綴形式的增量(減量)運算符會讓程序先使用變量的值后再給變量加1。
而前綴運算符輸出的直接就是變量增加后的值。
例如,x += ++i
其實可以拆分成:++i; x += i;
而x += i++
可以拆分成x += i; i++
所以這兩個看起來十分相似的兩個表達式其實最后的到的結果完全不一樣,我們今后如果忽略這些編譯器不會提示錯誤信息,但輸出的結果卻是錯誤的,很容易造成難以檢查出來的bug。
3.花括號的使用和縮進的使用
我們在學習C語言的過程中,老師不僅教給了我們基本的語法,還教給了我們比較標准的格式。我們要養成使用正確標准的格式來寫代碼。這樣,以后我們不僅寫代碼會更加的思路清晰,還會讓我們檢查錯誤時更加的容易。
以下的幾個樣例給我們展示了許多錯誤的習慣,也讓我們能夠更容易的理解怎樣的格式更加符合規范。
(1.)
#include <stdio.h>
int main(void)
{
int i = 0;
for(i = 1;i <= 5;i++)
printf("*");
printf("\n");
return 0;
}
運行結果如下:
(2.)
#include <stdio.h>
int main(void)
{
int i = 0;
for(i = 1;i <= 5;i++)
printf("*");
printf("\n");
return 0;
}
運行結果如下:
(3.)
#include <stdio.h>
int main(void)
{
int i = 0;
for(i = 1;i <= 5;i++)
{
printf("*");
printf("\n");
}
return 0;
}
運行結果如下:
(4.)
#include <stdio.h>
int main(void)
{
int i =0;
for(i = 1;i <= 5;i++)
{
printf("*");
}
printf("\n");
return 0;
}
運行結果如下:
顯而易見的,除了第三個程序外,其他的三個程序的輸出結果都相同,都是重復輸出了5個“ * ”,而第三個程序卻帶上了換行符從而變成了5行“ * ”。
我們知道在c語言編程時,空位字符不會被識別,即循環體的認定與縮格形式無關。我們編程時強調格式主要目的是為了讓我們的思路更加清晰和在今后查錯時更容易查找。而根據以上所述,第一第二個程序在編譯時是完全一樣的。
第一和第二個程序完全形同的原因我們可以比較容易的看出,一個制表符並不能改變編譯時的順序。可第四個源程序和第一及第二個源程序在看起來卻因為一對花括號看起來並不是那么的一樣了,這也可以從側面印證我們在寫代碼時標准的格式的重要性了。而原因就是因為for語句和if語句有一個相似的點,它只能和for后的第一個分號前的語句結合在一起。而第二個源程序卻將兩個語句縮進在了一起而沒有將它們用花括號把它們統一為一個代碼塊,很明顯雖然編寫者希望兩個語句在一個循環中運行,即目的為運行成第二個源程序所輸出的結果卻輸出了和第一個源代碼相同的結果。
當循環語句后有花括號時,花括號中的語句會成為一個代碼塊,一個代碼塊會被認定為一個語句而跟着它上面的循環語句。所以說:“{}”在程序中起作用,對結果產生很大的影響。
綜上所述,我們在判斷循環體時,當循環語句后沒有花括號時,循環體只是循環語句后第一個“;”前的語句,而當循環語句后有花括號時,花括號中所包含的一切合法語句都是循環體,包括另一個循環,這也是嵌套的基礎。
第一及第四個源程序都是正確的,符合了編寫者的意圖,猜測輸出結果符合它們的預期。但第四個的代碼格式卻更加的規范,更加的易懂。
第三個源程序很規范且正確的實現了編寫者所希望達到的目的。我今后寫程序時也會爭取做到為循環體加括號,哪怕這個循環體只有一個語句。
4.其他內容總結
我們的學習進度終於在開學兩個多月后到達了循環結構這一最初的難點,我認為循環結構相較於我們之前所學習的其他內容,對邏輯的考察變得更多了,在寫代碼時耗費的時間也比之前的內容更多。而在寫代碼時需要注意到的點也變得更多了,下面我將舉出幾個我所認為的需要注意的點。
for循環一般用於計算循環的次數,在該循環中,控制變量的值再每次迭代時遞增或遞減指定的值,直到到達某個最終值為止。
while循環只要給定的條件為true就繼續執行。如果循環條件一開始就是false,循環語句塊就不執行。
do-while循環類似於while循環,但其循環條件在循環語句塊執行后檢查。因此循環語句塊至少會執行一次。
- 在for循環的括號中應該用
;
來分隔,而不是,
。例如,我們這次作業的改錯題中,源程序就誤將,
當做;
從而產生了編譯器不報錯但會讓輸出結果錯誤的bug。我們當然不希望這種難以在今后檢查出來的錯誤存在。 - 在循環中使用的一些變量要在循環語句之前進行聲明和初始化,例如:
#include <stdio.h>
int main(void)
{
int i = 0,num = 0,odd = 0,even = 0;
//初始化循環需要用到的變量。
odd = 0;
even = 0;
for(i = 1; i <= 10;i++) //循環10次
{
scanf("%d",&num);
if(num % 2 == 0)
{
even++;
}
else
{
odd++;
}
}
//循環完成,輸出結果。
printf("%d %d",even,odd);
return 0;
}
的第7、8行就是初始化語句,如果在變量聲明時和循環前都不進行初始化,那么運算的結果將會十分的出人意料。下面我將上述程序的初始化語句刪除得到:
#include <stdio.h>
int main(void)
{
int i,num,odd,even;
//初始化循環需要用到的變量。
for(i = 1; i <= 10;i++) //循環10次
{
scanf("%d",&num);
if(num % 2 == 0)
{
even++;
}
else
{
odd++;
}
}
//循環完成,輸出結果。
printf("%d %d",even,odd);
return 0;
}
運行結果如下:可以看到,運行結果很明顯的不符合題目要求。原因我猜測是因為程序自動的將一個垃圾值賦值給了變量,而計算的正確結果卻加在了這個垃圾值上。我們可以用一下的程序來驗證我所提出的猜測。
#include <stdio.h>
int main(void)
{
int i,num,odd,even;
int i_b,num_b,odd_b,even_b;
//嘗試將垃圾值儲存下來。
i_b = i;
num_b = num;
odd_b = odd;
even_b = even;
//初始化循環需要用到的變量。
for(i = 1; i <= 10;i++) //循環10次
{
scanf("%d",&num);
if(num % 2 == 0)
{
even++;
}
else
{
odd++;
}
}
//循環完成,輸出結果。
printf("%d %d",even-even_b,odd-odd_b);
return 0;
}
輸入同樣的數據輸出結果為:成功證明我的猜測正確。
- 在寫for循環中的判斷時,一定要清楚自己想要讓循環體執行幾次。想要實現可以調整循環體中語句的順序,如先
num += t
和先t = t+3
的結果會相差一次循環。我們可以在紙上先運行一下自己的程序順序來理清思路。
(三)實驗總結
1.求奇數分之一序列前N項和
本題要求編寫程序,計算序列 1 + 1/3 + 1/5 + ... 的前N項之和。
Raptor流程圖如下:
源代碼如下:
//1 求奇數分之一序列前N項和
#include <stdio.h>
int main(void)
{
int N = 0;
double sum = 0.0;
scanf("%d",&N);
unsigned int i = 1;
for(;i <= N;i++)
{sum += 1.0/(i*2-1);}
printf("sum = %f",sum);
return 0;
}
這道題是我們學習完for語句后的第一道作業題,雖然簡單,但我們因為是第一次運用for語句,可能還不能熟練的使用它。可能產生幾個問題:1.沒有初始化i的值使得程序隨機執行與結束。2.for后的括號中缺少或沒有分號,將分號誤用為逗號。3.沒有理清楚for循環的次數。
而避免產生這些錯誤的方法為:1.記住for中的第一個表達式初始化要在for循環中使用的變量,大部分是將其初始化為0或1。2.明確記住for的括號后必定有兩個分號。可以沒有表達式,但一定要有分號。3.在演算紙上將程序運行循環的每一步寫出來。(較多時中間可以省略。)
PTA提交列表如下:
3.奇偶分家
給定N個正整數,請統計奇數和偶數各有多少個?
Raptor流程圖如下:
我的流程圖拼接錯誤,在老鐵的提醒下修改為:
本題源代碼如下:
//3 奇偶分家
#include <stdio.h>
int main(void)
{
int N = 0,odd = 0,event = 0,number = 0;
scanf("%d",&N);
for(unsigned int i = 1;i <= N;i++)
{
scanf("%d",&number);
if((number % 2) == 0)
{
odd++;
}
else
{
event++;
}
}
printf("%d %d",event,odd);
return 0;
}
實驗分析:
這道題的重點在於循環中會嵌套判斷語句,以及在判斷語句后怎么處理數據。在這道題中我直接在for語句中聲明了變量i,但我這種聲明方式會使得i變量只能在循環中使用,在循環外無法使用。此外,需要注意的是0屬於偶數,所幸它與2求余的結果為零使得程序不需要再增加判斷語句。
本題PTA提交列表:
5.統計字符
本題要求編寫程序,輸入10個字符,統計其中英文字母、空格或回車、數字字符和其他字符的個數。
本題Raptor流程圖如下:
本題源代碼如下:
//5 統計字符
#include <stdio.h>
#include <ctype.h>
int main(void)
{
char x = ' ';
int letter = 0,blank = 0,digit = 0,other = 0;
unsigned int i = 1;
for(;i <= 10;i++)
{
scanf("%c",&x);
if(isalpha(x))
{letter++;}
else if(isspace(x))
{blank++;}
else if(isdigit(x))
{digit++;}
else
{other++;}
}
printf("letter = %d, blank = %d, digit = %d, other = %d",letter,blank,digit,other);
return 0;
}
實驗總結:
這道題既可以用ASCII碼值來判斷輸入的為哪種類型,也可以直接調用ctype.h
頭文件所定義的值。而我的源代碼所用的方法為后者,當使用前者時要注意大寫字母和小寫字母之間在ASCII碼中還隔有其他字符。
本題PTA提交列表如下:
7.求交錯序列前N項和
本題要求編寫程序,計算交錯序列 1-2/3+3/5-4/7+5/9-6/11+... 的前N項之和。
Raptor流程圖如下:
本題源代碼如下:
//7 求交錯序列前N項和
#include <stdio.h>
#include <math.h>
int main(void)
{
int N = 0;
double sum = 0.0;
scanf("%d",&N);
unsigned int i = 1;
for(;i <= N;i++)
{
sum += pow(-1.0,i+1) * (double)i/(i*2-1);
}
printf("%.3f",sum);
return 0;
}
實驗總結:
這道題的難點在於加時的符號問題,我們既可以從數學角度來解決問題,也可以用flag變量來解決問題。當使用flag變量時,需要注意的是flag在每一次循環中的正負值。
int flag = 1;
flag = -flag; //此語句在循環中的賦值語句后。
甚至還要注意flag變量的變號是在賦值語句之前還是之后。
本題PTA提交列表如下:
(四)博客互評
我對李曉曉同學博客的評論:
大佬你在修改錯誤時將錯誤都用紅筆划了出來。而且你將比較大的流程圖切割開再在網頁上拼接上使得圖片更容易看清。這些都是我沒有做到應該向你學習的。
可大佬從來不回我。
我對風離你在同學博客的評論:
大佬你的博客非常的符合規范,排版整齊。流程圖的圖片截了兩次使得我們更容易看清。但我認為你的實驗總結好像有一些顯得單薄。
我對陳魔同學的博客的評論:
大佬你將題目的要求敘述的更加詳細,對截圖圖片進行了處理,還對文中重點部分用格式突出顯示了出來。這些都讓我們更容易的閱讀,看大佬的博客很賞心悅目,心情輕松。
我對孤寂一粒沙同學的博客的評論:
大佬的改錯完全按照助教的要求分每一步的把錯誤原因和改進方法寫了出來,非常符合格式。而且大佬你的插入代碼非常多,使得證明顯得非常的專業和有力。
感謝早交的同學們。我的評論如有冒犯,請多多包涵。我也希望同學們評價我的博客是請盡量指出我的錯誤與不足,你的意見是我進步的動力。