《學(xué)生信息管理系統(tǒng)》案例設(shè)計(jì)過(guò)程
適用于2022級(jí)軟工同學(xué)
說(shuō)明:
該文檔針對(duì)《程序設(shè)計(jì)實(shí)踐》課程的大作業(yè)-****系統(tǒng)的設(shè)計(jì)與實(shí)現(xiàn)過(guò)程中思路的搭建以及出現(xiàn)的問(wèn)題進(jìn)行闡述
為了讓大家更好地理解整個(gè)系統(tǒng)的設(shè)計(jì)和開(kāi)發(fā)過(guò)程,這里擬定有層次地闡述,以“學(xué)生成績(jī)管理系統(tǒng)”為例,大家可以根據(jù)自己的情況去有選擇地學(xué)習(xí)。
注意:這里也是為啥我不建議有同學(xué)自選題目說(shuō)要做學(xué)生信息管理系統(tǒng)的原因,因?yàn)橄M隳軌蜃约邯?dú)立地完成一個(gè)相對(duì)獨(dú)立的系統(tǒng);
1 思路整理
基礎(chǔ)設(shè)計(jì):
問(wèn)題1:你設(shè)計(jì)的系統(tǒng),是否利用“文件”來(lái)存儲(chǔ)相關(guān)數(shù)據(jù)?
答:若不用,那系統(tǒng)就沒(méi)有辦法將數(shù)據(jù)存入文件中,只是在內(nèi)存中,演示的話,只能運(yùn)行程序,然后一條條地錄入數(shù)據(jù)并演示各種功能,當(dāng)關(guān)閉程序時(shí),你之前錄入的數(shù)據(jù),都沒(méi)有了,因?yàn)?,你沒(méi)有將相關(guān)的數(shù)據(jù)存入到文件,即磁盤中!我希望你能完成帶“文件”存儲(chǔ)功能的系統(tǒng),這樣你會(huì)對(duì)這學(xué)期的知識(shí)點(diǎn)更加全面地理解!
當(dāng)然,根據(jù)你自己的情況來(lái)最好!若你非不用,那就好好地在功能上多考慮一些,也不是不行!
問(wèn)題2:學(xué)生信息管理,你是否打算就是單純的學(xué)生信息管理,不涉及到成績(jī)管理以及課程信息管理等功能呢?還是說(shuō)都涉及到呢?
答:這里你有幾個(gè)方案選擇:
方案1:就做學(xué)生信息管理(比如學(xué)號(hào)、姓名、性別、籍貫、政治面貌、專業(yè)、所屬學(xué)院等等),用一個(gè)文件存儲(chǔ)上述信息,比如這個(gè)文件名為student即可。
方案2:同方案1類似,在上述信息的基礎(chǔ)上,多幾個(gè)成績(jī)信息,比如(比如學(xué)號(hào)、姓名、性別、籍貫、政治面貌、專業(yè)、所屬學(xué)院,語(yǔ)文成績(jī)、數(shù)學(xué)成績(jī)、外語(yǔ)成績(jī)),其實(shí)也算是學(xué)生信息管理系統(tǒng),也可以看成學(xué)生成績(jī)管理系統(tǒng),因?yàn)橐泊_實(shí)是管理里一部分成績(jī)的功能。但其不足之處在于:課程就是語(yǔ)文、數(shù)學(xué)和外語(yǔ),當(dāng)系統(tǒng)做好后,若想自由增加,就很難,必須修改修改很多相應(yīng)的代碼;當(dāng)然,也是用一個(gè)文件(文件名為student)就可以存儲(chǔ)上述信息。
其實(shí)方案和方案2基本上是一樣的,可以視為一回事呀!
方案3:其實(shí)這里就是我們?cè)谌蝿?wù)書(shū)中提到的擴(kuò)展功能啦,就除了單純的學(xué)生信息管理之外,也擴(kuò)展了選課功能以及課程信息管理。
這個(gè)選擇相對(duì)功能完善:即學(xué)生成績(jī)管理系統(tǒng),能夠管理學(xué)生信息+課程信息+課程對(duì)應(yīng)成績(jī) 三方面的數(shù)據(jù);其對(duì)應(yīng)的關(guān)系呢,圖示如下;(這個(gè)圖呢,實(shí)際上,是將來(lái)數(shù)據(jù)庫(kù)原理課程中的ER實(shí)體-關(guān)系圖)
注意:這里并非讓你學(xué)習(xí)這個(gè),而是讓你知道,這個(gè)ER圖,將來(lái)要轉(zhuǎn)換為3個(gè)數(shù)據(jù)庫(kù)的表,對(duì)應(yīng)于我們現(xiàn)在實(shí)現(xiàn)學(xué)生選課系統(tǒng)的三個(gè)存儲(chǔ)數(shù)據(jù)的文件-學(xué)生文件(存儲(chǔ)學(xué)生信息)、課程文件(存課程的相關(guān)信息)、選課文件(存學(xué)號(hào)、課程號(hào)、該課程成績(jī)等信息)
此時(shí):至少需要3個(gè)文件(例如student文件、course文件、score文件)來(lái)存儲(chǔ)上述信息呀!

