使用者自定資料型態
使用者為何要自己定義資料型態?
C 語言中提供了 char, int, float, double, long 等基本型態,
又提供了陣列來組合同一種基本型態定義出來的變數,
程式設計者在撰寫程式來處理真實世界中的資料時還不夠用嗎?
我們在介紹變數的時候曾經希望你將變數想像為 CPU 暫時儲存資料的地方,
每一個變數都有一個唯一的名字是 CPU
用來記住它到底在變數裡面放了什麼東西的依據,
不同的資料變數嚴格來說是無關的,
若是真的有什麼關聯的話,
是 CPU 記著有那樣子的關係
(或者說是程式的邏輯內必須記住它們之間的關聯,
甚至其實說穿了還是程式設計者要記著這些變數之間的關聯。)
下面我們舉一個例子讓你體會一下。
平行陣列 (Parallel Array)
假設我們設計一個程式記錄全班同學程式設計這堂課的作業成績、
期中考成績、
期末考成績,
程式希望計算每一個同學的學期成績,
還要計算每一次考試的平均成績,
最後再依照同學學期成績的高低把所有的資料做一個排序,
如果使用者希望要看到平均成績在任何一個區間內有哪些同學時,
程式可以很快地找到,
並且列印出這些同學所有的成績資料。
這樣子的問題如果叫任何一個人來處理,
他應該都會列一張表,
記錄姓名、
學號、
作業 1、
作業 2、
期中考、
期末考、
平均成績,
不過這個方法碰到排序就比較傷腦筋了:
一種方法是準備另一張表格,
從原來的表格上依大小順序逐項抄寫到新的表格上;
另一種方法則是把表格上每一個同學的資料切開,
一個同學一張,
然後對調順序。
不管怎樣,
這種表格如果在 C 程式裡用基本型態變數和陣列來做的話,
我們需要做下面的宣告:
#define NUMBER_STUDENT 60
char name[NUMBER_STUDENT][11];
char id[NUMBER_STUDENT][11];
int assignment1[NUMBER_STUDENT];
int assignment2[NUMBER_STUDENT];
int midterm[NUMBER_STUDENT];
int final[NUMBER_STUDENT];
float average[NUMBER_STUDENT];
總共七個陣列來存放資料,
每一個學生的資料都放在七個陣列中 index 相同的地方,
這種陣列我們把它叫做 parallel array。
請注意:
設計程式的人自己要知道陣列中 index 一樣時指的是同一個學生的資料,
這樣子的七個陣列變數在程式內操作的時候,
無論是設定其內的資料、
列印其內的資料,
對調兩筆資料存放的位置時都要一致地來存取,
不要漏掉任何一個才好。
這樣子寫的程式不管是設計者自己過一段時間再來閱讀修改、
或是其它人員來閱讀修改時一定都會有一個很大的疑問,
就是到底 index 相同的是不是指同一個學生的資料?
如果你在宣告陣列變數時不是用常數 NUMBER_STUDENT
的話那更容易會弄不懂。
我們在設計程式時資料的組成結構最好能夠和現實世界中資料組合的方式一致,
這樣子的程式,
錯誤的機會少,
維護和修改都比較方便。
讓我們看一看上面這個範例在 C 程式中如何以自定的資料型態來表達:
struct 敘述:自定結構資料型態
typedef struct student
{
char name[11];
char id[11];
int assignment1;
int assignment2;
int midterm;
int final;
float average;
} StudentInfo;
以上敘述定義了一個叫做 StudentInfo 的自定資料型態,
這種資料型態內包含七個成員,
分別描述姓名、
學生證號碼、
作業一成績、
作業二成績、
期中、
期末、
及平均成績。
以此型態定義變數如下。
定義結構型態之變數
StudentInfo student1, *pStudent, studentAry[60];
struct student student1,*pStudent,studentAry[60];
上面敘述以 StudentInfo 型態定義了一個結構變數、
一個指標變數指向此種型態的變數、
以及一個包含 60 個 StudentInfo 型態變數元素的陣列變數,
每一個這種型態的變數包含了我們前面範例裡每一個學生的完整資料,
不管誰來看這樣子的程式都很容易感覺到這七種資料的整體性。
結構變數之使用:
-
設定任一成員資料
strcpy(student1.name, "John");
student1.assignment1 = 85;
-
使用成員資料
student1.assignment1 + student1.assignment2
-
利用指標存取成員資料
pStudent = &student1;
(*pStudent).final = 50;
或是 pStudent->final = 50;
-
取得某一成員之位址
-
初始化
StudentInfo student1={"John","1234",60,70,80,90,0.0};
結構變數使用多少記憶體?
sizeof(StudentInfo)
= sizeof(student1)
= sizeof(struct student)
= sizeof(struct student
{
char name[11];
char id[11];
int assignment1;
int assignment2;
int midterm;
int final;
float average;
})
上面這個數字是多少呢?
也許每一個編譯器都不太一樣,
甚至在很多編譯器裡會有選項來改變記憶體內各個元素間的間隔,
(所謂的 packed structure
就是讓結構內各個元素之間完全不留空隙以節省記憶體,
有些編譯器可以設定此選項,
例如:Visual C/C++ 的命令列選項 /Zp。)
一般最直接的想法會以為
sizeof(StudentInfo) == 2*sizeof(char [11])+4*sizeof(int)+sizeof(float)
其實不一定,
這是 sizeof(StudentInfo) 的最小值,
有時為了記憶體存取速度的原因,
各個元素會放置在所謂的 word boundary 上,
(例如:
某些機器上 word 是兩個位元組,
也就是說結構內每一個元素會由偶數位元組開始),
如此在某些元素之間會出現無用的位元組。
注意:
當以二進位方式將結構變數內的資料存於檔案時,
必須特別注意寫入及讀出程式內結構變數內元素排列必須一致,
否則雖然以下列敘述讀出及寫入,
資料不一定是正確的:
程式 A (寫入)
StudentInfo studentA;
fwrite(&studentA, sizeof(StudentInfo), 1, fp);
程式 B (讀出)
StudentInfo studentB;
fread(&studentB, sizeof(StudentInfo), 1, fp);
動態配置記憶體:
結構變數常常用在 List 及
Tree 資料結構中,
因此常常使用動態記憶體配置,
正確的配置方法如下:
pStudent=(StudentInfo*) malloc(sizeof(StudentInfo));
參考前面幾節的解釋,
千萬別以為 sizeof(StudentInfo) 一定是
2*sizeof(char[11])+4*sizeof(int)+sizeof(float)
= 2*11+4*2+4 = 34 位元組就用
pStudent = (StudentInfo*) malloc(34);
這是很容易出錯的,
同時程式也很難移植到別的機器平台上。
結構變數如何傳入函式做為參數?
如何做為函式傳回值?
結構變數是以傳值 (call by value)
的方式做為引數傳入函式之內,
不論結構有多大,
都是完全拷貝,
例如:
StudentInfo func(Student firstStudent)
{
StudentInfo StudentA;
.
.
.
return StudentA;
}
結構變數參數傳遞之效率需由程式設計者自己衡量,
如果不希望完全拷貝的話就要利用指標來傳遞。
注意:
在 C 語言中陣列基本上是一種指標,
把陣列當參數傳遞時一律只傳遞位址而不拷貝資料內容,
如果在某一應用中需要拷貝資料內容的話,
唯一的方法就是使用結構將陣列包裝起來如下:
typedef
struct arytag
{
int data[100];
} INTARY;
INTARY func(INTARY param)
{
INTARY tmp;
.
.
.
return tmp;
}
簡易串列(List)資料結構應用
節點定義:
typedef struct listNodeTag
{
int data;
struct listNodeTag *next;
} ListNode;
Head 指標定義:
操作串列函式:
insertHead() 及 deleteHead()
分別在串列最前端放一資料節點及刪除一資料節點,
void insertHead(int data)
{
ListNode *ptr;
ptr = (ListNode*)malloc(sizeof(ListNode));
ptr->data = data;
if (ptrHead==0)
ptr->next = 0;
else
ptr->next = ptrHead;
ptrHead = ptr;
}
int deleteHead(int *ptrData)
{
ListNode *ptr;
if (ptrHead==0)
return 0;
else
{
ptr = ptrHead;
ptrHead = ptr->next;
*ptrData = ptr->data;
free(ptr);
return 1;
}
}