結構:自訂不同資料型態串在一起
在設計程式的過程中,經常遇到一組變數需要宣告在一起,比如說學號、姓名、性別、年齡、地址、成績等變數,全都是用來描述一個學生:
int id; //學號為整數型 char name[8]; //姓名為字元陣列 char sex; //性別為字元型 int age; //年齡為整數型 char addr[30]; //地址為字元陣列 float score; //成績為浮點型
有時候我們就想要把這組變數綁在一起、讓它看起來更像是一體的,使變數之間的關聯變得更直接。
C 語言裡面有一個辦法能做到,叫 strutct (結構)。
在這組變數前面加上 struct、用大括號包起來:
struct student{ //名稱為student的結構 int id; //學號為整數型 char name[8]; //姓名為字元陣列 char sex; //性別為字元型 int age; //年齡為整數型 char addr[30]; //地址為字元陣列 float score; //成績為浮點型 }
從現在開始,我們已經成功利用結構、宣告了一個新的資料類型「student」。
在這邊必須強調的是,此處的「student」並不是一個變數,而是一種資料型態。
什麼意思啊?
也就是說這個 student 和 int, float, char 等資料類型一樣,是一種「用來宣告變數」的資料型態。
所以說,結構 (struct) 就是一種由使用者自訂之資料型態。
在這邊我們自己定義了一種資料型態叫「student」,裡面包括了 int、chat、float 等型態。
宣告一個結構變數
接下來,就可以使用這個新訂定出來的資料型態,來宣告變數了。
宣告的方式,和我們之前宣告 int 變數和 float 變數、char 變數的方法都一樣,前面放「型別」、後面放「變數名稱」:
int a; //宣告一個int變數a float b; //宣告一個flaot變數b char world; //宣告一個char變數world student Amy, John; //宣告兩個student變數Amy和Jjohn
我們也可以直接把變數宣告在結構的後面:
struct student{ //名稱為student的結構 int id; //學號為整數型 char name[8]; //姓名為字元陣列 char sex; //性別為字元型 int age; //年齡為整數型 char addr[30]; //地址為字元陣列 float score; //成績為浮點型 }Amy, John;
宣告完之後,該如何使用呢?
可以發現使用的方式和 Array 很像噢!
#include <stdio.h> #include <ctype.h> //引入字元測試與轉換函數標頭檔 struct student{ int id; char name[10]; }; int main(void) { student john = {291, {'j', 'o', 'h', 'n', '\0'}}; printf("學號:%d\n", john.id); printf("姓名:%s\n", john.name); printf("\n"); john.id = 1000 + john.id; //把原學號 +1000 for(int i = 0; john.name[i] != '\0'; i++){ //只要變數不是\0 john.name[i] = toupper(john.name[i]); //就把john陣列中的小寫都變大寫 } printf("新學號:%d\n", john.id); //印出改過後的學號 printf("新姓名:%s\n", john.name); //印出改過後的名字 return 0; }
來看看結果:
使用結構作為函數的參數
#include <stdio.h> #include <ctype.h> //引入字元測試與轉換函數標頭檔 struct student{ int id; char name[10]; }; void new_student(student new_one){ new_one.id = 1000 + new_one.id; for(int i = 0; new_one.name[i] != '\0'; i++){ new_one.name[i] = toupper(new_one.name[i]); } printf("新學生id:%d\n", new_one.id); //把new_one的值印出來 printf("新學生姓名:%s\n", new_one.name); //把new_one的值印出來 }; int main(void) { student john = {291, {'j', 'o', 'h', 'n', '\0'}}; new_student(john); printf("\n"); printf("學號:%d\n", john.id); //把john的值印出來 printf("姓名:%s\n", john.name); //把john的值印出來 }
我們同樣利用 struct 建好一個叫 student 的型別,接下來在 main 函數中宣告一個 student 變數 john,並給他一個初始值:291, john。
接下來把 john 這個變數當成一個參數,給 new_student 這個函數。
在 new_student 函數裡面,我又宣告了一個新的參數名稱叫 new_one,然後用 new_one 來接受傳進來的 john 的值。
接下來就利用 new_one 來調整原有 id 和 name 變數的值。最後把 new_one 的值印出來。
因為 new_student 這個函數是 void 型,所以不用回傳 main function 任何值。
最後再把原本的 john id和 name印出來,會發現和 new_one 完全不同。
實際上的運行過程,是把 main() 函數中變數初始化、複製一份給 new_student 裡面的參數 new_one 。
由於 main 函數把 john 複製了一份,給 new_student 裡面的 new_one,所以接下來對 one 做的任何修改都不會影響到主函數 main() 裡面的 john。
因為它們已經是兩個獨立的變數了。到最後 new_one 印 new_one 的、 john 印 john 的,不會互相干擾。
這個程式驗證了一個結論:
把一個結構變數當作參數、傳遞給一個函數的時候,實際上是把這個變數複製一份傳遞給這個函數。
也就是說函數會拿到的是這個變數的副本。
結構作為函數返回值
當我們把一個結構變數作為被呼叫函數的返回值,從一個函數裡面、return 給呼叫它的人的時候,其實是和參數的傳入一樣。
這個函數也會複製一個副本、然後把副本返回給呼叫它的人。
來看看這個程式吧:
#include <stdio.h> struct student{ int age; char name[10]; }; student newone(){ student one = {23, {'L', 'Y', 'N', 'N', '\0'}}; return one; }; int main(){ student lynn = newone(); printf("%d \n", lynn.age); printf("%s \n", lynn.name); return 0; }
我們先宣告了一個結構 student,然後在主函數中、宣告了一個結構變數 lynn,但沒有對它進行初始化。
而是直接把被呼叫函數 newone 的返回值、賦值給 lynn。
接下來把 lynn 的值印出來、驗證看看。如果 newone 的確能返回值給 lynn 的話,那麼在這邊我們就能把值印出來:
函數 newone 的裡面,我們宣告了一個局部變數 one,然後對 one 進行了初始化。
由於 one 是一個局部變數,所以說 one 這個變數作用僅限於函數的內部。當函數被呼叫完之後,one 就不存在了。
當完成初始化 one 後呢,newone 函數直接把 one 給 return 回呼叫函數的 main()。
系統在這個地方,會把 one 再複製一份副本,把副本返回給它的呼叫者。
具體來說,這個程式的執行過程如下:
在一片記憶體的區域中,main 函數會開始執行、呼叫了 newone 函數。
在 newone 函數中宣告並初始化一個局部變數 one。
在函數執行完畢的時候,把 one 變數當作返回值──也就是複製一份副本,把副本返回給 main 主函數。
也就是說在 main 主函數裡面得到的副本,和原來 newone 函數裡面的局部變數 one 分別占用的是兩個不同的記憶體空間。
可以看到在 main 函數裡面,我們並沒有對 lynn 進行初始化,但呼叫完之後就印出了一個初始化好的 lynn。
這和把結構變數作為函數參數的情況一樣。
我們在寫程式的很多時候,都可以利用結構變數這樣的特點。