問題分析
服裝信息管理及銷售管理系統。方便對庫存服裝的信息管理和添加新服裝數據,同時兼具庫存數量管理功能。
功能實現
1、建立服裝信息庫,包括:服裝代碼、型號、規格、面料、顏色、單價、數量;
2、建立銷售信息庫,包括:服裝代碼,數量,日期,售價,小記
3、程序分為兩大模塊:庫存模塊和銷售模塊。庫存模塊主要用于對服裝信息的增加、刪除、修改。銷售模塊主要用于管理庫存數量;
4、瀏覽:管理員可以查詢庫存信息和銷售信息。
5、查找或修改:可以通過編號查找服裝。可以根據編號進行修改、刪除信息;
6、登記:可以通過編號登記庫存情況、銷售情況,同時改變庫存信息。
現在來看,這個項目最大的難點就是利用c語言的文件系統實現同時修改多個文件,例如服裝下單時,要先檢查剩余庫存;購買時不僅要扣減庫存,還要寫入購買記錄。在沒有數據庫時實現這一功能比較麻煩。
代碼實現
/*該程序運行編譯器為Dev-C++ 5.4.0 ,64位系統*/
#include<stdio.h>
#include<string.h>
typedef struct _clothes{int nu; //nu as number,服裝代碼 char name[30]; char si[10]; //si as sizechar ma[20]; //ma as materialchar co[10]; //co as colorfloat pr; //pr as prizeint qu; //qu as quantity
}Clothes; //建立clothes結構類型,方便進行讀寫
typedef struct _sale{int rn; //rn as real number,means the sequence of infomationint nu; //nu as number,服裝代碼 int qu; //qu as quantitychar da[9]; //da as date,save by 8 bytes char;float pr; //pr as prizechar note[20];
}Sale; //建立sale結構類型,方便進行讀寫
//鍵盤輸入新的庫存信息
void cgetlist(Clothes aClo[],int number,int innumber){ int i;for(i=0;i<number;i++,innumber++){ //輸入number個數據 printf("服裝代碼為(系統自動分配):%d\n",innumber+1);aClo[i].nu=innumber;printf("\t輸入型號:");scanf("%29s",aClo[i].name);printf("\t輸入規格:");scanf("%9s",aClo[i].si); printf("\t輸入面料:");scanf("%19s",aClo[i].ma);printf("\t輸入顏色:");scanf("%9s",aClo[i].co);printf("\t輸入單價:");scanf("%f",&aClo[i].pr);printf("\t輸入庫存數量:");scanf("%d",&aClo[i].qu);}
}
//鍵盤輸入新的銷售信息
int sgetlist(Sale aSal[],int number,int innumber){ int i;for(i=0;i<number;i++,innumber++){printf("銷售記錄代碼為:%d\n",innumber+1); aSal[i].rn=innumber;printf("\t輸入服裝代碼:");scanf("%d",&aSal[i].nu);printf("\t輸入銷售數量:");scanf("%d",&aSal[i].qu);if(reducecheck(aSal[i].nu,aSal[i].qu)==-1) {return -1;} //檢查庫存是否足夠,不夠就返回,中止后面的輸入 printf("\t輸入日期:");scanf("%s",aSal[i].da); printf("\t輸入售價:");scanf("%f",&aSal[i].pr);printf("\t小記:");scanf("%s",aSal[i].note);}return 0; //不然就不返回值
}
//將cgetlist輸入的信息寫入文件,與文件直接通信
int save(Clothes aClo[],int number){ int ret=-1; //作為是否成功寫入的標記值 FILE*fp=fopen("clothes.data","a"); //嘗試用追加打開,用w打開的話會把之前的數據全部清除 if(!fp){ //如果打開失敗(假設是由于沒有可打開的文件) fclose(fp); //就關閉文件指針 FILE*fp=fopen("clothes.data","w"); //再用w打開來建立文件 fclose(fp); //然后關閉指針 FILE*fpc=fopen("clothes.data","a"); //再用追加打開 ret=fwrite(aClo,sizeof(Clothes),number,fpc); //寫入aClo結構體數組,同時給ret一個值 fclose(fpc); //關閉指針 }else if(fp){ //如果用追加方式打開了 ret=fwrite(aClo,sizeof(Clothes),number,fp); //就直接寫入 fclose(fp);}return ret==number; //返回number
}
//將sgetlist輸入的信息寫入文件,與文件直接通信
int ssave(Sale aSal[],int number){ int ret=-1; //作為是否成功寫入的標記值FILE*fp=fopen("sale.data","a"); //同save,用w或a打開 if(!fp){fclose(fp);FILE*fp=fopen("sale.data","w");fclose(fp);FILE*fpc=fopen("sale.data","a");ret=fwrite(aSal,sizeof(Sale),number,fpc);fclose(fpc);}else if(fp){ret=fwrite(aSal,sizeof(Sale),number,fp);fclose(fp);}return ret==number;
}
//讀取打印庫存數據
void read(FILE*fp,int index){ fseek(fp,index*sizeof(Clothes),SEEK_SET); //fp指針移動到指定數據的位置 Clothes clo; //建立結構體用來接受文件里的數據 if(fread(&clo,sizeof(Clothes),1,fp)==1){ //從文件讀取數據,如果不成功的話就不輸出到控制臺 printf("服裝代碼:%d",index+1); //用printf輸出clo里的數據 printf("\t型號:%s\n",clo.name);printf("\t規格:%s\n",clo.si);printf("\t面料:%s\n",clo.ma);printf("\t顏色:%s\n",clo.co);printf("\t單價:%.2f\n",clo.pr);printf("\t數量:%d\n",clo.qu);}
}
//read函數的封裝,無形參,操作界面里調用
void reads(){ FILE*fp=fopen("clothes.data","r"); //只讀方式打開庫存信息 if(fp){ //如果成功打開 fseek(fp,0L,SEEK_END); //移動到文件末位 long size=ftell(fp); //讀取文件末位的數據量 int number=size/sizeof(Clothes); //計算有幾條數據 int index=0; //讓用戶輸入查找的服裝編號 printf("共有%d條數據,輸入要檢查的服裝代碼:",number); scanf("%d",&index); //輸入服裝編號 read(fp,index-1); //系統分配服裝編號為了不出現0號,加了一,因此這里減一 fclose(fp);}getchar(); //scanf后輸入了回車,這里接收掉 screen(); //回到操作界面
}
//這里和read基本一樣,但是打印的是銷售數據
void sread(FILE*fp,int index){ fseek(fp,index*sizeof(Sale),SEEK_SET);Sale sal;if(fread(&sal,sizeof(Sale),1,fp)==1){printf("銷售記錄編號:%d\n",index+1);printf("\t服裝代碼:%d\n",sal.nu);printf("\t銷售數量:%d\n",sal.qu);printf("\t日期:%s\n",sal.da);printf("\t售價:%.2f\n",sal.pr);printf("\t小記:%s\n",sal.note);}
}
//sread的封裝,和reads基本一樣
void sreads(){ FILE*fp=fopen("sale.data","r");if(fp){fseek(fp,0L,SEEK_END);long size=ftell(fp);int number=size/sizeof(Sale);int index=0;printf("共有%d條數據,輸入要檢查的銷售記錄編號:",number);scanf("%d",&index);sread(fp,index-1);fclose(fp);}getchar();screen();
}
//庫存信息添加函數
void add(){ int number=0,innumber=0; //number是用來遍歷輸入數據的,innumber是系統分配的代碼 printf("要添加的服裝種類數量:");scanf("%d",&number);Clothes aClo[number]; //建立number個aClo結構體 FILE*fp=fopen("clothes.data","r"); //以只讀打開庫存數據,以下這段代碼是為了分配服裝代碼 if(fp){ fseek(fp,0L,SEEK_END); //移動到文件末尾 long size=ftell(fp); //讀取文件長度 innumber=size/sizeof(Clothes); //計算已有的結構體數量 fclose(fp); //關閉文件 }cgetlist(aClo,number,innumber); //用getlist獲取新庫存信息 if(!save(aClo,number)){ //保存文件,如果返回-1的話 printf("服裝入庫失敗\n"); //打印失敗 }else printf("服裝入庫成功\n"); //否則打印成功 getchar(); //接受scanf輸入的回車 screen(); //返回操作界面
}
//銷售信息添加函數,和add的實現方式基本一樣
void sadd(){ int number=0,innumber=0;printf("要添加的銷售記錄條數:");scanf("%d",&number);Sale aSal[number]; //建立number個aSal結構體 FILE*fp=fopen("sale.data","r");if(fp){fseek(fp,0L,SEEK_END); //這段代碼作用是讀取有多少個數據 long size=ftell(fp); //這樣就可以由系統自動分配下一個編號 innumber=size/sizeof(Sale);fclose(fp);}if(sgetlist(aSal,number,innumber)==-1){ //如果sgetlist返回-1,說明庫存不足 printf("^銷售信息寫入失敗\n");}else{ //如果沒有返回-1,調用ssave嘗試保存 if(!ssave(aSal,number)){ //保存sgetlist輸入的aSal結構體 printf("*銷售信息添加失敗\n"); }else printf("^銷售信息添加成功\n");} getchar(); //接受scanf輸入的回車screen();
}
//檢查庫存是否足夠和修改庫存,x是服裝編號,y是銷售數量
int reducecheck(int x,int y){FILE*fpr=fopen("clothes.data","r+"); //只讀方式打開庫存信息 if(fpr){ //如果成功打開Clothes clo[1]; //建立結構體用來接受文件里的數據 fseek(fpr,(x-1)*sizeof(Clothes),SEEK_SET); //fp指針移動到指定數據的位置 fread(&clo,sizeof(Clothes),1,fpr); //讀取文件中的數據 if(clo[0].qu<y){ //如果庫存不足 printf("庫存不足 ");return -1; //返回-1到getlist,getlist會返回-1到sadd }else { printf("\t^^^庫存充足\n"); clo[0].qu=clo[0].qu-y; //將clo中的數量值修改為銷售后的數量 fseek(fpr,(x-1)*sizeof(Clothes),0); //移動到數據位置 fwrite(clo,sizeof(Clothes),1,fpr); //用aclo覆蓋原來的數據 fclose(fpr); //關閉clothes.data,此時sale.data依然打開 }}
}
//庫存信息修改函數的實現,有形參
void change(FILE*fp,int index){Clothes aClo[1]; //建立臨時結構體aClo接收數據 cgetlist(aClo,1,index); //用cgetlist寫入數據 到aClo fseek(fp,index*sizeof(Clothes),0); //指針移動到數據位置 fwrite(aClo,sizeof(Clothes),1,fp); //保存aClo到文件中
}
//庫存信息修改函數的封裝,無形參
void changes(){FILE*fp=fopen("clothes.data","r+"); //只讀方式打開庫存信息 if(fp){ //如果成功打開 fseek(fp,0L,SEEK_END); //移動到文件末位 long size=ftell(fp); //讀取文件末位的數據量 int number=size/sizeof(Clothes); //計算有幾條數據 int index=0; //讓用戶輸入修改的服裝編號 printf("共有%d條數據,輸入要修改的服裝代碼:",number); scanf("%d",&index); //輸入服裝編號 change(fp,index-1); //調用修改函數,系統分配服裝編號時為了不出現0號,加了一,因此這里減一 fclose(fp);}getchar(); //scanf后輸入了回車,這里接收掉 screen();
}
//刪除函數用的getlist
void dcgetlist(Clothes aClo[],int innumber){aClo[0].nu=innumber;strcpy(aClo[0].name,"此處數據已刪除"); //為了查找系統正常工作,不清空數據,而是替換為空 strcpy(aClo[0].si,"");strcpy(aClo[0].ma,"");strcpy(aClo[0].co,"");aClo[0].pr=0;aClo[0].qu=0;
}
//刪除函數
void del(FILE*fp,int index){Clothes aClo[1]; //建立臨時結構體aClo dcgetlist(aClo,index-1); //將aClo變為刪除后的特殊結構體 fseek(fp,index*sizeof(Clothes),0); //指針移動到數據位置 fwrite(aClo,sizeof(Clothes),1,fp); //把aClo寫入文件
}
//刪除函數的封裝,無形參,操作界面里調用
void dels(){FILE*fp=fopen("clothes.data","r+"); //只讀方式打開庫存信息 if(fp){ //如果成功打開 fseek(fp,0L,SEEK_END); //移動到文件末位 long size=ftell(fp); //讀取文件末位的數據量 int number=size/sizeof(Clothes); //計算有幾條數據 int index=0; //讓用戶輸入刪除的服裝編號 printf("共有%d條數據,輸入要刪除的服裝代碼:",number); scanf("%d",&index); //輸入服裝編號 del(fp,index-1); //系統分配服裝編號為了不出現0號,加了一,因此這里減一 fclose(fp);}getchar(); //scanf后輸入了回車,這里接收掉 screen();
}
//操作界面函數
int screen(){char a;printf("N-服裝信息登記,M-服裝信息修改,R-服裝信息查找\n");printf("D-服裝信息刪除,S-服裝銷售登記,G-銷售信息查找\n");printf("Q-退出系統\n");printf("輸入字母來決定操作:");scanf("%c",&a);switch(a){case'N':add();break;case'M':changes();break;case'R':reads();break;case'D':dels();break;case'S':sadd();break;case'G':sreads();break;}
}
int main(){screen();
}
項目總結(大一時所寫原文,未改動)
首先,從結果來看,整個程序并不復雜,結構很簡單,但是設計過程中依然碰到了很多問題并嚴重延長了開發周期,這一方面時體現了對c語言仍然不夠熟練,另一方面也表明對程序設計難度的低估。
先來說說幾個遇到的問題。首先在還沒開始時就犯了難。要如何能夠把數據寫進文件,還能按原樣一個一個讀出來呢?又要如何實現查找呢?最早還沒開始做時,設想過用鏈表和二叉樹來寫,但實際操作才發現文件里實現不了。后來想過用“#”等符號作為分隔符,但是這樣好像也很難實現按型號、規格等一條條輸出,而且修改很困難。后來通過進一步學習才發現用二進制寫入可以實現寫入結構體和鏈表。現在寫入的問題解決了,接下來就要解決查找的問題。一開始想法是把文件里內容全部讀進內存再用strstr找,但這里也有問題,一是strstr查找精度不高,比如要查找Tom,函數可能找到Toma就返回了。其次,找到之后,要輸出接下來的數據也比較難。最終決定,由系統自動分配專門編號,再由這一編號來進行查找、修改和刪除等。這一決定也使得之后的銷售系統對庫存進行修改的操作變得更加簡單。
開始的添加和查找函數都比較簡單,所以遇到的困難不多。這里有一點,增加函數要么用“w”打開,要么用“a”打開,用w的話,之后再打開文件夾添加庫存時,會直接清空以前的文件;而用a打開的話,則無法創建文件夾。最后是設置了一個if語句來判斷是否存在文件。修改就有些麻煩了。考慮到刪除也可以看作修改,就先從刪除函數開始。因為要按照上文的按編號查找的話,是不能直接清空一個結構體的,這樣會直接導致后面的編號全部亂掉。所以采取了把數據替換為空字符的做法。這樣雖然不能直接減少空間,但考慮到本身就是數據量很小的純字符數據,所以這樣影響也不大。然后就是刪除函數的實現。首先肯定不能用w方法打開,這樣一打開就什么都沒了。然后用過用a打開,結果即使用fseek把指針移動到指定位置,也只能寫到文件末尾。最后發現,用“r+”方法打開就可以實現覆蓋到指定位置。之后還犯了一個小錯誤,直接把新字符串=原字符串,結果出現了亂碼。然后才發現要用strcpy函數。解決了刪除函數之后,修改函數也就不難了,只需要把刪除里面的空白結構體換成用戶輸入結構體即可。
然后是銷售管理部分。添加和查找銷售記錄都比較簡單,和上面一樣,只要修改一下結構體就可以。但涉及到和庫存函數的通信,就有些麻煩了。當我們輸入完服裝編號和銷量后,就應該立刻判斷是否庫存不夠,如果不夠就立即彈出信息并返回;同時輸入的數據也要及時清除,防止混進下一次輸入造成亂碼。于是這里用了一個檢測函數,并且可以直接return退出。但是這個檢測函數也沒那么簡單,碰到了很多麻煩。同時檢測函數也要擔負把銷售后剩余庫存信息寫進庫存文件的功能。最終的解決辦法是,再檢測函數里用另一個文件指針打開庫存文件,讀取這里的信息,檢測是否庫存足夠,足夠的話就把原有信息修改后,覆蓋到原來的位置。
最后是操作界面函數。以上的函數都能正常工作,但是放到switch里面時,要么不能打開,要么直接退出,要么顯示兩次操作界面。最后發現,每個函數都要用scanf輸入數據,這個過程會往緩沖區里寫入一個回車符,隨后它就直接輸入到了操作界面,這就引發了上面的諸多問題。最后的解決辦法是往每個函數末尾添加了一個getchar()來接收回車符。
通過這個項目的編寫,認識到了以下幾個點:
- 對C語言的很多操作,尤其是對文件的操作,其實還并不了解。此外,用等號給字符數組賦值的錯誤操作也出現了。
- C++的確是對C語言的非常重要的擴展。例如String類,就可以直接用等號賦值;更重要的是,上面的服裝信息系統和銷售信息系統本質上非常相似,其實完全可以用類來實現。以及一些函數,只是一些參數有變化,應該也可以用模板函數實現。
- 數據庫是非常偉大的發明。用C自帶的文件通訊還是比較麻煩的,而且經常會有一些錯誤操作導致數據損壞。用數據庫的話,不僅可以方便的儲存一些更高級的數據結構,同時在一些方面也更安全。