問(wèn)題3:系統(tǒng)采用的數(shù)據(jù)結(jié)構(gòu)是什么?即打算用單鏈表來(lái)存儲(chǔ)并操作相關(guān)數(shù)據(jù)?還是利用上學(xué)期的結(jié)構(gòu)體數(shù)組來(lái)操作呢呢?或者是說(shuō):有些時(shí)候用單鏈表,到排序的時(shí)候,用鏈表實(shí)現(xiàn)起來(lái)不爽,再考慮用結(jié)構(gòu)體數(shù)組來(lái)實(shí)現(xiàn)?
答:這里我建議大家可以根據(jù)自己的能力進(jìn)行選擇,實(shí)際上,用我們這學(xué)期的鏈表最好,這樣可以為數(shù)據(jù)結(jié)構(gòu)課程打下基礎(chǔ),當(dāng)然,你也可以都用(即有單鏈表了,也用了一些結(jié)構(gòu)體數(shù)組),甚至各實(shí)現(xiàn)一個(gè)版本,那就更不錯(cuò),鍛煉了自己。
2 初步實(shí)現(xiàn)
這里,算是個(gè)引入,其實(shí)我們?cè)谡n堂上也這樣講了,但還是擔(dān)心有的孩子沒(méi)有聽(tīng)課,連最基本的啟動(dòng)都不會(huì)。所以,我先以一個(gè)沒(méi)有涉及文件功能+單鏈表的一個(gè)簡(jiǎn)單的學(xué)生成績(jī)管理系統(tǒng)為例(上課講過(guò)的,教材P283第11-10 例),一步一步帶著你嘗試完成一個(gè)框架!
那有同學(xué)會(huì)問(wèn),那沒(méi)有文件,后面加文件容易不?當(dāng)然容易,一步一步來(lái)吧啊!
2.1 搭建“學(xué)生信息管理系統(tǒng)”的框架
2.1.1 結(jié)構(gòu)體類型的定義
定義學(xué)生的結(jié)構(gòu)體類型(根據(jù)自己想要保存的學(xué)生那些信息來(lái)定義結(jié)構(gòu)體中的成員)
你要根據(jù)你自己實(shí)現(xiàn)系統(tǒng)的功能來(lái)定義該結(jié)構(gòu)體呀,當(dāng)然,你如果采用了方案3
struct stud_node{
? int num;
? char name[20];
? int score;
? struct stud_node *next;
};
因?yàn)槲覀兿M幸粋€(gè)類似于菜單的界面,這里直接采用例11-10的循環(huán)框架,為了讓大家學(xué)會(huì)逐步地?cái)U(kuò)大自己系統(tǒng)的功能,這里我盡可能地一步一步來(lái)闡述。
比如我想實(shí)現(xiàn)的系統(tǒng)功能模塊包括:(學(xué)生信息的錄入模塊、信息修改模塊、信息刪除模塊、信息瀏覽模塊)即增加,刪除,修改以及遍歷功能。注意這里并沒(méi)有涉及 ‘信息查詢模塊“,將來(lái)有賴于大家的進(jìn)一步實(shí)現(xiàn)呀!
你想一下:我們寫程序的時(shí)候,不可能一口氣將所有功能模塊(即對(duì)應(yīng)的函數(shù))都實(shí)現(xiàn)出來(lái),但是我們可以先進(jìn)行規(guī)劃呀?。ㄒ?guī)劃系統(tǒng)初步有哪些功能模塊、每個(gè)函數(shù)的名字、參數(shù)等)
2.1.2 功能模塊的劃分以及定義
當(dāng)你初步規(guī)劃功能模塊后,通常建議呢,用圖的形式展現(xiàn)出來(lái)!勝過(guò)千言萬(wàn)語(yǔ)!
問(wèn):那要是初步規(guī)劃后,后面還需要加,咋辦呢?很簡(jiǎn)單呀,再加上就好了呀!還得表?yè)P(yáng)呢!這就叫不斷迭代呀!
以例11-1為案例,假如我們當(dāng)初就想到了學(xué)生信息的錄入模塊、信息修改模塊、信息刪除模塊、信息瀏覽模塊;那就畫一個(gè)圖展示一下唄!

