0x00 學生管理系統
說到學生管理系統,對於每一個初學c語言的人都是一道不得不過的砍。不過,學習c,我覺得我們都應該寫一個學生管理系統,當然,寫出來也只是初步了解c而已。對於初學c,通過寫一個簡單的學生管理系統我們可以來練習和熟悉基本的語法,理解鏈表這種數據結構,查找、刪除等操作,以及和數組的區別。
話不多說,這次寫的學生管理系統是基於單向鏈表實現的,單向鏈表的內容可以查看之我前寫的c語言之單向鏈表。
0x01 環境
我所有的代碼都是在linux下編寫的,編譯器使用的是gcc,若移植到windows下,請做簡單修改。
我的環境:
$ uname -a
Linux kali 4.19.0-kali5-amd64 #1 SMP Debian 4.19.37-5kali1 (2019-06-20) x86_64 GNU/Linux
$ gcc --version
gcc (Debian 9.2.1-30) 9.2.1 20200224
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
0x02 定義數據結構
這里為了簡化,使用的學生結構的成員僅有學號和姓名,由於我們使用list.h所以必須定義學生結構的比較、摧毀和打印函數。
點擊查看list.h
#ifndef LINK_H #define LINK_H
typedef struct node//鏈表元素的結構
{
void *data;//節點中的數據域,設置為無類型指針,數據類型大小由使用者定義
struct node *next;//指向下一節點的指針
}Node;typedef struct list//鏈表的結構
{
int size;//鏈表中節點個數
void (*destroy)(void data);//由於鏈表節點中的數據是用戶自定義的,故需要調用者提供釋放空間的函數
void (print_data)(const void data);//同,由用戶自定義打印數據的函數
int (match)(const void *key1, const void *key2);//同,由用戶自定義數據的比較方式Node *head;//記錄鏈表的頭部位置 Node *tail;//記錄鏈表的尾部位置
}List;
extern void list_init(List list, void (destroy)(void data), void (print_data)(const void data),
int (match)(const void *key1, const void *key2));//初始化一個鏈表
extern int list_ins_head(List *list, const void *data);//鏈表的插入,將節點從頭部插入
extern int list_ins_tail(List *list, const void *data);//鏈表的插入,將節點從尾部插入
extern int list_ins_sort(List *list, const void data);//鏈表的插入,插入后鏈表是一個有序的鏈表
extern void list_search(List *list, const void data);//在鏈表中查找指定數據,若找到返回數據的地址
extern void list_remove(List *list, const void *data);//在鏈表中刪除指定數據,若找到刪除節點並將數據地址返回
extern void list_reverse(List *list);//將鏈表逆置
extern void list_sort(List *list);//將鏈表按照一定方式排序
extern void print_list(List *list);//打印鏈表
extern void list_destroy(List *list);//刪除整個鏈表define list_size(list) (list->size) //返回鏈表節點個數
endif // LINK_H
/*這是一個學生結構體*/
typedef struct stu
{
int stu_id;//學生的id
char name[32];//學生的姓名
}STU;
/*學生信息的打印*/
void print_stu(const void *data)
{
STU *temp = (STU *)data;
printf("id = %d, name = %s\n", temp->stu_id, temp->name);
return;
}
/*學生之間的比較,此處以學生id做比較,若想使用其他字段比較,另行定義*/
int cmp_stu(const void *key1, const void *key2)
{
STU *stu1 = (STU *)key1;
STU *stu2 = (STU *)key2;
if(stu1->stu_id > stu2->stu_id)
return 1;
else if(stu1->stu_id < stu2->stu_id)
return -1;
else
return 0;
}
/*學生節點的摧毀,由於學生節點比較簡單,直接對free函數簡單封裝一下*/
void destroy(void *data)
{
if(data != NULL)
{
free(data);
data = NULL;
}
return;
}
0x03 主菜單
/*打印菜單*/
void menu()
{
printf("------------------menu----------------------\n");
printf("|打印菜單: menu |\n");
printf("|打印列表: print |\n");
printf("|插入數據: insert |\n");
printf("|查找數據: search |\n");
printf("|刪除數據: remove |\n");
printf("|逆置數據: reverse |\n");
printf("|排序數據: sort |\n");
printf("|清空屏幕: clear |\n");
printf("|退出程序: quit |\n");
printf("--------------------------------------------\n");
return;
}
運行效果:
0x04 主體邏輯
其實,剩下的極為簡單,這些只是業務邏輯的處理,還有就是對鏈表接口的利用。
int main()
{
menu();//打印菜單
List list;//定義一個鏈表
list_init(&list, destroy, print_stu, cmp_stu);//初始化鏈表
while(1)
{
char cmd[32] = "";
printf("請輸入你要執行的命令:\n");
printf("$ ");
scanf("%s", cmd);
if(strcmp(cmd, "print") == 0)//打印學生信息
{
print_list(&list);
}
else if(strcmp(cmd, "insert") == 0)//插入學生信息
{
STU *temp = (STU *)calloc(1, sizeof (STU));//初始化一個學生信息
printf("請輸入需要插入的數據:\n");
printf("-> ");
scanf("%d %s",&temp->stu_id, temp->name);
list_ins_head(&list, temp); //使用鏈表的頭部插入
//list_ins_tail(&list, temp); //使用鏈表的尾部插入
//list_ins_sort(&list, temp); //使用鏈表的有序插入
}
else if(strcmp(cmd, "search") == 0)//查找學生信息
{
printf("請輸入查找學員的學號:\n");
printf("-> ");
STU *temp = (STU *)calloc(1, sizeof (STU));//感覺此處極為丑陋,為了查找學號,還必須分配一個完整的學生信息空間,可是若不分配,及必須破壞鏈表,無法單獨的抽象出鏈表結構
scanf("%d", &temp->stu_id);
strcpy(temp->name, "");
STU *ret = (STU *)list_search(&list, temp);
if(ret != NULL)
printf("id = %d, name = %s\n", ret->stu_id, ret->name);
else
printf("not found\n");
destroy(temp);//由於僅僅是比較,學生信息只是臨時的,所以要釋放堆空間
}
else if(strcmp(cmd, "remove") == 0)//刪除學生信息
{
printf("請輸入刪除學員的學號:\n");
printf("-> ");
STU *temp = (STU *)calloc(1, sizeof (STU));
scanf("%d", &temp->stu_id);
strcpy(temp->name, "");
STU *ret = (STU *)list_remove(&list, temp);//由於返回值是一個指向學生信息的空間,並且已經被刪除,所以需要手動釋放
if(ret != NULL)
{
printf("removed:id = %d, name = %s\n", ret->stu_id, ret->name);
list.destroy(ret);//釋放掉返回的空間
}
else
printf("not found\n");
destroy(temp);
}
else if(strcmp(cmd, "reverse") == 0)//逆置鏈表
{
list_reverse(&list);
}
else if(strcmp(cmd, "sort") == 0)//對學生信息排序
{
list_sort(&list);
}
else if(strcmp(cmd, "clear") == 0)//使用系統調用,對控制台清屏
{
system("clear");//windows下使用system("cls");
}
else if(strcmp(cmd, "menu") == 0)
{
menu();
}
else if(strcmp(cmd, "quit") == 0)//退出程序
{
list_destroy(&list);//在程序退出前,清理鏈表內存
printf("byebye!\n");
break;
}
else
{
printf("請輸入正確指令!\n");
}
}
return 0;
}
0x05 運行結果
0x06 總結
學生管理系統還不是很完善,有很多細節可以扣,容錯方面是肯定不夠的,另外我們沒有對學生信息存儲,可以使用文件或數據庫。
其實,學生管理系統只是形式,我們只要學會鏈表的增刪改查,可以隨意更改,可以是書籍管理系統、電影管理系統等。
附,源碼:
#ifndef LINK_H
#define LINK_H
typedef struct node//鏈表元素的結構
{
void *data;//節點中的數據域,設置為無類型指針,數據類型大小由使用者定義
struct node *next;//指向下一節點的指針
}Node;
typedef struct list//鏈表的結構
{
int size;//鏈表中節點個數
void (*destroy)(void *data);//由於鏈表節點中的數據是用戶自定義的,故需要調用者提供釋放空間的函數
void (*print_data)(const void *data);//同,由用戶自定義打印數據的函數
int (*match)(const void *key1, const void *key2);//同,由用戶自定義數據的比較方式
Node *head;//記錄鏈表的頭部位置
Node *tail;//記錄鏈表的尾部位置
}List;
extern void list_init(List *list, void (*destroy)(void *data), void (*print_data)(const void *data), \
int (*match)(const void *key1, const void *key2));//初始化一個鏈表
extern int list_ins_head(List *list, const void *data);//鏈表的插入,將節點從頭部插入
extern int list_ins_tail(List *list, const void *data);//鏈表的插入,將節點從尾部插入
extern int list_ins_sort(List *list, const void *data);//鏈表的插入,插入后鏈表是一個有序的鏈表
extern void* list_search(List *list, const void *data);//在鏈表中查找指定數據,若找到返回數據的地址
extern void* list_remove(List *list, const void *data);//在鏈表中刪除指定數據,若找到刪除節點並將數據地址返回
extern void list_reverse(List *list);//將鏈表逆置
extern void list_sort(List *list);//將鏈表按照一定方式排序
extern void print_list(List *list);//打印鏈表
extern void list_destroy(List *list);//刪除整個鏈表
#define list_size(list) (list->size) //返回鏈表節點個數
#endif // LINK_H
#include "list.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void list_init(List *list, void (*destroy)(void *data), void (*print_data)(const void *data), \
int (*match)(const void *key1, const void *key2))
{
list->size = 0;
list->head = NULL;
list->tail = NULL;
list->match = match;
list->destroy = destroy;
list->print_data = print_data;
return;
}
void print_list(List *list)
{
if(list->head == NULL)//鏈表為空
{
printf("list is empty\n");
}
else //鏈表非空
{
Node * p_cur = list->head;
while (p_cur)
{
list->print_data(p_cur->data);
p_cur = p_cur->next;
}
}
return;
}
/*在鏈表的頭部插入數據*/
int list_ins_head(List *list, const void *data)
{
Node *new_node = (Node *)calloc(1, sizeof (Node)); //創建插入的節點
if(new_node == NULL)
return -1;
new_node->data = (void *)data;//關聯節點與數據
/*
if(list_size(list) == 0)//鏈表為空時,插入節點
{
list->tail = new_node;
new_node->next = NULL;
list->head = new_node;
}
else //鏈表非空時將節點插入頭部
{
new_node->next = list->head;
list->head = new_node;
}
*/
if(list_size(list) == 0)//鏈表為空時,插入節點
list->tail = new_node;
new_node->next = list->head;
list->head = new_node;
list->size ++;
return 0;
}
/*在鏈表的尾部插入數據*/
int list_ins_tail(List *list, const void *data)
{
Node *new_node = (Node *)calloc(1, sizeof (Node)); //創建插入的節點
if(new_node == NULL)
return -1;
new_node->data = (void *)data;//關聯節點與數據
if(list_size(list) == 0)
list->head = new_node;
else
list->tail->next = new_node;
list->tail = new_node;
new_node->next = NULL;
list->size ++;
return 0;
}
/*在鏈表的有序插入數據*/
int list_ins_sort(List *list, const void *data)
{
Node *new_node = (Node *)calloc(1, sizeof (Node)); //創建插入的節點
if(new_node == NULL)
return -1;
new_node->data = (void *)data;//關聯節點與數據
if(list_size(list) == 0)//鏈表為空時,插入節點
{
list->tail = new_node;
new_node->next = NULL;
list->head = new_node;
}
else//鏈表非空時
{
Node *p_cur = list->head;
Node *p_pre = list->head;
while(p_cur != NULL && list->match(new_node->data, p_cur->data) > 0)//查找鏈表的插入位置
{
p_pre = p_cur;
p_cur = p_cur->next;
}
if(p_cur != NULL)//插入位置在頭部和中間時
{
if(p_cur == list->head)//插入位置在頭部
{
new_node->next = list->head;
list->head = new_node;
}
else//位置在鏈表中間
{
new_node->next = p_pre->next;
p_pre->next = new_node;
}
}
else//插入位置在鏈表尾部
{
list->tail->next = new_node;
list->tail = new_node;
new_node = NULL;
}
}
list->size ++;
return 0;
}
/*查找鏈表中與數據匹配的節點,並返回節點指針*/
void* list_search(List *list, const void *data)
{
if(list_size(list) == 0)
{
printf("list is empty\n");
return NULL;
}
else
{
Node *p_cur = list->head;
while(p_cur != NULL && list->match(p_cur->data, data) != 0)//查找數據在鏈表中的位置
p_cur = p_cur->next;
if(p_cur != NULL)//找到返回數據地址,否則返回NULL
return p_cur->data;
else
return NULL;
}
}
/*刪除指定數據的節點*/
void* list_remove(List *list, const void *data)
{
void *old_data = NULL;
Node *p_cur = list->head;
Node *p_pre = list->head;
while (p_cur != NULL && list->match(p_cur->data, data) !=0)
{
p_pre = p_cur;
p_cur = p_cur->next;
}
if(p_cur != NULL && list->match(p_cur->data, data) ==0)//刪除位置在頭部和中間時
{
if(p_cur == list->head)//刪除位置在頭部
{
list->head = p_cur->next;
if(p_cur->next == NULL)
list->tail = NULL;
}
else//中部時或尾部
{
p_pre->next = p_cur->next;
if(p_cur->next == NULL)
list->tail = p_pre;
}
old_data = p_cur->data;
free(p_cur);
list->size --;
}
return old_data;
}
void list_reverse(List *list)
{
if(list_size(list) != 0)
{
Node *p_pre = list->head;
Node *p_cur = list->head->next;
list->head->next = NULL;
list->tail = list->head;
while(p_cur!= NULL)
{
p_pre = p_cur;
p_cur = p_cur->next;
p_pre->next = list->head;
list->head = p_pre;
}
}
return;
}
void list_sort(List *list)
{
if(list_size(list) != 0)
{
Node *p_i = list->head;
while(p_i->next != NULL)
{
Node *p_min = p_i;
Node *p_j = p_min->next;
while (p_j != NULL)
{
if(list->match(p_min->data, p_j->data) > 0)
p_min = p_j;
p_j = p_j->next;
}
if(p_min != p_i)
{
void *data = p_i->data;
p_i->data = p_min->data;
p_min->data = data;
}
p_i = p_i->next;
}
}
return;
}
void list_destroy(List *list)
{
Node *p_cur = list->head;
while (p_cur != NULL)
{
list->head = list->head->next;
list->destroy(p_cur->data);//釋放節點中的數據
free(p_cur);//釋放節點
p_cur = list->head;
}
memset(list, 0, sizeof (List));
return;
}
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "list.h"
typedef struct stu
{
int stu_id;
char name[32];
}STU;
void print_stu(const void *data)
{
STU *temp = (STU *)data;
printf("id = %d, name = %s\n", temp->stu_id, temp->name);
return;
}
int cmp_stu(const void *key1, const void *key2)
{
STU *stu1 = (STU *)key1;
STU *stu2 = (STU *)key2;
if(stu1->stu_id > stu2->stu_id)
return 1;
else if(stu1->stu_id < stu2->stu_id)
return -1;
else
return 0;
}
void destroy(void *data)
{
if(data != NULL)
{
free(data);
data = NULL;
}
return;
}
void menu()
{
printf("------------------menu----------------------\n");
printf("|打印菜單: menu |\n");
printf("|打印列表: print |\n");
printf("|插入數據: insert |\n");
printf("|查找數據: search |\n");
printf("|刪除數據: remove |\n");
printf("|逆置數據: reverse |\n");
printf("|排序數據: sort |\n");
printf("|清空屏幕: clear |\n");
printf("|退出程序: quit |\n");
printf("--------------------------------------------\n");
return;
}
int main()
{
menu();
List list;
list_init(&list, destroy, print_stu, cmp_stu);
while(1)
{
char cmd[32] = "";
printf("請輸入你要執行的命令:\n");
printf("$ ");
scanf("%s", cmd);
if(strcmp(cmd, "print") == 0)
{
print_list(&list);
}
else if(strcmp(cmd, "insert") == 0)
{
STU *temp = (STU *)calloc(1, sizeof (STU));
printf("請輸入需要插入的數據:\n");
printf("-> ");
scanf("%d %s",&temp->stu_id, temp->name);
list_ins_head(&list, temp);
//list_ins_tail(&list, temp);
//list_ins_sort(&list, temp);
}
else if(strcmp(cmd, "search") == 0)
{
printf("請輸入查找學員的學號:\n");
printf("-> ");
STU *temp = (STU *)calloc(1, sizeof (STU));
scanf("%d", &temp->stu_id);
strcpy(temp->name, "");
STU *ret = (STU *)list_search(&list, temp);
if(ret != NULL)
printf("id = %d, name = %s\n", ret->stu_id, ret->name);
else
printf("not found\n");
destroy(temp);
}
else if(strcmp(cmd, "remove") == 0)
{
printf("請輸入刪除學員的學號:\n");
printf("-> ");
STU *temp = (STU *)calloc(1, sizeof (STU));
scanf("%d", &temp->stu_id);
strcpy(temp->name, "");
STU *ret = (STU *)list_remove(&list, temp);
if(ret != NULL)
{
printf("removed:id = %d, name = %s\n", ret->stu_id, ret->name);
list.destroy(ret);
}
else
printf("not found\n");
destroy(temp);
}
else if(strcmp(cmd, "reverse") == 0)
{
list_reverse(&list);
}
else if(strcmp(cmd, "sort") == 0)
{
list_sort(&list);
}
else if(strcmp(cmd, "clear") == 0)
{
system("clear");
}
else if(strcmp(cmd, "menu") == 0)
{
menu();
}
else if(strcmp(cmd, "quit") == 0)
{
list_destroy(&list);
printf("byebye!\n");
break;
}
else
{
printf("請輸入正確指令!\n");
}
}
return 0;
}