結構 (structure) 是將數個相關的資料類型 (稱為欄位或成員) 集結起來組成一個衍生的資料類型, 讓程式設計者可以依據需要自訂資料類型. 結構裡面的欄位可以是同質的 (全部欄位之類型相同) 或異質的 (包含不同類型), 而且欄位也可以是另一個結構 (即巢狀結構).
例如公司的產品, 可以用包含型號 (字串), 品名 (字串), 與價格 (整數) 這三個欄位組成的結構來儲存; 好友通訊錄可以用包含姓名 (字串), 電話 (字串), 與生日 (字串) 等類型組成之結構來表示. 結構可讓資料的儲存更有系統.
1. 定義結構 :
C/C++ 使用 struct 關鍵字來定義結構, 語法如下 :
struct 結構名稱 {
資料類型 欄位名稱1;
資料類型 欄位名稱2;
....
};
注意, 每個欄位宣告後要用分號結束; 結尾大括號後面也要用分號結束. 定義一個結構就是在描述此新的資料型態是如何組成的, 它的成員可由既有的基本資料型態組成, 例如數值, 字串, 陣列, 指標, 甚至是另一個結構 (即巢狀結構, 結構裡面的成員是另一個結構). 其次, 結構的定義可以寫在 main() 裡面 (其他函式不可用), 也可以寫在 main() 外面 (其他函式可用).
例如定義一個記錄學生成績的結構 :
struct score {
char name[10];
int chinese;
int english;
int math;
int nature;
}
C++ 可以匯入 string 函式庫後使用 string 類型 :
struct score {
string name;
int chinese;
int english;
int math;
int nature;
}
2. 結構變數的宣告與賦值 :
定義一個結構後, 即可使用下列語法宣告結構變數 :
struct 結構名稱 結構變數名稱;
以上面定義的 score 結構為例宣告兩個結構變數 :
sctruc score score1, score2; //宣告了兩個結構變數 score1, scroe2
但這兩個變數目前內容是空的, 賦值的方式是透過小數點符號取得欄位, 對於數值欄位 (整數與浮點數) 而言, 直接用 = 運算子將欄位值指派給該欄位即可, 但字串欄位則必須匯入 string 函式庫後使用 strcpy() 函式來賦值, 例如 :
strcpy(score1.name, "Tony");
score1.chinese=90;
score1.english=87;
score1.math=77;
score1.nature=92;
也可以用陣列的大括弧來賦值, 裡面的值一一對應到宣告結構時之順序, 例如 :
struct score score2={"Amy", 98, 91, 56, 65}
使用此方式用不到 strcpy() 複製字串到欄位裏, 所以就不需要匯入 string 函式庫了. 結構成員的取值方式一律使用小數點符號與欄位名稱, 例如 score1.name, score2.nature 等. 如果要修改欄位內容, 除了字串需使用 strcpy() 外, 數值可直接用指派運算子 '=' 直接修改.
下面範例中宣告了兩個結構變數 score1 與 score2, 分別用宣告後再賦值與宣告同時賦值兩種方式來儲存資料 :
/* 建立結構和顯示結構內容 test01.c */
#include <stdio.h>
#include <string.h>
struct score { //定義結構
char name[10];
int chinese;
int english;
int math;
int nature;
};
int main() {
/* 宣告結構變數score1並使用.賦值 */
struct score score1;
strcpy(score1.name, "Tony");
score1.chinese=90;
score1.english=87;
score1.math=77;
score1.nature=92;
/* 顯示結構score1資料 */
printf("結構score1的內容:\n");
printf("姓名: %s\n", score1.name);
printf("國文: %d\n", score1.chinese);
printf("英文: %d\n", score1.english);
printf("數學: %d\n", score1.math);
printf("自然: %d\n", score1.nature);
/* 宣告結構變數score2並使用{}賦值 */
struct score score2={"Amy", 98, 91, 56, 65);
/* 顯示結構score2資料 */
printf("結構score2的內容:\n");
printf("姓名: %s\n", score2.name);
printf("國文: %d\n", score2.chinese);
printf("英文: %d\n", score2.english);
printf("數學: %d\n", score2.math);
printf("自然: %d\n", score2.nature);
return 0;
}
結構score1的內容:
姓名: Tony
國文: 90
英文: 87
數學: 77
自然: 92
結構score2的內容:
姓名: Amy
國文: 98
英文: 91
數學: 56
自然: 65
3. 結構陣列 :
陣列的元素為結構變數時, 此陣列變數稱為結構陣列, 其元素為在記憶體連續空間中的結構變數資料, 這與宣告一些相同定義之結構變數不同 (可能會放在不連續空間).
結構陣列變數宣告方式如下 :
struct 結構名稱 結構陣列名稱[長度];
在此之前要先用 struct 關鍵字宣告一個結構, 例如 :
struct score { //定義結構
char name[10];
int chinese;
int english;
int math;
int nature;
};
然後宣告含有兩個元素的結構陣列變數 scores[] :
struct score scores[2];
注意, 給每一個元素賦值時不可以用 {} 指派給元素 :
scores[0]={"Tony", 90, 87, 77, 92};
scores[1]={"Amy", 98, 91, 56, 65};
這種包含字串地內容要在宣告時同時賦值 :
struct score scores[2]={{"Tony", 90, 87, 77, 92}, {"Amy", 98, 91, 56, 65}}
完整範例程式如下 :
/* 建立結構陣列和顯示其內容 : test02.c */
#include <stdio.h>
#include <string.h>
struct score { //定義結構
char name[10];
int chinese;
int english;
int math;
int nature;
};
int main() {
/* 宣告結構陣列變數scores並賦值 */
struct score scores[2]; //宣告結構陣列變數
strcpy(scores[0].name, "Tony"); //字串要用 strcpy() 賦值
scores[0].chinese=90;
scores[0].english=87;
scores[0].math=77;
scores[0].nature=92;
strcpy(scores[0].name, "Amy"); //字串要用 strcpy() 賦值
scores[1].chinese=98;
scores[1].english=91;
scores[1].math=56;
scores[1].nature=65;
/* 顯示結構變數scores[0]資料 */
printf("結構score[0]的內容:\n");
printf("姓名: %s\n", scores[0].name);
printf("國文: %d\n", scores[0].chinese);
printf("英文: %d\n", scores[0].english);
printf("數學: %d\n", scores[0].math);
printf("自然: %d\n", scores[0].nature);
/* 顯示結構變數scores[1]資料 */
printf("結構score[1]的內容:\n");
printf("姓名: %s\n", scores[1].name);
printf("國文: %d\n", scores[1].chinese);
printf("英文: %d\n", scores[1].english);
printf("數學: %d\n", scores[1].math);
printf("自然: %d\n", scores[1].nature);
return 0;
}
結構score[0]的內容:
姓名: Amy
國文: 90
英文: 87
數學: 77
自然: 92
結構score[1]的內容:
姓名:
國文: 98
英文: 91
數學: 56
自然: 65
既然是陣列, 可以用迴圈讓程式更簡潔 :
/* 建立結構陣列和顯示其內容 : test03.c */
#include <stdio.h>
#include <string.h>
struct score { //定義結構
char name[10];
int chinese;
int english;
int math;
int nature;
};
int main() {
/* 宣告結構陣列變數scores並賦值 */
struct score scores[2]; //宣告結構陣列變數
strcpy(scores[0].name, "Tony"); //字串要用 strcpy() 賦值
scores[0].chinese=90;
scores[0].english=87;
scores[0].math=77;
scores[0].nature=92;
strcpy(scores[0].name, "Amy"); //字串要用 strcpy() 賦值
scores[1].chinese=98;
scores[1].english=91;
scores[1].math=56;
scores[1].nature=65;
/* 顯示結構變數資料 */
int i;
for (i=0; i<2; i++) {
printf("結構score[%d]的內容:\n", i);
printf("姓名: %s\n", scores[i].name);
printf("國文: %d\n", scores[i].chinese);
printf("英文: %d\n", scores[i].english);
printf("數學: %d\n", scores[i].math);
printf("自然: %d\n", scores[i].nature);
}
}
4. 結構指標 :
指標可以指向任何類型的資料, 指向結構開始位址的指標稱為結構指標, 只要在宣告結構變數時, 在變數名稱前面加個星號, 此指標所存之位址即指向一個結構.
結構指標語法如下 :
struct *結構指標名稱;
首先宣告一個結構 score :
struct score { //定義結構
char name[10];
int chinese;
int english;
int math;
int nature;
};
接著宣告一個結構變數 score1 :
struct score score1={"Tony", 90, 87, 77, 92};
然後宣告一個結構指標 ptr, 並將結構 score1 的位址指派給 *ptr :
struct score *ptr=&score1;
這樣結構指標 ptr 就指向結構 score1 了.
存取結構指標所指向的內容有兩種做法 :
- 使用指標所指內容搭配點運算子 : (*ptr).欄位名稱
由於存取欄位的小數點運算子比指標運算子優先權高, 因此要先用括號將指標內容括起來, 例如要用 (*ptr).name 而非 *ptr.name (這會編譯錯誤). - 使用指標名稱搭配 -> 運算子 : ptr->欄位名稱
注意, 使用 -> 時要用指標名稱, 不是所指向的內容 (*ptr).
下面範例指標內容搭配點運算子來存取結構之欄位 :
/* 建立結構陣列和顯示其內容 : test04.c */
#include <stdio.h>
struct score { //定義結構
char name[10];
int chinese;
int english;
int math;
int nature;
};
int main() {
/* 宣告結構變數score1並賦值 */
struct score score1={"Tony", 90, 87, 77, 92};
struct score *ptr=&score1;
/* 顯示結構變數score1資料 */
printf("結構score1的內容:\n");
printf("姓名: %s\n", score1.name);
printf("國文: %d\n", score1.chinese);
printf("英文: %d\n", score1.english);
printf("數學: %d\n", score1.math);
printf("自然: %d\n", score1.nature);
/* 使用指標所指內容(*ptr)搭配點運算子來顯示結構資料 */
printf("結構指標*ptr的內容:\n");
printf("姓名: %s\n", (*ptr).name);
printf("國文: %d\n", (*ptr).chinese);
printf("英文: %d\n", (*ptr).english);
printf("數學: %d\n", (*ptr).math);
printf("自然: %d\n", (*ptr).nature);
return 0;
}
此例使用指標內容 (*ptr) 搭配點運算子來存取結構欄位.
結構score1的內容:
姓名: Tony
國文: 90
英文: 87
數學: 77
自然: 92
結構指標*ptr的內容:
姓名: Tony
國文: 90
英文: 87
數學: 77
自然: 92
可見由於 ptr 指向 score1, 因此兩個內容完全一樣, 更改 (*ptr) 的任一欄位同時也會改到 score1 的欄位內容.
下面範例指標名稱搭配 -> 運算子來存取結構之欄位 :
/* 建立結構陣列和顯示其內容 : test05.c */
#include <stdio.h>
struct score { //定義結構
char name[10];
int chinese;
int english;
int math;
int nature;
};
int main() {
/* 宣告結構變數score1並賦值 */
struct score score1={"Tony", 90, 87, 77, 92};
struct score *ptr=&score1;
/* 顯示結構變數score1資料 */
printf("結構score1的內容:\n");
printf("姓名: %s\n", score1.name);
printf("國文: %d\n", score1.chinese);
printf("英文: %d\n", score1.english);
printf("數學: %d\n", score1.math);
printf("自然: %d\n", score1.nature);
/* 使用指標名稱搭配 -> 運算子顯示結構資料 */
printf("結構指標*ptr的內容:\n");
printf("姓名: %s\n", ptr->name);
printf("國文: %d\n", ptr->chinese);
printf("英文: %d\n", ptr->english);
printf("數學: %d\n", ptr->math);
printf("自然: %d\n", ptr->nature);
return 0;
}
執行結果與前面範例一樣.
5. 巢狀結構 :
結構的成員是另一個結構稱為巢狀結構, 語法如下 :
struct 結構名稱1 {
資料類型 欄位名稱1;
資料類型 欄位名稱2;
....
};
struc 結構名稱2 {
資料類型 欄位名稱1;
struct 結構名稱1 結構變數名稱;
....
};
例如 :
struct name { //定義結構 name
char first_name[10];
char last_name[10];
};
struct score { //定義結構 score
struct name student_name; // 此欄位為另一個結構 name
int chinese;
int english;
int math;
int nature;
};
此處定義了兩個結構 name 與 score, 其中 score 的第一個成員是 name 結構, 形成巢狀結構. 接著宣告兩個 score 結構變數 score1 與 score2 :
struct score score1={{"Tony", "Huang"}, 90, 87, 77, 92};
struct score score2={{"Amy", "Chen"}, 98, 91, 56, 65);
因為第一個成員是另一個結構 name, 因此要用 {} 賦值. 存取內層結構 name 的成員時必須使用兩層的點運算子, 例如 :
score1.student_name.first_name
score1.student_name.last_name
score2.student_name.first_name
score2.student_name.last_name
完整範例如下 :
/* 建立巢狀結構和顯示其內容 : test06.c */
#include <stdio.h>
struct name { //定義結構 name
char first_name[10];
char last_name[10];
};
struct score { //定義結構 score
struct name student_name;
int chinese;
int english;
int math;
int nature;
};
int main() {
/* 宣告結構變數score1並賦值 */
struct score score1={{"Tony", "Huang"}, 90, 87, 77, 92};
struct score score2={{"Amy", "Chen"}, 98, 91, 56, 65};
/* 顯示結構變數score1資料 */
printf("結構score1的內容:\n");
printf("姓名: %s %s\n", score1.student_name.first_name, score1.student_name.last_name);
printf("國文: %d\n", score1.chinese);
printf("英文: %d\n", score1.english);
printf("數學: %d\n", score1.math);
printf("自然: %d\n", score1.nature);
/* 顯示結構變數score1資料 */
printf("結構score2的內容:\n");
printf("姓名: %s %s\n", score2.student_name.first_name, score1.student_name.last_name);
printf("國文: %d\n", score2.chinese);
printf("英文: %d\n", score2.english);
printf("數學: %d\n", score2.math);
printf("自然: %d\n", score2.nature);
return 0;
}
結構score1的內容:
姓名: Tony Huang
國文: 90
英文: 87
數學: 77
自然: 92
結構score2的內容:
姓名: Amy Huang
國文: 98
英文: 91
數學: 56
自然: 65
6. 將結構傳入函式 :
結構作為一種自訂的資料型態, 當然也可以當引數傳給函式, 但要注意, 直接將結構傳入函式中時使用的是傳值呼叫, 亦即函式接收到的參數只是一個副本, 在函式內對此副本的存取不會影響到外部的結構內容, 例如 :
/* 將結構傳入函式和顯示其內容 : test07.c */
#include <stdio.h>
#include <string.h>
struct score { //定義結構
char name[10];
int chinese;
int english;
int math;
int nature;
};
void set_score(struct score s); // 函式原型宣告必須在結構定義之後
int main() {
/* 宣告結構變數score1並賦值 */
struct score score1={"Tony", 90, 87, 77, 92};
set_score(score1); // 這是傳值呼叫
/* 顯示函式外結構變數score1資料 */
printf("函式外結構score1的內容:\n");
printf("姓名: %s\n", score1.name);
printf("國文: %d\n", score1.chinese);
printf("英文: %d\n", score1.english);
printf("數學: %d\n", score1.math);
printf("自然: %d\n", score1.nature);
return 0;
}
void set_score(struct score s) {
/* 設定結構變數s資料 */
strcpy(s.name, "Amy");
s.chinese=98;
s.english=91;
s.math=56;
s.nature=65;
/* 顯示函式內結構變數s資料 */
printf("函式內結構變數s的內容:\n");
printf("姓名: %s\n", s.name);
printf("國文: %d\n", s.chinese);
printf("英文: %d\n", s.english);
printf("數學: %d\n", s.math);
printf("自然: %d\n", s.nature);
}
此例宣告了一個結構變數 score1, 然後將其作為引數傳入函式 set_score() 中, 由於直接將結構傳入函式為傳值呼叫, 傳入之參數為結構之副本, 故於函式內設定該結構副本之欄位對函式外部的結構毫無影響.
函式內結構變數s的內容:
姓名: Amy
國文: 98
英文: 91
數學: 56
自然: 65
函式外結構score1的內容:
姓名: Tony
國文: 90
英文: 87
數學: 77
自然: 92
可見函式內的結構雖然被改成 Amy 的成績, 但函式外的結構仍然是 Tony 的成績. 如果要在函式內更改函式外的結構變數內容, 必須使用結構指標, 例如 :
/* 將結構指標傳入函式和顯示其內容 : test08.c */
#include <stdio.h>
#include <string.h>
struct score { //定義結構
char name[10];
int chinese;
int english;
int math;
int nature;
};
void set_score(struct score *ptr);
int main() {
/* 宣告結構變數score1並賦值 */
struct score score1={"Tony", 90, 87, 77, 92};
struct score *ptr; // 宣告結構指標變數
ptr=&score1; // 取得score1位址指定給指標ptr
set_score(ptr); // 這是傳址呼叫
/* 顯示函式外結構變數score1資料 */
printf("函式外結構score1的內容:\n");
printf("姓名: %s\n", score1.name);
printf("國文: %d\n", score1.chinese);
printf("英文: %d\n", score1.english);
printf("數學: %d\n", score1.math);
printf("自然: %d\n", score1.nature);
return 0;
}
void set_score(struct score *ptr) {
/* 設定結構變數s資料 */
strcpy((*ptr).name, "Amy");
(*ptr).chinese=98;
(*ptr).english=91;
(*ptr).math=56;
(*ptr).nature=65;
/* 顯示函式內結構變數s資料 */
printf("函式內結構變數s的內容:\n");
printf("姓名: %s\n", (*ptr).name);
printf("國文: %d\n", (*ptr).chinese);
printf("英文: %d\n", (*ptr).english);
printf("數學: %d\n", (*ptr).math);
printf("自然: %d\n", (*ptr).nature);
}
函式內結構變數s的內容:
姓名: Amy
國文: 98
英文: 91
數學: 56
自然: 65
函式外結構score1的內容:
姓名: Amy
國文: 98
英文: 91
數學: 56
自然: 65
可見由於傳入函式的引數是結構指標, 因此在函式內更改結構內容也就是更改函式外結構變數 score1 的內容, 所以列印的結果完全相同. 如上所述, 也可以使用結構專用的 -> 運算子來存取結構成員, 例如以 ptr->name 取代 (*ptr).name :
/* 將結構指標傳入函式和顯示其內容 : test09.c */
#include <stdio.h>
#include <string.h>
struct score { //定義結構
char name[10];
int chinese;
int english;
int math;
int nature;
};
void set_score(struct score *ptr);
int main() {
/* 宣告結構變數score1並賦值 */
struct score score1={"Tony", 90, 87, 77, 92};
struct score *ptr; // 宣告結構指標變數
ptr=&score1; // 取得score1位址指定給指標ptr
set_score(ptr); // 這是傳址呼叫
/* 顯示函式外結構變數score1資料 */
printf("函式外結構score1的內容:\n");
printf("姓名: %s\n", score1.name);
printf("國文: %d\n", score1.chinese);
printf("英文: %d\n", score1.english);
printf("數學: %d\n", score1.math);
printf("自然: %d\n", score1.nature);
}
void set_score(struct score *ptr) {
/* 設定結構變數s資料 */
strcpy(ptr->name, "Amy");
ptr->chinese=98;
ptr->english=91;
ptr->math=56;
ptr->nature=65;
/* 顯示函式內結構變數s資料 */
printf("函式內結構變數s的內容:\n");
printf("姓名: %s\n", ptr->name);
printf("國文: %d\n", ptr->chinese);
printf("英文: %d\n", ptr->english);
printf("數學: %d\n", ptr->math);
printf("自然: %d\n", ptr->nature);
}
結果與上面用 (*ptr). 作法結果相同.
7. 用 typedef 定義新資料型態 :
型態定義關鍵字 typedef 用來為既有之資料型態取別名, 語法如下 :
typedef 原資料型態名稱 新資料型態名稱
這經常被用在結構這種自訂資料型態上, 可使結構變數之宣告變得較簡短, 不必每次宣告一個結構變數時都要冠上 struct 關鍵字, 例如上面範例中所定義的結構 :
struct score { //定義結構
char name[10];
int chinese;
int english;
int math;
int nature;
};
當宣告結構變數時必須使用 struct 關鍵字 :
struct score score1, score2;
如果用 typedef 為此結構取一個型態別名 :
typedef struct score myscore; // 為結構 score 取一個型態別名 myscore
則宣告 score 結構變數時便可直接使用 myscore 型態 :
myscore score1, score2;
測試範例如下 :
/* 用typedef為結構取一個別名 : test10.c */
#include <stdio.h>
struct score { //定義結構
char name[10];
int chinese;
int english;
int math;
int nature;
};
int main() {
typedef struct score myscore; //用typedef為結構score取別名myscore
/* 宣告結構變數score1並賦值 */
myscore score1={"Tony", 90, 87, 77, 92}; //用myscore宣告結構變數
struct score score2={"Amy", 98, 91, 56, 65}; //用struct score宣告結構變數
/* 顯示函式外結構變數score1資料 */
printf("結構score1的內容:\n");
printf("姓名: %s\n", score1.name);
printf("國文: %d\n", score1.chinese);
printf("英文: %d\n", score1.english);
printf("數學: %d\n", score1.math);
printf("自然: %d\n", score1.nature);
/* 顯示函式外結構變數score2資料 */
printf("結構score2的內容:\n");
printf("姓名: %s\n", score2.name);
printf("國文: %d\n", score2.chinese);
printf("英文: %d\n", score2.english);
printf("數學: %d\n", score2.math);
printf("自然: %d\n", score2.nature);
}
此例用 typedef 為結構 socre 取了一個型態別名 myscore, 然後分別用 myscore 與原始的 struct score 方式宣告了兩個結構變數 score1 與 score2, 顯然用新型態 myscore 較簡短, 以線上編譯器
onlinGDB 執行結果如下 :
結構score1的內容:
姓名: Tony
國文: 90
英文: 87
數學: 77
自然: 92
結構score2的內容:
姓名: Amy
國文: 98
英文: 91
數學: 56
自然: 65
也可以在定義結構時就用 typedef 為結構取一個型態別名, 語法如下 :
typedef struct 結構名稱 {
資料類型 欄位名稱1;
資料類型 欄位名稱2;
....
} 新型態名稱;
例如 :
typedef struct score { //定義結構並取一個新型態名稱 myscore
char name[10];
int chinese;
int english;
int math;
int nature;
} myscore;
這樣既可以用原始的 struct score 來宣告結構變數, 也可以用 myscore 來宣告 :
struct score score1;
myscore score2;
可將上面範例修改為 :
/* 用typedef為結構取一個別名 : test11.c */
#include <stdio.h>
typedef struct score { //定義結構並取一個新型態名稱
char name[10];
int chinese;
int english;
int math;
int nature;
} myscore;
int main() {
/* 宣告結構變數score1並賦值 */
myscore score1={"Tony", 90, 87, 77, 92}; //用myscore宣告結構變數
struct score score2={"Amy", 98, 91, 56, 65}; //用struct score宣告結構變數
/* 顯示函式外結構變數score1資料 */
printf("結構score1的內容:\n");
printf("姓名: %s\n", score1.name);
printf("國文: %d\n", score1.chinese);
printf("英文: %d\n", score1.english);
printf("數學: %d\n", score1.math);
printf("自然: %d\n", score1.nature);
/* 顯示函式外結構變數score2資料 */
printf("結構score2的內容:\n");
printf("姓名: %s\n", score2.name);
printf("國文: %d\n", score2.chinese);
printf("英文: %d\n", score2.english);
printf("數學: %d\n", score2.math);
printf("自然: %d\n", score2.nature);
}
執行節果與上一個範例相同.
事實上 typedef 可以用來為任何資料型態取別名, 例如為 int 型態取 integer 別名 :
typedef int integer;
integer year=2023;
typedef char* string;
string="Hello World!"
注意, 字串指標要用 char* 而非 *char.
完整範例如下 :
/* 用typedef為int與*char取一個別名 : test13.c */
#include <stdio.h>
int main() {
typedef int integer; // 為int型別取別名integer
integer year=2023; // 用integer宣告整數變數
printf("%d\n", year);
typedef char* string; // 為*char型別取別名string
string str="Hello World!"; // 用string宣告字串變數
printf("%s\n", str);
return 0;
}
執行結果如下 :