那接下來(lái),對(duì)上述功能模塊分別給出對(duì)應(yīng)的函數(shù)名以及參數(shù)定義吧
struct stud_node * Create_Stu_Doc(); ?/* 新建鏈表 */
struct stud_node * InsertDoc(struct stud_node * head, struct stud_node *stud); /* 插入 */
struct stud_node * updateDoc(struct stud_node * head, int num); /*修改功能 ? 這個(gè)例11-10中沒(méi)有*/
struct stud_node * DeleteDoc(struct stud_node * head, int num); ?/* 刪除 */
void Print_Stu_Doc(struct stud_node * head); ?/* 遍歷 */
當(dāng)然,這些名稱包括對(duì)應(yīng)參數(shù),都是可以根據(jù)功能來(lái)改變的!比如說(shuō),第三個(gè)功能,信息刪除功能的第二個(gè)參數(shù)是num,是表示當(dāng)你在主函數(shù)中輸入要?jiǎng)h除的學(xué)號(hào)時(shí),調(diào)用該DeleteDoc函數(shù),那我們?nèi)绻窍脒@樣修改:
情形1:老師,我不想在主函數(shù)中錄輸入要?jiǎng)h除的學(xué)號(hào),能不能在該函數(shù)的肚子里,讓用戶輸入要?jiǎng)h號(hào)的學(xué)號(hào)對(duì)應(yīng)的學(xué)生,行不行?當(dāng)然可以了,就把對(duì)應(yīng)的代碼移動(dòng)到該函數(shù)的肚子里,當(dāng)然,對(duì)應(yīng)的函數(shù)的參數(shù)也需要修改呀,比如改為struct stud_node * DeleteDoc(struct stud_node * head)
情形2:老師,我不想按照學(xué)號(hào)進(jìn)行刪除,我能否按照學(xué)生的姓名進(jìn)行刪除?當(dāng)然可以了,當(dāng)然,這樣有個(gè)缺點(diǎn)就是,學(xué)生的名字不能有重復(fù)的名字,要不然就得進(jìn)一步考慮萬(wàn)一有重名的時(shí)候咋辦?那如何修改函數(shù)的參數(shù)呢?
這些都是根據(jù)你自己定義的工作流程來(lái)決定的!
當(dāng)定義好這些函數(shù)的名字和參數(shù)之后,那這些函數(shù)還沒(méi)有寫出來(lái),咋辦?沒(méi)關(guān)系,可以寫成空函數(shù),即肚子里啥都不實(shí)現(xiàn),空起來(lái),可以寫上一條相應(yīng)的輸出語(yǔ)句,表明這個(gè)函數(shù)的功能就行,等后期一步一步地實(shí)現(xiàn)!
那來(lái)吧!
先把主函數(shù)寫上,觀察一下,一個(gè)主函數(shù)+4個(gè)用戶自定義函數(shù)
注意一下,我把主函數(shù)中的一些需要錄入輸入后才能調(diào)用對(duì)應(yīng)函數(shù)的代碼暫時(shí)注釋掉,為了更好地演示主函數(shù)中菜單調(diào)用每個(gè)函數(shù)的效果!
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct stud_node{
? ?int num;
? ?char name[20];
? ?int score;
? ?struct stud_node *next;
};
struct stud_node * Create_Stu_Doc(); ?/* 新建鏈表 */
struct stud_node * InsertDoc(struct stud_node * head, struct stud_node *stud); /* 插入 */
struct stud_node * updateDoc(struct stud_node * head, int num); /* 修改 */
struct stud_node * DeleteDoc(struct stud_node * head, int num); ?/* 刪除 */
void Print_Stu_Doc(struct stud_node * head); ?/* 遍歷 */
int main(void)
{
? ?struct stud_node *head, *p;
? ?int choice, num, score;
? ?char name[20];
? ?int size = sizeof(struct stud_node);
? ?do{
? ? ? ?printf("1:Create 2:Insert 3:update 4:delete 5:browse 0:Exit\n");
? ? ? ?scanf("%d", &choice);
? ? ? ?switch(choice){
? ? ? ? ? ?case 1:
? ? ? ? ? ? ? ?head = Create_Stu_Doc();
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case 2:
? ? ? ? ? ? ? ?/*
? ? ? ? ? ? ? ?printf("Input num,name and score:\n");
? ? ? ? ? ? ? ?scanf("%d%s%d", &num,name, &score);
? ? ? ? ? ? ? ?p = (struct stud_node *) malloc(size);
? ? ? ? ? ? ? ?p->num = num;
? ? ? ? ? ? ? ?strcpy(p->name, name);
? ? ? ? ? ? ? ?p->score = score;
? ? ? ? ? ? ? ?*/
? ? ? ? ? ? ? ?head = InsertDoc(head, p);
? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case 3:
? ? ? ? ? ? ? ?scanf("%d", &num);
? ? ? ? ? ? ? ?head = updateDoc(head, num); ?
? ? ? ? ? ? ? ?break;
? ? ? ? ? ? ? ?
? ? ? ? ? ?case 4:
? ? ? ? ? ? ? ?printf("Input num:\n");
? ? ? ? ? ? ? ?scanf("%d", &num);
? ? ? ? ? ? ? ?head = DeleteDoc(head, num); ? ? ? ? ? ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case 5:
? ? ? ? ? ? ? ?Print_Stu_Doc(head);
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case 0:
? ? ? ? ? ? ? ?break;
? ? ? ?}
? ?}while(choice != 0);
? ?return 0;
}
先不對(duì)4個(gè)用戶自定義函數(shù)進(jìn)行實(shí)現(xiàn),就用一個(gè)輸出語(yǔ)句來(lái)代替吧!
/*新建鏈表*/
struct stud_node * Create_Stu_Doc() ?
{
? ?printf("你好,該模塊完成新建鏈表的功能\n");
}
struct stud_node * InsertDoc(struct stud_node * head, struct stud_node *stud)
{
? ? printf("你好,該模塊完成在鏈表中插入一個(gè)節(jié)點(diǎn)的功能\n");
? ?/* ?*/
? ?
}
struct stud_node * updateDoc(struct stud_node * head, int num)
{
? ? printf("你好,該模塊完成修改功能\n");
? ?/* 參數(shù)1:給我一個(gè)鏈表的頭指針,參數(shù)2:給我一個(gè)學(xué)號(hào),當(dāng)然是由其他函數(shù)傳入給我 */
? ?
}
struct stud_node * DeleteDoc(struct stud_node * head, int num)
{
? ? printf("你好,該模塊完成刪除鏈表的一個(gè)節(jié)點(diǎn)的功能\n");
? ?/* 參數(shù)1:給我一個(gè)鏈表的頭指針,參數(shù)2:給我一個(gè)要?jiǎng)h除學(xué)生的學(xué)號(hào),當(dāng)然是由其他函數(shù)傳入給我 */ ? ?
}
void Print_Stu_Doc(struct stud_node * head)
{
? ?printf("你好,該模塊完成瀏覽即遍歷整個(gè)鏈表中所有數(shù)據(jù)的功能\n");
? ?/* 參數(shù)1:給我一個(gè)鏈表的頭指針即可*/
}
那將上述兩部分代碼放在一個(gè)文件中,那豈不是可以運(yùn)行了呢?來(lái)試試吧!

