目錄表
國立屏東大學 資訊工程學系 程式設計(二)
15. 使用者自定資料型態
15.1 Structures
我們可以將相關的資料項目集合起來,定義為一個Structure(結構體)。從程式的角度來看,所謂的結構體是一個包含有多個變數的資料集合,可用以宣告變數–稱為結構體變數,進行相關的程式處理。
15.1.1 結構體變數
結構體變數的宣告在語法上以「struct{ … } structVarName;」定義一個結構體,其中可包含一個或一個以上的欄位(field)定義。每個欄位定義其實就是一個變數的宣告,不可包含初始值的給定,其語法結構如下:
struct { [type fieldName;]+ } structVarName[,structVarName]*;
例如,下面的程式碼片段定義了兩個平面上的點,每個點包含有x與y軸座標:
struct { int x; int y; } p1, p2;
當然,你也可以這樣寫:
struct { int x,y; } p1, p2;
在上述程式碼片段中,p1與p2為所宣告的結構體的變數,我們可以p1.x,p1.y,p2.x與p2.y來存取這些結構體中的欄位(變數)。下面提供另一個例子:
struct { int productNo; char *productName; float price; int quantity; } mfone;
這個例子宣告了一個名為mfone的結構體變數,其中包含有產品編號、名稱、單價與庫存數量。現在讓我們來看看在記憶體中的空間配置,如figure 1。
現在請同學動動腦,想想看在前述例子中的p1, p2與mfone各佔用了多少的記憶體空間呢?
<note>
</note>
<這邊要進行一些討論…structdef.c>
關於結構體的變數其初始值可以在結構變數宣告時以「={ … }」方式,依欄位的順序進行給定,請參考下面的例子:
struct { int x; int y; } p1 = {0,0}, p2 = {100, 100}; struct { int productNo; char *productName; float price; int quantity; } mfone = {12, "mPhone 6s", 15000, 100};
從C99開始,還支援了指定初始子(designated initializers)的方法,讓我們可以在宣告其初始值的同時,給定欄位的名稱,且不受原本順序的限制,請參考下例:
struct { int x; int y; } p1 = {.x=0, .y=0}, p2 = {.y=100, .x=100}; struct { int productNo; char *productName; float price; int quantity; } mfone = {.productName="mPhone 6s", .price=15000, .quantity=100}; //productNo並沒給定初始值
15.1.2 結構體變數的操作
結構體變數的操作相當簡單,你可以「結構體變數名稱.欄位名稱」來存取其值,例如:
struct { int x; int y; } p1 = {0,0}, p2 = {100, 100}; p1.x = 100; float z; z = sqrt(p1.x*p1.x + p1.y*p1.y); p2.x+=5; scanf("%d", &p1.x);
但結構體變數最方便的地方在於可以使用「=」運算子,讓兩個結構體間可以互相給定其值,包含了其中所有的欄位,甚至也包含了字串的值。請參考下面的程式:
struct { int productNo; char *productName; float price; int quantity; } mfone = {12, "mPhone 6s", 15000, 100}, mfone2; mfone2=mfone;
15.1.3 結構體型態
我們也可以將單獨地宣告結構體,而不用同時將其變數加以宣告。這樣做有很多的好處,尤其是當程式碼需要共享時,我們可以將結構體的宣告獨立於某個檔案,爾後其它程式可以直接以該定義來宣告其變數,其語法如下:
struct structName { [type fieldName;]+ };
有了先宣告好的結構體之後,就可以在需要時再宣告之變數,請參考下面的程式:
struct point { int x; int y; }; struct point p1, p2; struct point p3 ={6,6};
當然,你也可以這樣寫:
struct point { int x,y; } p1, p2; struct point p3;
另一種方式,則是直接將結構體定義為一種新的資料型態,其語法如下:
struct structName { [type fieldName;]+ }; typedef struct structName typeName; typedef struct { [type fieldName;]+ } typeName;
如此一來,我們就可以利用這個新的資料型態來宣告變數,請參考下面的程式:
struct point { int x,y; }; typedef struct point Point;
或是
typedef struct { int x,y; } Point;
接著你就可以在需要的時候,以「Point」做為資料型態的名稱來進行變數的宣告,例如:
#include <stdio.h> struct point { int x,y; }; typedef struct point Point; int main() { Point p1; Point p2 = {5,5}; p1=p2; p1.x+=p1.y+=10; printf("p1=(%d,%d) p2=(%d,%d)\n", p1.x, p1.y, p2.x, p2.y); return 0; }
15.1.4 結構體與函式
在結構體與函式方面,我們將先探討以結構體變數做為引數的方法,請參考下面的例子:
#include <stdio.h>
struct point
{
int x,y;
};
typedef struct point Point;
void showPoint(Point p)
{
printf("(%d,%d)\n", p.x, p.y);
}
int main()
{
Point p1;
Point p2 = {5,5};
p1=p2;
p1.x+=p1.y+=10;
showPoint(p1);
showPoint(p2);
return 0;
}
請看下一個程式,我們設計另一個函式,讓我們修改所傳入的Point的值:
#include <stdio.h>
struct point
{
int x,y;
};
typedef struct point Point;
void resetPoint(Point p)
{
p.x=p.y=0;
}
void showPoint(Point p)
{
printf("(%d,%d)\n", p.x, p.y);
}
int main()
{
Point p1;
Point p2 = {5,5};
p1=p2;
p1.x+=p1.y+=10;
showPoint(p1);
showPoint(p2);
resetPoint(p1);
showPoint(p1);
return 0;
}
請先猜猜看其執行結果為何?然後再實際執行看看其結果為何?
<在這裡要討論一下 … >
#include <stdio.h>
struct point
{
int x,y;
};
typedef struct point Point;
void resetPoint(Point *p)
{
p->x=p->y=0;
}
void showPoint(Point p)
{
printf("(%d,%d)\n", p.x, p.y);
}
int main()
{
Point p1;
Point p2 = {5,5};
p1=p2;
p1.x+=p1.y+=10;
showPoint(p1);
showPoint(p2);
resetPoint(&p1);
showPoint(p1);
return 0;
}
讓我們做一個總結:若採用call-by-reference的方式,將變數所在的記憶體位址傳入函式,那麼在函式中,做為一個指向記憶體中某個結構體的指標,其要存取結構體內的欄位時,須以「→」運算子為之。
當我們在呼叫某個函式時,也可以直接產生一個新的結構體做為引數,例如:
showPoint( (Point) {5,6} );
我們也可以讓結構體做為函式的傳回值,請參考下面的例子:
#include <stdio.h>
#include <math.h>
struct point
{
int x,y;
};
typedef struct point Point;
Point addPoints(Point p1, Point p2)
{
Point p;
p.x = p1.x + p2.x;
p.y = p1.y + p2.y;
return p;
}
void showPoint(Point p)
{
printf("(%d,%d)\n", p.x, p.y);
}
int main()
{
Point p1;
Point p2 = {5,5};
Point p3;
p1=p2;
p1.x+=p1.y+=10;
showPoint(p1);
showPoint(p2);
p3=addPoints(p1,p2);
showPoint(p3);
return 0;
}
現在,讓我們看看下面這個程式:
#include <stdio.h>
struct point
{
int x,y;
};
typedef struct point Point;
Point * addPoint(Point *p1, Point p2)
{
p1->x = p1->x + p2.x;
p1->y = p1->y + p2.y;
return p1;
}
void showPoint(Point p)
{
printf("(%d,%d)\n", p.x, p.y);
}
int main()
{
Point *p1;
Point p2 = {5,5};
Point *p3;
p1=&p2;
p1->x += p1->y+=10;
showPoint(*p1);
showPoint(p2);
p3=addPoint(p1,p2);
showPoint(*p1);
showPoint(*p3);
return 0;
}
15.1.5 巢狀式結構體
我們也可以在一個結構體內含有另一個結構體做為其欄位,請參考下面的程式:
#include <stdio.h>
typedef struct { char firstname[20], lastname[20]; } Name;
typedef struct
{
Name name;
int phone;
} Contact;
void showName(Name n)
{
printf("%s, %s", n.lastname, n.firstname);
}
void showContact(Contact c)
{
showName(c.name);
printf(" %d\n", c.phone);
}
int main()
{
Contact someone={.phone=12345, .name={.firstname="Jun", .lastname="Wu"}};
showContact(someone);
return 0;
}
15.1.6 結構體陣列
我們也可以利用結構體來宣告陣列,請參考下面的片段:
Point a={3,5}; Point twoPoints[2]; Point points[10] = { {0,0}, {1,1}, {2,2}, {3,3}, {4,4}, {5,5}, {6,6}, {7,7}, {8,8}, {9,9} }; twoPoints[0].x=5; twoPoints[0].y=6; twoPoints[1] = a;
15.2 Unions
Union與結構體非常相像,但Union在記憶體內所保有的空間僅能存放一個欄位的資料,適用於某種資料可能有兩種以上不同型態的情況,例如: 一個在程式中會使用到的數值資料,在某些應用時是整數,但在另一些應用時可能會是浮點數。在這種情況下,我們可以宣告一個union來解決此問題:
union { int i; double d; } data;
當我們需要它是整數時,就使用其i的欄位;若需要它是浮點數時,就使用其d的欄位,例如:
#include <stdio.h>
union
{
int i;
double d;
} data;
int main()
{
data.i=5;
printf("%d %f\n", data.i, data.d );
data.d=10.5;
printf("%d %f\n", data.i, data.d );
return 0;
}
請執行看看這個程式,想想看為何會有這樣的執行結果,也想想看這樣的工具可以用在哪?
當然,前面在結構體時可以應用的宣告方式,定義方式等都可以適用在union上,例如下面的程式碼:
union { int i; double d; } data = {0} // 只有第一個欄位可以有初始值
union { int i; double d; } data = {.d=3.14} // 可以指定其它的欄位做為初始值
union num { int i; double d; }; typedef union num Num;
typedef union { int i; double d; } Num;
15.3 Enumerations
我們可以用以下的宣告,來限制變數s1與s2可能的數值:
enum { SPADE, HEART, DIAMOND, CLUB } s1, s2;
也可以把enum定義好,然後再宣告變數:
enum suit { SPADE, HEART, DIAMOND, CLUB }; enum suit s1, s2; 或是 typedef enum { SPADE, HEART, DIAMOND, CLUB } Suit; Suit s1,s2;
其實enum的實作是把列舉的數值視為整數,第一個數值視為0,第二個為1,依此類推。不過,我們也可以自行定義其數值:
enum suit { SPADE=1, HEART=13, DIAMOND=26, CLUB=39 };
15.4 綜合演練
請參考下面這個比較完整的例子:
#include <stdio.h>
typedef enum { Sunday=0, Monday=1, Tuesday=2, Wednesday=3, Thursday=4,
Friday=5, Saturday=6, Undef=-1 } Weekday;
char weekdayString[][10] ={"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday" };
Weekday getWeekday(char day[])
{
Weekday i=Sunday;
for(i=Sunday;i<=Saturday;i++)
{
if(strcmp(day, weekdayString[i])==0)
return i;
}
return Undef;
}
int main()
{
Weekday startDay, endDay;
char today[10];
int days;
printf("What day (of the week) is it today? ");
scanf(" %s", today);
if((startDay = getWeekday(today))!=Undef)
{
printf("How many days after today? ");
scanf(" %d", &days);
endDay = (startDay+days)%7;
printf("%d days later is %s.\n", days, weekdayString[endDay]);
if(endDay==Sunday) // how about if(endDay==0)?
printf("The end day is Sunday. We extend it to Monday!\n");
}
else
printf("unknown weekday");
return 0;
}
union也常和結構體一起使用,用以定義更有彈性的結構體,請參考下例:
#include <stdio.h>
#include <string.h>
typedef enum {BOOK, KEYCHAIN, T_SHIRT} Type;
enum color {red, gold, silver, black, blue, brown};
enum _size {XS, S, M, L, XL, XXL, XXXL};
typedef enum _size Size;
typedef struct {
int stock_number;
float price;
Type type;
union
{
char author[20];
enum color color;
Size size;
} attribute;
} Product;
int main()
{
Product littlePrince;
Product bubbleBear;
Product transformer;
littlePrince.stock_number=10;
littlePrince.price = 280.0;
littlePrince.type = BOOK;
strcpy(littlePrince.attribute.author, "Antoine");
bubbleBear.stock_number=20;
bubbleBear.price = 55.0;
bubbleBear.type = KEYCHAIN;
bubbleBear.attribute.color = gold;
transformer.stock_number=23;
transformer.price = 350.0;
transformer.type = T_SHIRT;
transformer.attribute.size = L;
return 0;
}
下面這個例子,應用到了本章所介紹的相關方法:
#include <stdio.h>
#include <string.h>
typedef enum {BOOK, KEYCHAIN, T_SHIRT} Type;
typedef enum {red, gold, silver, black, blue, brown} Color;
char colorName[][10]={"red", "gold", "silver", "black", "blue", "brown"};
typedef enum {XS, S, M, L, XL, XXL, XXXL} Size;
char sizeName[][5]={"XS", "S", "M", "L", "XL", "XXL", "XXXL"};
typedef struct {
int stock_number;
float price;
Type type;
union
{
char author[20];
Color color;
Size size;
} attribute;
} Product;
void showInfo(Product p)
{
printf("Stock Number: %d\n", p.stock_number);
printf("Price: %.2f\n", p.price);
switch(p.type)
{
case BOOK:
printf("Author: %s\n", p.attribute.author );
break;
case KEYCHAIN:
printf("Color: %s\n", colorName[p.attribute.color]);
break;
case T_SHIRT:
printf("Size: %s\n", sizeName[p.attribute.size]);
break;
}
printf("\n");
}
int main()
{
Product p[3];
p[0].stock_number=10;
p[0].price = 280.0;
p[0].type = BOOK;
strcpy(p[0].attribute.author, "Antoine");
p[1].stock_number=20;
p[1].price = 55.0;
p[1].type = KEYCHAIN;
p[1].attribute.color = gold;
p[2].stock_number=23;
p[2].price = 350.0;
p[2].type = T_SHIRT;
p[2].attribute.size = L;
showInfo(p[0]);
showInfo(p[1]);
showInfo(p[2]);
return 0;
}

