目錄表
國立屏東大學 資訊工程學系 程式設計
第十二章補充程式練習題 第4題 補充說明
題目
設計一個程式用以管理10 個聯絡人資訊。每個聯絡人包含以下資訊:
- name(姓名)
- gender(性別)
- birthday(生日)
- phonetype(電話型態)
- phone(電話號碼)
- address(住址)
請參考以下的Contact.h(你可以在本書隨附光碟中的/Exercises/ch12/4/ 找到所需要的檔案)所定義的相關結構體、列舉資料型別與共有體:
<note> 注意:在課本補充習題中,原第28行所宣告的number[10],應改為number[11]。 </note>
#include <stdio.h> #include <stdlib.h> #include <string.h> #define numContact 10 typedef enum {Male, Female} Gender; typedef enum {January, February, March, April, May, June, July, August, September, October, November, December} Month; typedef struct { Month month; short day; short year; } Date; typedef struct { char firstname[20]; char lastname[10]; } Name; typedef enum {CHT, TWN, FET} Carrier; typedef struct { char number[11]; // 課本此題原先宣告為number[10],應更正為number[11] Carrier carrier; } Mobile; typedef struct { char areacode[4]; char number[8]; } Landline; typedef enum {LandLine, MobilePhone} PhoneType; typedef struct { Name name; Gender gender; Date birthday; PhoneType phonetype; union { Landline landline; Mobile mobile; } phone; char address[50]; } Contact; Contact getAContact(); void showAContact(Contact c); void sortContacts(Contact cs[]);
請設計並實作一個名為Contact.c 的程式,並在其中完成「getAContact()」、「showAContact()」與「sortContacts()」函式之實作。相關程式功能可以使用以下的Main.c(你可以在本書隨附光碟中的/Exercises/ch12/5/ 找到所需要的檔案)進行測試:
#include "Contact.h" int main() { int i=0; Contact mycontacts[numContact]; for(i=0;i<numContact;i++) mycontacts[i]=getAContact(); sortContacts(mycontacts); printf("\n\n"); for(i=0;i<numContact;i++) showAContact(mycontacts[i]); }
一律使用「firstname lastname」的格式,並假設使用者不會輸入錯誤的日期,電話區碼用括號標示 排序後(依birthday 排序,年紀由小到大輸出),輸出如下
程式的執行結果可參考下面的畫面:
[9:19 user@ws hw] ./a.out Name: Amy Wang Gender (M/F): F Birthday (YYYY/MM/DD): 1977/3/5 Phone Type (L/M): L Number: (08)7238700 Address: No.51, Mingsheng E.Rd., Pingtung City Name: Cheng-Kung Liu Gender (M/F): M Birthday (YYYY/MM/DD): 1995/12/3 Phone Type (L/M): M Number: 0918123456 Carrier (C/T/F): T Address: No.99, ChongHui St., Taipei City ⋮ ChengKung Liu (Male) December 3rd, 1995, 0918123456(Taiwan Mobile), rNo. 99, ChongHui St., Taipei City. Amy Wang (Female) March 5th, 1977, (08)7238700, No.51, Mingsheng E.Rd., Pingtung City. ⋮ [9:19 user@ws hw]
<note> 注意: 在電信業者方面, 中華電信以Chunghwa Telecom 表示, 遠傳以FarEasTone 表示,台灣大哥大以Taiwan Mobile 表示。 </note>
題目中的Contact.h及Main.c 可在本書隨附光碟中的/Exercises/ch12 目錄裡找到,你只需要完成Contact.c 的程式設計。撰寫此程式時,你可以暫時將numContact 設定為2~3,以便進行測試,或是將10 筆資料先存成文字檔,以I/O 轉向(I/O redirect)的方式進行測試。
本題可以視需要增加新的函式及程式碼,但不可以變動Contact.h 檔案內容以及 Main.c 中的內容,應繳交的檔案為Contact.c。
解答
此題必須在Contact.c裡完成「getAContact()」、「showAContact()」以及「sortContacts()」等函式之實作。我們首先載入以下三個header files:
#include <stdio.h> #include "Contact.h" // 本題所定義的Contact等相關結構體,以及函式定義 #include <string.h> // 字串相關函式定義
在getAContact()函式方面,其作用是取得使用者所輸入的聯絡人資訊,並放入一個Contact結構體後傳回。首先在第12行印出提示字串提示使用者輸入聯絡人的姓名,並使用第14行的fgets()來加以取回存放到tempName字串中,後續再進行分割該字串為firstname與lastname的操作。
<note> 要注意的是由於我們假設使用者一定會先輸入firstname再輸入lastname(以一個空白字元分隔),所以也可以更簡單地使用第13行的scanf()直接取回兩個字串放到c.name.firstname與c.name.lastname裡。 </note>
Contact getAContact() { int i=0; Contact c; // 宣告一個Contact結構體的變數,用以存放所取回的聯絡人資訊後傳回 char tempName[32]; //存取使用者所輸入的聯絡人姓名的字串 char *p, tempc; printf("Name: "); // scanf(" %s %s", c.name.firstname, c.name.lastname); fgets(tempName,30,stdin);
後續的第16行至33行,就是負責將tempName字串依據空白字元進行分割,將空白字元前面的部份放到c.name.firstname裡,並將其後的部份放入到c.name.lastname裡。
p=tempName; i=0; while(*p!=' ') { c.name.firstname[i]=*p; i++; p++; } c.name.firstname[i]='\0'; i=0; p++; while(*p!='\n') { c.name.lastname[i]=*p; i++; p++; } c.name.lastname[i]='\0';
接下來的第35-42行,則是讓使用者輸入聯絡人的性別,並據以設定c.gender的數值(使用Gender列舉型態的數值)。
printf("Gender (M/F): "); scanf(" %c",&tempc); if(tempc=='F') c.gender=Female; else if(tempc=='M') c.gender=Male;
再緊接著是45行開始,先取回的生日的月、日、年放入變數month、day與year裡,在將其做為c.birthday.month、c.birthday.day與c.birthday.year的數值。由於c.birthday.month是Month列舉型態,其值只能為January, February, March, April, May, June, July, August, September, October, November與December之一,由於列舉型態是以整數0開始賦值,所以我們只要將使用者輸入的月份數值-1就可以表示對應的Month列舉型態數值,如第50行所示。
short month, day, year; printf("Birthday (YYYY/MM/DD): "); scanf(" %hd/%hd/%hd", &year, &month, &day); c.birthday.month = (month-1); c.birthday.day=day; c.birthday.year=year;
第53與54行取回使用者所輸入的電話型態(市話L或行動電話M),並分別在第56-65行以及第66-82行取回其市話號碼或行動電話號碼(以及電信業者)。當使用者輸入的是「L」(也就是市話)時,第58行先設定c.phonetype為LandLine,接著在第62行利用scanf()的scanset取回(###)#######格式的電話號碼,並在第63與64行複製區碼及號碼到c.phone.landline.areacode與c.phone.landline.number裡。若使用者輸入的是「M」(也就是行動電話),則在第68行設定c.phonetype為MobilePhone,然後再取回其電話號碼以及電信業者。
printf("Phone Type (L/M): "); scanf(" %c",&tempc); if(tempc=='L') { c.phonetype=LandLine; char area[4]; char number[8]; printf("Number: "); scanf(" (%[^)])%s",area, number); strcpy(c.phone.landline.areacode, area); strcpy(c.phone.landline.number, number); } else if(tempc=='M') { c.phonetype=MobilePhone; char number[11]; printf("Number: "); scanf(" %s", number); strcpy(c.phone.mobile.number, number); printf("Carrier (C/T/F): "); scanf(" %c", &tempc); if(tempc=='C') c.phone.mobile.carrier=CHT; else if(tempc=='T') c.phone.mobile.carrier=TWN; else if(tempc=='F') c.phone.mobile.carrier=FET; }
第85行取回住址,並放於c.address裡。最後,我們將各個欄位都填寫好的Contact結構體變數c在第88行傳回。要注意的是,我們在第86行,使用一個getchar()將輸入的緩衝區裡的Enter鍵加以清空。至此,getAContact()函式的設計已經完成。
printf("Address: "); scanf(" %[^\n]",c.address); getchar(); return c; }
後續的showAContact()函式,接收一個Contact結構體的變數並將其內容依題目要求的格式輸出。此部份並不困難,請同學自行參考。
void showAContact(Contact c) { printf("%s %s (", c.name.firstname, c.name.lastname); if(c.gender==Male) printf("Male"); else printf("Female"); printf(") "); showDate(c.birthday); if(c.phonetype==LandLine) printf(", (%s)%s,", c.phone.landline.areacode, c.phone.landline.number); else { printf(", %s(", c.phone.mobile.number); if(c.phone.mobile.carrier==CHT) printf("ChungHwa Telecom"); else if(c.phone.mobile.carrier==TWN) printf("Taiwan Mobile"); else printf("FarEasTone"); printf("),"); } printf(" %s\n", c.address); }
在上述程式碼的第127行,透過呼叫showDate()函式來將聯絡人的生日依題目要求輸出。showDate()函式的實作並不困難,請參考下列程式碼:
void showDate(Date d) { char months[][10]={"January", "February", "March", "April", "May", "June", "July","August", "September", "October", "November", "December"}; printf("%s %d", months[d.month], d.day); switch(d.day) { case 1: case 21: case 31: printf("st"); break; case 2: case 22: printf("nd"); break; case 3: case 23: printf("rd"); break; default: printf("th"); break; } printf(", %d", d.year); }
此程式在第144行開始,實作了一個名為compareAge()的函式,用以判斷兩個聯絡人a與b的年齡;若a比b年長,則傳回1、b比a年長,則傳回-1,若兩人同一天生日,則傳回0。此函式的實作相當簡單,請同學自行參考。
int compareAge(Contact a, Contact b) { // return 1 if a is older // return 0 if a and b have the same birthday // return -1 if b is older int adays, bdays; adays=a.birthday.year*365+a.birthday.month*30+a.birthday.day; bdays=b.birthday.year*365+b.birthday.month*30+b.birthday.day; if(adays>bdays) return -1; else if(adays<bdays) return 1; return 0; }
最後在第160-177行,則是以氣泡排序法依據聯絡人的年齡(由小到大)進行排序。
void sortContacts(Contact contacts[]) { int i,j; for(i=0;i<numContact-1;i++) { for(j=0;j<numContact-i-1;j++) { if( compareAge(contacts[j],contacts[j+1])==1) { Contact temp; temp=contacts[j]; contacts[j]=contacts[j+1]; contacts[j+1]=temp; } } } }
至此,Contact.c程式已開發完成,有需要完整程式的同學可參考此處。