問(wèn):有同學(xué)問(wèn),這樣的菜單也不好看呀,能不能變個(gè)樣子呀!當(dāng)然可以了
嘗試寫一個(gè)menu函數(shù),就單干輸出菜單行不行?讓主函數(shù)開(kāi)始調(diào)用它,看看行不行唄!
void menu()
{ ? ?printf("\n\n***************************\n");
? ? ? ?printf("\t1:新建鏈表\n\n\t2:插入一個(gè)節(jié)點(diǎn)\n\n\t3:修改一個(gè)結(jié)點(diǎn)\n\n\t4:刪除一個(gè)結(jié)點(diǎn)\n\n\t5:瀏覽所有信息\n\n\t0:退出系統(tǒng)\n");
? ? ? ?printf("***************************\n");
}
那就修改一下主函數(shù)吧!如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct stud_node{
? ?int num;
? ?char name[20];
? ?int score;
? ?struct stud_node *next;
};
void menu();/* 顯示菜單 ? 可別忘記這里對(duì)菜單函數(shù)進(jìn)行聲明呀,具體menu函數(shù)實(shí)現(xiàn),放到最后一個(gè)函數(shù)后面*/
struct stud_node * Create_Stu_Doc(); ?/* 新建鏈表 */
struct stud_node * InsertDoc(struct stud_node * head, struct stud_node *stud); /* 插入 */
struct stud_node * updateDoc(struct stud_node * head, int num); /* 修改 */
struct stud_node * DeleteDoc(struct stud_node * head, int num); ?/* 刪除 */
void Print_Stu_Doc(struct stud_node * head); ?/* 遍歷 */
int main(void)
{
? ?struct stud_node *head, *p;
? ?int choice, num, score;
? ?char name[20];
? ?int size = sizeof(struct stud_node);
? ?do{
? ? ? ? menu();/* 這個(gè)地方就是調(diào)用菜單menu函數(shù)!*/
? ? ? ?scanf("%d", &choice);
? ? ? ?switch(choice){
? ? ? ? ? ?case 1:
? ? ? ? ? ? ? ?head = Create_Stu_Doc();
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case 2:
? ? ? ? ? ? ? ?/*
? ? ? ? ? ? ? ?printf("Input num,name and score:\n");
? ? ? ? ? ? ? ?scanf("%d%s%d", &num,name, &score);
? ? ? ? ? ? ? ?p = (struct stud_node *) malloc(size);
? ? ? ? ? ? ? ?p->num = num;
? ? ? ? ? ? ? ?strcpy(p->name, name);
? ? ? ? ? ? ? ?p->score = score;
? ? ? ? ? ? ? ?*/
? ? ? ? ? ? ? ?head = InsertDoc(head, p);
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case 3:
? ? ? ? ? ? ? ?scanf("%d", &num);
? ? ? ? ? ? ? ?head = updateDoc(head, num);
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case 4:
? ? ? ? ? ? ? ?printf("Input num:\n");
? ? ? ? ? ? ? ?scanf("%d", &num);
? ? ? ? ? ? ? ?head = DeleteDoc(head, num);
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case 5:
? ? ? ? ? ? ? ?Print_Stu_Doc(head);
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case 0:
? ? ? ? ? ? ? ?break;
? ? ? ?}
? ?}while(choice != 0);
? ?return 0;
}

問(wèn):有同學(xué)說(shuō),還不夠好看呀,不能顯示跟系統(tǒng)有關(guān)的內(nèi)容呀?
答:那就直接修改menu函數(shù)即可,其他的不用動(dòng)即可!
繼續(xù)修改menu函數(shù)
void menu()
{ ? ?printf("\n\n********青果學(xué)生信息管理系統(tǒng)*************\n\n");
? ? ? ?printf("\t1:新建一個(gè)學(xué)生信息鏈表\n\n\t2:插入一個(gè)學(xué)生信息\n\n\t3:修改一個(gè)學(xué)生信息\n\n\t4:刪除一個(gè)學(xué)生信息\n\n\t5:瀏覽所有學(xué)生信息\n\n\t0:退出學(xué)生信息系統(tǒng)\n");
? ? ? ?printf("**************************************\n");
}

