# 結構體(Structure)
結構體(struct)
「struct」是個C語言的關鍵字(keyword,又稱保留字),為英文structure的縮寫,中文翻譯作「結構體」,簡稱「結構」,是一種「使用者自訂」的「資料型態i」,並且它是物件導向語言中「類別」(class)的前身。
C語言已經有內建short、long、int、float、double、char … 等「資料型態」,不過由於這些資料型態的變數彼此之間可能會有某種相關性,例如班級中每個學生的成績單上都會有:1.學號(字元陣列或字串型態),2.數學成績(整數型態)3.英文成績(整數型態)4.平均成績(浮點數型態),所以我們就可以說成績單就是一種結構(struct)的資料型態,因為成績單整合(綁定)了字串(在C語言是字元陣列)、整數和浮點數這三種資料型態的變數。
如同成績單的例子,結構(struct)是一種我們自訂的「資料型態」,可以更清楚地表現出變數之間的關聯性,方便我們管理與使用資料。換句話說,struct就像int、float、double、char … 一樣是種「資料型態」,只不過int、float、double、char … 是C語言內建的資料型態,而struct xxx的「xxx」是我們「自訂」的資料型態。
重點是,我們並不是自己創造一個很厲害的資料型態,例如什麼超級整數、快速浮點數、閃電字元…等,這些都是我亂掰的,我們只是「綁定」int、float、double、char … 這些基本的資料型態,成為我們自訂的資料型態。如下圖,我們使用關鍵字struct告訴C語言編譯器,我們現在開始要來做一種自訂的資料型態,我把它命名為「TestScore」來代表成績單使用的資料型態(可任意取名,但名稱要有意義才方便使用),TestScore這種資料型態綁定了char、int、float這三種資料型態(的變數),術語稱為:「TestScore是一種結構化的資料型態(data type)」。
![](https://i.imgur.com/y0prH66.png)
註:習慣上(約定俗成),對於自訂的資料型態名稱,開頭首個字母會使用大寫以示區別,例如這裡的TestScore。它也融合了駝峰式命名法,和「Test_score」是同樣意思。
在C語言中,定義結構化資料型態的語法如下:
struct 自己取的結構化的「資料型態」的名稱 {
資料型態 成員變數1;
資料型態 成員變數2;
資料型態 成員變數3;
……
資料型態 成員變數n;
} ; // 最後要記得加上分號
剛才,我們把這個結構化的資料型態取名為「TestScore」,是一種用來儲存學生的學號和成績資料的資料型態(data type)。我們把剛才的中文用C語言實際寫出來:
struct TestScore { // TestScore是這個結構化的資料型態(data type)的名稱
char ID_number[10] ; // C語言沒有string(字串)型態,故以字元陣列代表字串
int math , english ; // 數學和英文成績是整數型態
float average ; // 平均成績是浮點數型態
} ; // } 括號的後面還要加上分號(如int a = 8 ;要加分號的道理)
TestScore為一個結構化的「資料型態」的名稱,這種型態整合(綁定)了三種不同資料型態的四個變數,分別為:名稱為ID_number的字元陣列(或說字串型態)、名稱為math和english的整數型態的變數、名稱為average的浮點數型態的變數。
既然TestScore是一個「資料型態」的名稱,和int、float、double、char … 是同樣地位的東西。現在想想我們一般怎麼宣告整數型態的變數 … 「int a ;」,那我們就比照辦理:
struct TestScore 小華;
struct TestScore 小明;
struct TestScore 小美;
struct TestScore 小張;
特別注意,雖然宣告結構化變數和宣告一般變數的方式差不多,但在C語言中,宣告結構化型態的變數,前面還要多寫上「struct」關鍵字,用意是告訴C語言編譯器,後面有「使用者自訂的結構化的資料型態」,這沒什麼道理,只是個(強制性的)語法而已,請讀者自己要特別留意。
如此一來,我們就有四個「結構化的資料型態」的變數,其變數名稱分別為「小華、小明、小美、小張」。
後來,程式設計師們覺得前面要多加一個struct關鍵字確實蠻奇怪的,會破壞C語言語法的一致性,所以另外設計了一個關鍵字「typedef」,就是英文type define的縮寫。「typedef」關鍵字可以讓我們把「struct TestScore」這兩個字給縮減成我們自己設定的另外一個名稱,術語叫alias,就是「別名」或「綽號」的意思,就像把「李奧納多‧狄卡皮歐」給取綽號叫「皮卡丘」是同樣的道理。
typedef關鍵字的語法如下:
typedef struct TestScore Student ; // student是任意取的名稱,但名稱要有意義才方便使用
以上語法,就相當於我們現在可以用Student來取代struct TestScore這兩個字,可讓我們的程式碼更加精簡,也更容易理解,就把它想成Word裡面「尋找及取代」的功能就對了。
註1:除了以上示範,typedef關鍵字還可以讓我們為任意名稱取綽號,也就是說上面範例的藍色底色的區塊,無論有多少個名稱(就算只有一個也行),都可以被取代為範例中橘色底色的那個名稱(綽號),但是綽號只能有一個。
註2:typedef關鍵字給了程式設計師一些命名上的彈性,但不要濫用,否則自由過頭了,名稱會變得很混亂,反而讓程式變得不好理解。事實上,typedef較少用在struct以外的地方。
不過,現在我們都沒有登錄任何資料進去,以小美為例,我們要來登錄她的學號和英文成績資料,也就是做初始化的動作:
小美 . ID_number = “ LN2003 ”; // 學號是我隨便亂編的
小美 . english = 88 ; // 把88(分)指派到小美代表英文成績的變數內
上面的語法中,英文的句點「.」此時可想成是中文的「的」,等於我們跟電腦要求,我們現在要指派小美「的」學號,以及小美「的」英文成績,然後和往常一樣把等號右邊的東西指派到等號左邊去。
如果我們想看(印出)小美「的」學號是幾號,以及小美「的」英文考幾分,那我們就使用以下語法:
printf(小美 . ID_number); // 學號是字串(字元陣列)型態
printf(小美 . english); // 英文成績是整數型態
則螢幕上就會顯示:LN2003和88(分)。
-----------------------------------------------------------------------------------------
結構(stuct)還可以與陣列(array)做搭配,會更方便記錄大量資料。範例承上,語法如下:
struct TestScore stumath[4] ; // 宣告一個結構化的陣列,名為stumath,內有4個元素
stumath[0].math = 75 ;
stumath[1].math = 67 ;
stumath[2].math = 82 ;
stumath[3].math = 95 ;
於是,我們這樣就把這四個人的數學成績,放到stumath[4]陣列(student_math的簡寫)儲存起來了。
以上的寫法比較囉嗦,相當於我們先宣告一個變數,並且分開來做初始化,例如:
int a ;
a = 8 ;
我們知道可以把上面2句程式碼合併寫成「int a = 8 ;」,更簡短而且可讀性更高。同理,宣告結構變數時也可以同時做初始化,例如以下語法,可以在定義TestScore型態時順便宣告兩個結構化型態的變數x和y,可以讓程式碼變得更加精簡,可讀性更高,讀者可以把它背起來:
struct TestScore {
char idNumber[10];
int eng, math ;
float avg ;
} x, y ; // x,y寫在 } 後面,並且結尾記得加上分號
依此類推,其實也可以同時宣告更多個變數,如x,y,z,和「int x,y,z,a,b,c,d ;」…是同樣的道理。
再舉一個例子,這次我們在宣告一個結構化資料型態的時候,同時將這個結構化陣列的名字取為members[4],並且同時做初始化的動作(宣告時就賦值給member[4]陣列):
struct TestScore {
char ID_number[10];
int math ;
} members[4] = {{“LN2001”,75},{“LN2002”,67},{“LN2003”,82},{“LN2004”,95}};
members[4]陣列存放的是四個人的學號和數學成績。
註1:members[4]陣列是一個兩層的「巢狀」陣列,這樣的寫法只有指出第一層的元素數量(4個)。
註2:為了簡化說明,這邊忽略英文成績和平均成績。
補充練習:
定義一個結構化的資料型態,名為Contact(通訊錄)。而結構成員有姓名、年齡和電話,並且做初始化:
struct Contact {
char name[ ]; // 可以不指定陣列的元素數量,讓系統自己判斷
int age ;
char phone[ ]; // 可以不指定陣列的元素數量,讓系統自己判斷
} my_contact[ ] = {{“Mary”,25,”0933308752”},{“John”,38,”0955125478”},{“Jack”,52,”0912451265”},{“Jerry”,28,”0912153074”}} ;
注意以上練習是隨便掰了四個人的通訊錄資料,然後電話號碼是「字串」(字元陣列)而不是數值資料。
再提醒一下,struct是告訴電腦:「我們現在要開始創建一個自訂的結構化的資料型態」的關鍵字,而Contact才是這個結構化的「資料型態」的名稱(如同int、float、double、char…的地位),而my_contact是Contact這個結構化的資料型態的變數或陣列的名稱(如同「int a;」 的a)。
struct是系統內定的關鍵字(keyword),而Contact和my_contact都是我們自己取的名稱,雖然可以任意取名,但要取個有意義的名字才方便使用,命名規則與一般變數的命名規則相同。
-----------------------------------------------------------------------------------------
從struct到class:邁入「物件導向」的設計概念
C語言是種「程序導向」的語言,沒有物件導向的概念,但是C語言發明的struct直接導致了物件導向程式設計(Object-Oriented Programming)的誕生。
我們從剛才的解說中發現,C語言的struct,在結構化的資料型態內只能整合「變數」,無法整合「函數」。後來的程式語言發現了這件事情,因此把它加以改進,讓它也能整合(綁定)函數,並把擴充之後的struct改稱為「類別」(class)。
例如,C++語言最為知名的特色,就是它在C語言中加入了一整套物件導向的觀念,並自認為是C語言的升級版,所以取這個名字。雖然C++不是第一個有物件導向觀念的語言,但因為C++知名度和市占率很高,故後進者們都紛紛加入了物件導向的功能,並在JAVA發揚光大,遍地開花。
class作為struct的升級版(大致上可以這麼認為),class之內除了變數外還可以整合函數,讓class比struct更為廣義,實用性大為提升,甚至形成一個一個可以互動的基礎單元,奠定了更廣義的程式設計概念,使物件導向程式設計成為主流,直到今日。
我們把類別(class)的大括號{ }裡的變數稱為成員變數,或以物件導向的觀點來說,又稱為「屬性」(property)或欄位(field)。而類別的大括號{ }裡的函數稱為成員函數,或者以物件導向的觀點來說,又把成員函數稱為「使用方法」(method、或簡稱「方法」)。這雖然都只是名詞而已,但還是要明白,才知道別人在說什麼。
所以,理解C語言struct的觀念之後,再學習C++、Java、C#、Python…等「物件導向」的程式語言,就會覺得很容易理解了。
註:由於已經有class(類別)可用,所以Java和Python直接把struct功能給廢掉了,不過C++和C# 仍把它保留下來,但不常用到就是了。