任務和代碼:
學生成績統計:
每位同學的信息學號、姓名、C、高數、英語成績。定義一個學生成績的結構體數組,其中的數據成員包括學號(char num[13])、姓名(name)、三門課的成績(grade)、總分(score))
score.txt中是一些同學的學號、姓名、C++、高數、英語成績,利用前面定義的結構體數組,讀取文件score.txt中的數據,完成下面的應用:
- 從文件中讀出數據,存放到結構體數組中;
- 求出每名同學的總分(可以在讀入過程中“順便”計算);
- 按總分降序排序並輸出成績單;
- 按學號升序排序並輸出成績單;
- 輸出C語言不及格的同學姓名和C語言成績;
- 有30名同學可以獲得獎學金,規則是總分高者優先,有掛科不能得獎學金。請輸出可以得獎學金同學的名單。若符合得將的最後一名同學有重複(例如總分全爲S),則總分爲S的同學全得獎;
- 將程序用一個“菜單”組織起來,做成一個“學生成績管理系統”之類的應用程序;
- 所有功能在main()函數中實現,並用多文件組織完成。
//head.h
#ifndef HEAD_H_INCLUDED
#define HEAD_H_INCLUDED
typedef struct {
char num[13];
char name[10];
int c;
int math;
int english;
int score;
}Student;
Student stu[500];
int stuNum;
void readData();
int chooseInMenu();
void sort_grade(Student [],int);
void sort_num(Student [],int);
void output(Student [],int);
void outputExcellent(Student [],int);
void outputImpassC(Student [],int);
#endif // HEAD_H_INCLUDED
//main.c
#include <stdio.h>
#include <stdlib.h>
#include "head.h"
int stuNum=0;
int main(){
int iChoice,i;
readData();
Student stud[stuNum]; //因爲Student stu[]已經是一個全局變量了,故
for(i=0;i<stuNum;i++) //將按學號升序排序後的學生名單放入一個新定義結構體數組
stud[i]=stu[i];
printf("歡迎進入學生成績管理系統!\n");
do{
iChoice = chooseInMenu(); //從菜單中獲得功能代碼
switch(iChoice){
case 1:
sort_grade(stu,stuNum); //按總分排序並輸出成績單
output(stu,stuNum);
break;
case 2:
sort_num(stud,stuNum); //按學號排序並輸出成績單
output(stud,stuNum);
break;
case 3:
outputExcellent(stu,stuNum); //輸出獲得獎學金同學的名單
break;
case 4:
outputImpassC(stu,stuNum); //輸出C語言不及格的同學姓名和C語言成績
break;
case 0:
printf("結束. \n");
}
}while(iChoice);
return 0;
}
//chooseInMenu.c
#include <stdio.h>
#include <stdlib.h>
#include "head.h"
int chooseInMenu(){
int i;
while(1){
printf("1. 按總分降序並輸出成績單\n");
printf("2. 按學號升序並輸出成績單\n");
printf("3. 輸出獲得獎學金同學的名單\n");
printf("4. 輸出C語言不及格的同學姓名和C語言成績\n");
printf("0. 結束\n");
printf("請選擇(0-4):");
scanf("%d", &i);
if(i>=0&&i<=4)
break;
else
printf("請重新選擇\n");
}
return i;
}
//output.c
#include <stdio.h>
#include <stdlib.h>
#include "head.h"
void output(Student s[],int n){
printf("學號\t姓名\t總分\n");
int i;
for(i=0;i<n;i++)
printf("%s\t%s\t%d\n",s[i].num,s[i].name,s[i].score);
printf("\n");
}
//outputExcellent.c
#include <stdio.h>
#include <stdlib.h>
#include "head.h"
void outputExcellent(Student s[],int n){
FILE *fp;
if ((fp=fopen("scholarship.txt", "w"))==NULL){
printf("scholarship.txt cannot be opened!");
exit(0);
}
int i,j,m=0,k=0;
sort_grade(s,n);
for(i=0,j=0;i<n;i++){
if(s[i].c>60&&s[i].math>60&&s[i].english>60)
s[j++]=s[i];
if(j==30)
break;
}
for(j=30;i<n;i++){
if(s[i].c>60&&s[i].math>60&&s[i].english>60&&s[i].score==s[j-1].score&&s[i].name!=s[j-1].name)
s[j++]=s[i];
}
for(i=0;i<j;i++){
printf("%s\t",s[i].name);
m++;
if(m%5==0)
printf("\n");
}
printf("\n\n");
for(i=0;i<j;i++){
fprintf(fp,"%s\t",s[i].name);
k++;
if(k%5==0)
fprintf(fp,"\n");
}
}
//outputImpassC.c
#include <stdio.h>
#include <stdlib.h>
#include "head.h"
void outputImpassC(Student s[],int n){
FILE *fp;
if ((fp=fopen("impassC.txt", "w"))==NULL){
printf("impassC.txt cannot be opened!");
exit(0);
}
int i,m=0;
for(i=0;i<n;i++){
if(s[i].c<60){
printf("%s\t%d\t\t",s[i].name,s[i].c);
m++;
if(m%3==0)
printf("\n");
}
}
printf("\n\n");
for(i=0;i<n;i++){
if(s[i].c<60){
fprintf(fp,"%s\t%d\t\t",s[i].name,s[i].c);
m++;
if(m%3==0)
fprintf(fp,"\n");
}
}
}
//readData.c
#include <stdio.h>
#include <stdlib.h>
#include "head.h"
//extern int stuNum;
void readData(){
FILE *fp;
fp = fopen("score.txt","r");
if(fp==NULL){
printf("dictionary open error!\n");
exit(1);
}
fscanf(fp,"%s%s%d%d%d",stu[stuNum].num,stu[stuNum].name,&stu[stuNum].c,&stu[stuNum].math,&stu[stuNum].english);
while (!feof(fp)){
stu[stuNum].score=stu[stuNum].math+stu[stuNum].english+stu[stuNum].c;
++stuNum;
fscanf(fp,"%s%s%d%d%d",stu[stuNum].num,stu[stuNum].name,&stu[stuNum].c,&stu[stuNum].math,&stu[stuNum].english);
}
fclose(fp);
}
//sort_grade.c
#include <stdio.h>
#include <stdlib.h>
#include "head.h"
void sort_grade(Student s[],int n){
int i,j;
Student t;
for(i=0;i<n-1;i++)
for(j=0;j<n-i-1;j++)
if(s[j].score<s[j+1].score){
t=s[j];
s[j]=s[j+1];
s[j+1]=t;
}
FILE *fp;
if ((fp=fopen("roster1.txt", "w"))==NULL){
printf("roster1.txt cannot be opened!");
exit(0);
}
for(i=0; i<n; i++)
fprintf(fp,"%s\t%s\t%d\n",s[i].num,s[i].name,s[i].score);
fclose(fp);
}
//sort_num.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "head.h"
void sort_num(Student s[],int n){
int i,j;
Student t;
for(i=0;i<n-1;i++)
for(j=0;j<n-i-1;j++)
if(strcmp(s[j].num,s[j+1].num)==1){ //注意字符串的比較
t=s[j];
s[j]=s[j+1];
s[j+1]=t;
}
FILE *fp;
if ((fp=fopen("roster2.txt", "w"))==NULL){
printf("roster2.txt cannot be opened!");
exit(0);
}
for(i=0; i<n; i++)
fprintf(fp,"%s\t%s\t%d\n",s[i].num,s[i].name,s[i].score);
fclose(fp);
}
運行結果:
按成績排序的成績單、按學號排序的成績單、獎學金名單、C未通過的名單(含成績)
遇到的問題:
疑惑一:在readData.c中,原來的掃描語句是下面這樣的,結尾爲什麼會多出一個0來呢?
while (!feof(fp)){
fscanf(fp,"%s%s%d%d%d",stu[stuNum].num,stu[stuNum].name,&stu[stuNum].c,&stu[stuNum].math,&stu[stuNum].english);
stu[stuNum].score=stu[stuNum].math+stu[stuNum].english+stu[stuNum].c;
++stuNum;
}
對比正確的:
fscanf(fp,"%s%s%d%d%d",stu[stuNum].num,stu[stuNum].name,&stu[stuNum].c,&stu[stuNum].math,&stu[stuNum].english);
while (!feof(fp)){
stu[stuNum].score=stu[stuNum].math+stu[stuNum].english+stu[stuNum].c;
++stuNum;
fscanf(fp,"%s%s%d%d%d",stu[stuNum].num,stu[stuNum].name,&stu[stuNum].c,&stu[stuNum].math,&stu[stuNum].english);
}
疑惑二:在outputExcellent.c中,相同的程序一個打印到窗口,一個打印到文件中,爲什麼結果有點不同呢?
for(i=0;i<j;i++){
printf("%s\t",s[i].name);
m++;
if(m%5==0)
printf("\n");
}
printf("\n\n");
for(i=0;i<j;i++){
fprintf(fp,"%s\t",s[i].name);
m++;
if(m%5==0)
fprintf(fp,"\n");
}
疑惑三:原來的這個成績管理的程序放在一個源程序中能夠正常運行,在一個程序,多個文件處理的時候,爲什麼會編譯不通過呢?
stuNum是全局變量,在頭文件中原來的定義如下:
//head.h
#ifndef HEAD_H_INCLUDED
#define HEAD_H_INCLUDED
typedef struct {
char num[13];
char name[10];
int c;
int math;
int english;
int score;
}Student;
Student stu[500];
int stuNum=0;
void readData();
int chooseInMenu();
void sort_grade(Student [],int);
void sort_num(Student [],int);
void output(Student [],int);
void outputExcellent(Student [],int);
void outputImpassC(Student [],int);
#endif // HEAD_H_INCLUDED
對應的解決方案:
1)掃描文件,結尾爲什麼會多出一個0?
答:這個問題正是讀文件使用feof()多讀一次的問題,對於feof()這個函數, 它是先讀再判斷是否到文件尾,
也就是說在它之前一定要讀一次才能做出判斷。我們會這樣把它使用在循環中:
對於第一個代碼,當我文件指針位於最後一個數據末尾,fp還是爲真,所以繼續讀入,
兩個%s都讀入爲空,到了文件結尾就結束讀入。輸出0,是因爲多輸入了一行,數組時全局變量裏面默認的元素是0;
對於第二個代碼,將掃描語句放置在輸出語句之後,當讀到最後一個數據時,也進行了一次掃描,但不再進入循環體輸出了。
2)相同的程序一個打印到窗口,一個打印到文件中,爲什麼結果有點不同呢?
答:仔細看上面的代碼,是放在同一個源文件中的,在第二個格式輸出時,m作爲前一個遍歷的變量又作爲第二個遍歷的變量,
顯然存在問題。故,一些用於遍歷的變量,計數器,有時候使用要歸零。
3)這個成績管理的程序放在一個源程序中能夠正常運行,在一個程序,多個文件處理的時候,爲什麼會編譯不通過呢?
答:int stuNum=0;作爲全局變量在一個程序一個源文件中運行沒有問題,
但是在一個程序多個源文件中,頭文件是被包含在多個源程序中的,就不能在頭文件中直接給全局變量賦初值。
修改方式有以下來兩種:
修改一:
刪除頭文件裏被賦初值的全局變量,剩下的就是如何不在頭文件中仍能在多個源文件中定義全局變量,
答案就是在main函數所在源文件定義所需要的全局變量,在其他文件中用extern指定外部變量
修改二:
保留頭文件中的全局變量但不要賦初值,在main函數所在的源文件給該全局變量賦初值,
注意該變量不能放在main函數裏,否則就成了局部變量,使外部變量無效。
總結:
1)遇到while(!feof(fp))用fscanf掃描文件時,循環體內的掃描語句要放在輸出語句之後;更多
2)一些用於遍歷的變量,再次使用時要歸零;
3)多文件處理時不要在頭文件裏給全局變量賦初值。