是不是感覺(jué)好多了?嗯!
問(wèn):那功能沒(méi)有實(shí)現(xiàn)呀?
答:不要急,接下來(lái)就開(kāi)始啦!將每一個(gè)函數(shù)的實(shí)現(xiàn)過(guò)程替換掉對(duì)應(yīng)函數(shù)肚子的那個(gè)輸出語(yǔ)句!
第一:以新建鏈表的函數(shù)為例Create_Stu_Doc,其內(nèi)部實(shí)現(xiàn),是需要調(diào)用節(jié)點(diǎn)插入InsertDoc函數(shù),那這兩個(gè)函數(shù)要一起實(shí)現(xiàn),要不然,光一個(gè)Create_Stu_Doc可不夠呀!
第二,那寫的對(duì)不對(duì)也需要測(cè)試,比如說(shuō):你創(chuàng)建成功了一個(gè)學(xué)生信息單鏈表,那到底創(chuàng)建成功沒(méi)有?,那就趕緊把遍歷即瀏覽函數(shù)Print_Stu_Doc寫一下,這樣,調(diào)用輸出一下,不就可以更好地看一下是否創(chuàng)建成功了沒(méi)有?
第三:當(dāng)你寫好了修改函數(shù),和刪除函數(shù),是不是也需要測(cè)試一下,也離不開(kāi)瀏覽函數(shù)Print_Stu_Doc,所以,Print_Stu_Doc必須是正確無(wú)誤的呀!
來(lái),一步一步啦,不要急
先寫Create_Stu_Doc函數(shù),來(lái)吧
注意,我們講課時(shí),都希望你創(chuàng)建的是帶頭節(jié)點(diǎn)的單鏈表,那下面的代碼是創(chuàng)建的是啥鏈表呢??
你看,它調(diào)用的是一個(gè)InsertDoc(head, p)函數(shù),那我們看看
/*新建鏈表*/
struct stud_node * Create_Stu_Doc() ?
{
? ?struct stud_node * head,*p;
? ?int num,score;
? ?char ?name[20];
? ?int size = sizeof(struct stud_node);
? ?head = NULL;
? ?printf("Input num,name and score:\n");
? ?scanf("%d%s%d", &num,name, &score);
? ?while(num != 0){
? ? ? p = (struct stud_node *) malloc(size);
? ? ? p->num = num;
? ? ? strcpy(p->name, name);
? ? ? p->score = score;
? ? ? head = InsertDoc(head, p); ? ?/* 調(diào)用插入函數(shù) */
? ? ? scanf("%d%s%d", &num, name, &score);
? }
?
return head;
}
一起讀一下下面的insertDoc函數(shù)吧,區(qū)別于我們講過(guò)的,你看一下,
該函數(shù)兩個(gè)參數(shù),一個(gè)是給我一個(gè)鏈表,第二個(gè)參數(shù),就是傳給我的一個(gè)學(xué)生結(jié)構(gòu)體變量的指針
然后呢?這個(gè)學(xué)生要插入到什么位置呢?下面的代碼仔細(xì)看呀!
情況1:若給我傳過(guò)來(lái)的head為NULL,即是空鏈表,新來(lái)的這個(gè)就是第一個(gè)結(jié)點(diǎn)(這說(shuō)明這個(gè)算法是否考慮頭結(jié)點(diǎn))
情況2: if(ptr->num <= ptr2->num) ?就是判斷一下傳入的這個(gè)學(xué)生的學(xué)號(hào),如果是處于兩個(gè)學(xué)生節(jié)點(diǎn)的學(xué)號(hào)之間,那就插入到這兩個(gè)學(xué)生節(jié)點(diǎn)之間; ? 這點(diǎn)區(qū)別于我們之前講過(guò)的頭插,尾插,而是按照學(xué)號(hào)的順序插入呀,這就是靈活變化呀,根據(jù)題目的要求來(lái)變化呀! ? ? ?
情況3:就是else的后面的,不是第一個(gè),也不是中間的,那就放到單鏈表的最后,作為尾節(jié)點(diǎn)!
/* 插入操作 */
struct stud_node * InsertDoc(struct stud_node * head, struct stud_node *stud) ?
{ ?
? struct stud_node *ptr ,*ptr1, *ptr2;
? ?ptr2 = head;
? ?ptr = stud; /* ptr指向待插入的新的學(xué)生記錄結(jié)點(diǎn) */
? ?/* 原鏈表為空時(shí)的插入 */
? ?if(head == NULL){
? ? ? ?head = ptr; /* 新插入結(jié)點(diǎn)成為頭結(jié)點(diǎn) */
? ? ? ?head->next = NULL;
? ?}
? ?else{ ? ?/* 原鏈表不為空時(shí)的插入 */
? ? ? ? while((ptr->num > ptr2->num) && (ptr2->next != NULL)){
? ? ? ? ? ?ptr1 = ptr2; /* ptr1, ptr2各后移一個(gè)結(jié)點(diǎn) */
? ? ? ? ? ?ptr2 = ptr2->next;
? ? ? ? }
? ? ? ? if(ptr->num <= ptr2->num){ /* 在ptr1與ptr2之間插入新結(jié)點(diǎn) */
? ? ? ? ? ?if(head == ptr2){
head = ptr;
? ? ? ? ? ?}else{
ptr1->next = ptr;
}
? ? ? ? ? ?ptr->next = ptr2;
? ? ? ?}else{ ? ?/* 新插入結(jié)點(diǎn)成為尾結(jié)點(diǎn) */
? ? ? ? ? ?ptr2->next = ptr;
? ? ? ? ? ?ptr->next = NULL;
? ? ? ?}
? ?}
? ?
? ?return head;
}
不管如何,那為了驗(yàn)證新建鏈表函數(shù)和插入節(jié)點(diǎn)函數(shù)是否成功,還需要寫一下瀏覽函數(shù),這個(gè)函數(shù)實(shí)現(xiàn)很簡(jiǎn)單,就是給我一個(gè)單鏈表的頭指針,我來(lái)“順藤摸瓜”輸出每個(gè)學(xué)生節(jié)點(diǎn)的內(nèi)容!
/*遍歷操作*/
void Print_Stu_Doc(struct stud_node * head)
{ ?
struct stud_node * ptr;
? ?if(head == NULL){
? ? ? ?printf("\n對(duì)不起,該學(xué)生鏈表中沒(méi)有數(shù)據(jù)\n");
? ? ? ?return;
? ?}
? ?printf("\nThe Students' Records Are: \n");
? ?printf("Num\t Name\t Score\n");
? ?for(ptr = head; ptr != NULL; ptr = ptr->next){
? ? ? printf("%d\t%s\t%d \n", ptr->num, ptr->name, ptr->score);
? ?}
}
那這樣吧,我們就把主函數(shù)也弄過(guò)來(lái),把調(diào)用Create_Stu_Doc、InsertDoc、 Print_Stu_Doc三個(gè)函數(shù)的對(duì)應(yīng)的注釋去掉吧
這里我給一個(gè)相對(duì)完整的代碼吧!
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct stud_node{
? ?int num;
? ?char name[20];
? ?int score;
? ?struct stud_node *next;
};
void menu();/* 顯示菜單*/
struct stud_node * Create_Stu_Doc(); ?/* 新建鏈表 */
struct stud_node * InsertDoc(struct stud_node * head, struct stud_node *stud); /* 插入 */
struct stud_node * updateDoc(struct stud_node * head, int num); /* 修改 */
struct stud_node * DeleteDoc(struct stud_node * head, int num); ?/* 刪除 */
void Print_Stu_Doc(struct stud_node * head); ?/* 遍歷 */
int main(void)
{
? ?struct stud_node *head, *p;
? ?int choice, num, score;
? ?char name[20];
? ?int size = sizeof(struct stud_node);
? ?do{
? ? ? ? menu();
? ? ? ?scanf("%d", &choice);
? ? ? ?switch(choice){
? ? ? ? ? ?case 1:
? ? ? ? ? ? ? ?head = Create_Stu_Doc();
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case 2:
? ? ? ? ? ? ? ?printf("Input num,name and score:\n");
? ? ? ? ? ? ? ?scanf("%d%s%d", &num,name, &score);
? ? ? ? ? ? ? ?p = (struct stud_node *) malloc(size);
? ? ? ? ? ? ? ?p->num = num;
? ? ? ? ? ? ? ?strcpy(p->name, name);
? ? ? ? ? ? ? ?p->score = score;
? ? ? ? ? ? ? ?head = InsertDoc(head, p);
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case 3:
? ? ? ? ? ? ? ?scanf("%d", &num);
? ? ? ? ? ? ? ?head = updateDoc(head, num);
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case 4:
? ? ? ? ? ? ? ?printf("Input num:\n");
? ? ? ? ? ? ? ?scanf("%d", &num);
? ? ? ? ? ? ? ?head = DeleteDoc(head, num);
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case 5:
? ? ? ? ? ? ? ?Print_Stu_Doc(head);
? ? ? ? ? ? ? ?break;
? ? ? ? ? ?case 0:
? ? ? ? ? ? ? ?break;
? ? ? ?}
? ?}while(choice != 0);
? ?return 0;
}
/*新建鏈表*/
struct stud_node * Create_Stu_Doc()
{
? ?struct stud_node * head,*p;
? ?int num,score;
? ?char ?name[20];
? ?int size = sizeof(struct stud_node);
? ?head = NULL;
? ?printf("Input num,name and score:\n");
? ?scanf("%d%s%d", &num,name, &score);
? ?while(num != 0){
? ? ? p = (struct stud_node *) malloc(size);
? ? ? p->num = num;
? ? ? strcpy(p->name, name);
? ? ? p->score = score;
? ? ? head = InsertDoc(head, p); ? ?/* 調(diào)用插入函數(shù) */
? ? ? scanf("%d%s%d", &num, name, &score);
? }
return head;
}
/* 插入操作 */
struct stud_node * InsertDoc(struct stud_node * head, struct stud_node *stud)
{
? struct stud_node *ptr ,*ptr1, *ptr2;
? ?ptr2 = head;
? ?ptr = stud; /* ptr指向待插入的新的學(xué)生記錄結(jié)點(diǎn) */
? ?/* 原鏈表為空時(shí)的插入 */
? ?if(head == NULL){
? ? ? ?head = ptr; /* 新插入結(jié)點(diǎn)成為頭結(jié)點(diǎn) */
? ? ? ?head->next = NULL;
? ?}
? ?else{ ? ?/* 原鏈表不為空時(shí)的插入 */
? ? ? ? while((ptr->num > ptr2->num) && (ptr2->next != NULL)){
? ? ? ? ? ?ptr1 = ptr2; /* ptr1, ptr2各后移一個(gè)結(jié)點(diǎn) */
? ? ? ? ? ?ptr2 = ptr2->next;
? ? ? ? }
? ? ? ? if(ptr->num <= ptr2->num){ /* 在ptr1與ptr2之間插入新結(jié)點(diǎn) */
? ? ? ? ? ?if(head == ptr2){
head = ptr;
? ? ? ? ? ?}else{
ptr1->next = ptr;
}
? ? ? ? ? ?ptr->next = ptr2;
? ? ? ?}else{ ? ?/* 新插入結(jié)點(diǎn)成為尾結(jié)點(diǎn) */
? ? ? ? ? ?ptr2->next = ptr;
? ? ? ? ? ?ptr->next = NULL;
? ? ? ?}
? ?}
? ?return head;
}
struct stud_node * updateDoc(struct stud_node * head, int num)
{
? ? printf("你好,該模塊完成修改功能\n");
? ?/* 參數(shù)1:給我一個(gè)鏈表的頭指針,參數(shù)2:給我一個(gè)學(xué)號(hào),當(dāng)然是由其他函數(shù)傳入給我 */
}
struct stud_node * DeleteDoc(struct stud_node * head, int num)
{
? ? printf("你好,該模塊完成刪除鏈表的一個(gè)節(jié)點(diǎn)的功能\n");
? ?/* 參數(shù)1:給我一個(gè)鏈表的頭指針,參數(shù)2:給我一個(gè)要?jiǎng)h除學(xué)生的學(xué)號(hào),當(dāng)然是由其他函數(shù)傳入給我 */
}
/*遍歷操作*/
void Print_Stu_Doc(struct stud_node * head)
{
struct stud_node * ptr;
? ?if(head == NULL){
? ? ? ?printf("\nNo Records\n");
? ? ? ?return;
? ?}
? ?printf("\nThe Students' Records Are: \n");
? ?printf("Num\t Name\t Score\n");
? ?for(ptr = head; ptr != NULL; ptr = ptr->next){
? ? ? printf("%d\t%s\t%d \n", ptr->num, ptr->name, ptr->score);
? ?}
}
void menu()
{ ? ?printf("\n\n********青果學(xué)生信息管理系統(tǒng)*************\n\n");
? ? ? ?printf("\t1:新建一個(gè)學(xué)生信息鏈表\n\n\t2:插入一個(gè)學(xué)生信息\n\n\t3:修改一個(gè)學(xué)生信息\n\n\t4:刪除一個(gè)學(xué)生信息\n\n\t5:瀏覽所有學(xué)生信息\n\n\t0:退出學(xué)生信息系統(tǒng)\n");
? ? ? ?printf("**************************************\n");
}

