目錄表

國立屏東大學 資訊工程學系 程式設計

第十二章補充程式練習題 第4題 補充說明


題目

設計一個程式用以管理10 個聯絡人資訊。每個聯絡人包含以下資訊:

請參考以下的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程式已開發完成,有需要完整程式的同學可參考此處