前兩周做了一個小作業,學生成績管理系統,第一周實現了錄入學生信息、刪除學生信息、顯示學生信息和按照學生平均成績排序的功能,總體來說比較順利,第二周只做了一件事就是讀txt文件中的學生信息,將txt文件中的學生信息讀到程序中插入到鏈表中,這一個看似簡單的工作,花費了一周的時間。
我程序中用到的txt文件中的數據是學生成績,其中有學生ID、學生姓名、成績等,在鏈表中分別是int型,字符串和float型,如下所示。
因為之前學習了I/O文件的操作,沒有學習基於流的I/O,所以一開始就是瞎撞,根本不知道怎么解決,把這些信息讀出來好像也沒有什么用,想的最多的就是程序怎么把這些文件中的信息分別讀出來,ID是ID、成績是成績、姓名是姓名,這就是一開始的思路。然后開始看書,上網搜索都沒有直接的信息,看了其他的程序都是用基於流的I/O操作,用fopen等函數來操作文件的。先學習了fopen函數和fread函數。
fopen(const char *path,cost char *mode)
頭文件: #include<stdio.h>
參數說明:
第一個參數*path表示要打開文件的路徑;
第二個參數*mode代表打開的方式,mode有以下幾種值:
r:只讀方式打開,文件必須存在
r+:可讀寫,必須存在
rb+:打開二進制文件,可以讀寫
rt+:打開文本文件,可讀寫
w:只寫,文件存在則文件長度清0,文件不存在則建立該文件
w+:可讀寫,文件存在則文件長度清0,文件不存在則建立該文件
a:附加方式打開只寫,不存在建立該文件,存在寫入的數據加到文件尾,EOF符保留
a+:附加方式打開可讀寫,不存在建立該文件,存在寫入的數據加到文件尾,EOF符不保留
wb:打開二進制文件,只寫
wb+:打開或建立二進制文件,可讀寫
wt+:打開或建立文本文件,可讀寫
at+:打開文本文件,可讀寫,寫的數據加在文本末尾
ab+:打開二進制文件,可讀寫,寫的數據加在文件末尾
由mode字符可知,上述如r、w、a在其后都可以加一個b,表示以二進制形式打開文件。
返回值:文件打開成功返回一個指向該打開文件的指針(FILE結構);文件打開失敗,錯誤上存error code。
fread(void *restrict ptr, size_t size, size_t nobj, FILE *stream)
頭文件: #include <stdio.h>
參數說明:第一個參數ptr是讀出的數據存放的地址,也就是讀出來的數據都存放在ptr指向的地址;第二個參數size為單個元素的大小,即由指針寫入地址的數據大小,單位是字節;第三個參數nobj為元素個數,即要讀取的數據大小為size的元素個素;第4個參數stream是fopen函數的返回值,也就是打開文件的的指針。
返回值:函數成功,返回讀取的總數據元素個數,失敗返回錯誤號。
這兩個函數的一個應用實例是分別統計一篇英語文章中字母,空格和數字的多少。代碼如下:
#include <stdio.h> #include <stdlib.h> #define MAX 1024 #include <string.h> int main(void) { FILE *fp; char buf[MAX]; int n; char *p; int buf1[MAX]; int letter, number, blank; fp = fopen("stu.txt", "rb"); if(fp == NULL) { perror("fail to open"); exit(1); } letter = 0; number = 0; blank = 0; while((n = fread(buf, sizeof(char), MAX-1, fp)) > 0) { buf[n] = '\0'; p = buf; while(*p != '\0') { if(('a' <= *p && *p <= 'z') || ('A' <= *p && *p <= 'Z')) letter++; if(*p == ' ') blank++; if('0' <= *p && *p <= '9') number++; p++; } } if(n == -1) { perror("fail to read"); exit(1); } printf("the letteris : %d \nthe number is %d\nthe blank is %d\n", letter, number, blank); fclose(fp); return 0; }
上邊這一段代碼實現的是統計一篇英語文章中字母,空格和數字的多少,敲完這段代碼之后才明白txt文件中存儲的都是字符串,即使是數字存儲在txt文件中也是以字符串的形式來存儲的,因此,如果想要把txt文件中的數組信息讀出來就需要先讀出字符串,然后將字符串轉化成數字。到了這里思路才開始有一些明朗,接下來就是兩件事,把數據按字符串的形式讀出來,然后將字符串中的不同部分分別讀出來,插入鏈表。
把文件中字符串讀出來可以用fgets函數,一行一行的讀出來,然后將每一行進行轉換,按照上邊統計字符個數那樣,整數就是十位乘以10加上個位,但是后來發現我們存儲的學生成績是float形式的數據,不能用這種方法來讀出來,當時也想過降低要求,把學生成績都變成整型的就解決了這個問題,但是還是覺得既然題目這樣要求肯定有解決的辦法。現在一個主要的問題就是找到如何將字符串轉化成float型。
最終找到了一些函數,atoi函數將字符串轉換成整數、atof函數將字符串轉換成浮點數。
atoi (const char * str) 函數
參數說明:參數str是要轉換的字符串,也可以是字符數組。
返回值:成功返回轉換的整型數字,失敗返回0;
函數說明:atoi() 函數會掃描參數 str 字符串,跳過前面的空白字符(例如空格,tab縮進等),直到遇上數字或正負符號才開始做轉換,而再遇到非數字或字符串結束時('\0')才結束轉換,並將結果返回。
同理的atof函數可以將字符串轉換成浮點數。這兩個函數解決了如何將txt文件中讀出來的字符串轉化成浮點數的問題,解決了問題的一半,下一個目標就是如何將讀出來的字符串分段,將學生ID的字符串、學生姓名的字符串和學生成績的字符串分開,分別進行轉換就可以了。
這個時候再用fgets函數不是很方便去解決這個問題了,這個時候又找到了一個函數fscanf函數。
fscanf函數可以從文本中讀一個字符串到指定的數組中。從下面這段代碼中可以更直觀的了解fscanf這個函數。
#include <stdio.h> #include <string.h> struct node{ char a[20]; char b[20]; char c[20]; char d[20]; }; int main() { FILE *fp; struct node buf; fp=fopen("1.txt", "r"); fscanf(fp,"%[^ ] %[^ ] %[^ ] %s",buf.a,buf.b,buf.c,buf.d); printf("[%s][%s][%s][%s]\n",buf.a,buf.b,buf.c,buf.d); return 0 ; }
我們知道scanf的用法,非常嚴格能夠讀進去,能夠讀進去空格。fscanf是遇到空格的時候或者“,”的時候停止前邊的放在一個字符串中。我們的文件中是用空格來進行間隔的,這樣就好理解上邊代碼中的那一句話了 fscanf(fp,"%[^ ] %[^ ] %[^ ] %s",buf.a,buf.b,buf.c,buf.d); 這里%[ ],是掃描集的意思,%[^ ]其中^的意思就是當fscanf一個一個字符從文件讀上來的時候如果遇到“ ”空格就會停下來,就會把前面讀取的字符存到buf中,以此類推就可以分別得到4個字符串,這樣就完成了將文件中不同的信息分別存放在不同的字符串的需求。
也就是用fopen打開文件,用fscanf函數分段讀取出學生信息,用atof函數和atoi函數將字符串進行轉換,這樣就能得到符合插入鏈表要求的學生信息,然后再創建一個鏈表結點把這些信息寫進去,加上循環就能夠得到一個學生成績的鏈表。
下面函數演示了如何從文件中將信息讀出來,然后將信息寫入結點,但是沒有創建鏈表。下面的程序中要說明的一點是,在實際操作中發現用fscanf函數進行循環的時候,只能讀出來第一行,換行之后讀不出來了,在%[^ ]前加上換行符號“\n”之后不能讀出來第一個字符串,直接從第二個字符串開始讀,因此想到了在第一個字符串前加空格的辦法,來使得程序換行后從第一個字符串開始讀,文件中信息的存儲格式如一開始文章的圖片所示。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #define MAX 10 typedef struct student * Student; static Student head; static int ID_count; struct student { Student next; int ID; char name[MAX]; float chgrade; float mathgrade; float avegrade; }; int main(void) { FILE *fp; int i; int j = 0; int n = 1 ; int ID; char name[MAX]; float chgrade; float mathgrade; float avegrade; char a[20]; char b[20]; char c[20]; char d[20]; char e[20]; char f[20]; fp = fopen("1.txt", "r"); if(fp == NULL) { printf("fail to fopen\n"); exit(1); } while(n != -1) { n = fscanf(fp, " %[^ ] %[^ ] %[^ ] %[^ ] %[^ ]", a, b, c, d, e); ID = atoi(a); for(i = 0; i < 10; i++) { name[i] = b[i]; } chgrade = atof(c); mathgrade = atof(d); avegrade = atof(e); printf("%d\n%s\n%.2f\n%.2f\n%.2f\n", ID, name, chgrade, mathgrade, avegrade); printf("n = %d\n", n); j++; } printf("%d\n", j); return 0; }