此處,也是一個(gè)可以改進(jìn)的地方呀,難道必須每次都得輸入0以后,還得輸入一個(gè)姓名和分?jǐn)?shù)才行嗎?
如何改進(jìn)呢?你答辯的時(shí)候,是不是可以給我炫耀一下,你是如何改進(jìn) 的呢?也是頭腦風(fēng)暴之一呀!



這里,三個(gè)函數(shù)都通過(guò)測(cè)試,那至于刪除函數(shù)和修改函數(shù),你自行補(bǔ)充進(jìn)去,是不是就可以了呢?
你來(lái)做吧!
這里我假定你已經(jīng)補(bǔ)充進(jìn)去了!
至此,你是不是已經(jīng)對(duì)一個(gè)簡(jiǎn)單的學(xué)生信息管理系統(tǒng)有了了解呢!
那如何加上“文件”功能?不能每一次運(yùn)行,都讓我輸入大量的數(shù)據(jù)呀!
2.2 在上述“學(xué)生信息管理系統(tǒng)”框架基礎(chǔ)上加上“文件“的功能(未寫完)
思考一下:
文件的作用是什么?在什么時(shí)候使用?
答:
場(chǎng)景1:最簡(jiǎn)單的場(chǎng)景,就是事先已經(jīng)有一個(gè)文件,里面按照格式已經(jīng)錄入了若干條學(xué)生的信息數(shù)據(jù);在程序運(yùn)行的時(shí)候,先用一個(gè)load函數(shù),就是讀入該文件,將該文件讀入到單鏈表中或者結(jié)構(gòu)體數(shù)組中,那后面的至于插入操作、增加操作、刪除操作,修改操作、查詢操作等,都在單鏈表或者結(jié)構(gòu)體數(shù)組上完成,等最后退出整個(gè)系統(tǒng)的時(shí)候,調(diào)用一個(gè)save函數(shù),即再把單鏈表或者結(jié)構(gòu)體數(shù)組中的數(shù)據(jù)寫回到文件中,就可以了!
場(chǎng)景2:假如是第一次使用該系統(tǒng),那必然沒(méi)有該數(shù)據(jù)文件啦,那程序應(yīng)該可以提醒創(chuàng)建一個(gè)文件,然后,你錄入的數(shù)據(jù),就可以存入到該文件中
那我們回來(lái)看一下我們已經(jīng)寫好的代碼里,是如何創(chuàng)建起來(lái)鏈表的呢?
現(xiàn)有的代碼的流程是:選擇1,調(diào)用 head = Create_Stu_Doc();這個(gè)函數(shù),
Create_Stu_Doc()函數(shù)的具體實(shí)現(xiàn)為:
struct stud_node * Create_Stu_Doc()
{
? ?struct stud_node * head,*p;
? ?int num,score;
? ?char ?name[20];
? ?int size = sizeof(struct stud_node);
? ?head = NULL;
? ?printf("Input num,name and score:\n");
? ?scanf("%d%s%d", &num,name, &score);
? ?while(num != 0){
? ? ? p = (struct stud_node *) malloc(size);
? ? ? p->num = num;
? ? ? strcpy(p->name, name);
? ? ? p->score = score;
? ? ? head = InsertDoc(head, p); ? ?/* 調(diào)用插入函數(shù) */
? ? ? scanf("%d%s%d", &num, name, &score);
? }
return head;
}
觀察一下,循環(huán)地 scanf("%d%s%d", &num,name, &score); ?就是循環(huán)地讀取數(shù)據(jù),那我們能否將這里的數(shù)據(jù)的讀入,改為從文件讀入呢?即,讀一行數(shù)據(jù),創(chuàng)建一個(gè)節(jié)點(diǎn),鏈接進(jìn)鏈表,那讀取到最后,不就是創(chuàng)建完成了?
擴(kuò)展功能的頭腦風(fēng)暴(未寫完)
你自己實(shí)現(xiàn)的任何你自己覺(jué)得是頭腦風(fēng)暴中的功能,都可以在答辯的時(shí)候給我闡述出來(lái),不要怕小,其實(shí)小功能也能體現(xiàn)出不少的細(xì)節(jié)!
程序整體以及功能方面:
(1)就各個(gè)系統(tǒng)而言,例:若只完成圖書(shū)管理的圖書(shū)管理系統(tǒng),也確實(shí)能夠滿足基本要求,若能夠進(jìn)一步完成圖書(shū)借閱功能,那將是一個(gè)不錯(cuò)的功能擴(kuò)展,比如學(xué)生成績(jī)管理系統(tǒng),加上課程的管理,以及對(duì)應(yīng)成績(jī)的管理,也是功能的擴(kuò)展。
(2)多文件包含:將寫好的一個(gè)很大很長(zhǎng)的程序,進(jìn)行拆解,寫成多文件包含的形式!雖然是在設(shè)計(jì)之初就應(yīng)該將這些搞定,很多同學(xué)都是寫好程序之后,才想著拆分,其實(shí)也是一個(gè)好主意!也避免拆壞了!
(3)具體功能實(shí)現(xiàn),能否利用一些非課堂中學(xué)習(xí)的算法來(lái)實(shí)現(xiàn);(比如排序,除了學(xué)習(xí)過(guò)的冒泡和選擇排序外,自己能夠自學(xué)數(shù)據(jù)結(jié)構(gòu)課程中的其他排序算法,并實(shí)現(xiàn),那也就鍛煉了自學(xué)能力)
細(xì)節(jié):
(1)錄入數(shù)據(jù)的時(shí)候,學(xué)號(hào)是否重復(fù)呢?如何處理呢?
(2)錄入數(shù)據(jù)的時(shí)候,學(xué)號(hào)、身份證號(hào)等類似的信息,通常有固定的長(zhǎng)度,若用戶輸入錯(cuò)誤了,程序應(yīng)該有類似于校驗(yàn)并提示的功能;
(3)身份證號(hào)與出生日期是否一致?這其實(shí)也是一個(gè)校驗(yàn)的功能;要想實(shí)現(xiàn),當(dāng)然也需要寫一個(gè)小的函數(shù)呀!
(4)刪除數(shù)據(jù)的時(shí)候的提示信息“確定要?jiǎng)h除”等人性化的處理細(xì)節(jié);
(5)查詢功能的處理,可以嘗試實(shí)現(xiàn)比如模糊查詢,組合查詢等功能,比如查詢一下圖書(shū)名稱含有為“程序設(shè)計(jì)”字樣的圖書(shū)、組合查詢:比如查詢一下“清華大學(xué)出版社”出版的含有“程序設(shè)計(jì)”字樣的圖書(shū)等等
(6)各類報(bào)表的輸出,自己想一些功能,比如導(dǎo)出**個(gè)學(xué)院的借閱圖書(shū)前十名的學(xué)生,導(dǎo)出到某一個(gè)文檔中,其實(shí)就是一個(gè)寫入數(shù)據(jù)到另一個(gè)文件的過(guò)程;
(7)數(shù)據(jù)導(dǎo)入功能的設(shè)計(jì),其實(shí)就是從一個(gè)文件中讀出,寫入到另外一個(gè)文件中。
所以,你會(huì)發(fā)現(xiàn),所謂的細(xì)節(jié),實(shí)現(xiàn)起來(lái)呢,并不是很難,就是要去想,要站到用戶的角度去想,哪些功能和細(xì)節(jié)是容易忽略的,然后,你將它設(shè)計(jì)出來(lái)。