640 likes | 762 Views
Chap 15 struct 與資料結構. struct 可以在同一個名稱下擁有多種資料型態。使用 struct 能讓資料的存取和處理更為靈活。. struct 與資料結構. 15.1 struct 的宣告和使用 15.2 由 struct 構成的陣列 15.3 struct 資料型態與函數參數的傳遞 15.4 struct 實例的動態宣告 15.5 指標成員與資料結構 15.6 union 資料型態 15.7 enum 資料型態. struct 的宣告和使用. 組成份子稱為 成員 (member) 或 資料欄位 (data field) 。
E N D
Chap 15 struct與資料結構 struct 可以在同一個名稱下擁有多種資料型態。使用struct能讓資料的存取和處理更為靈活。
struct與資料結構 • 15.1struct的宣告和使用 • 15.2 由struct構成的陣列 • 15.3struct資料型態與函數參數的傳遞 • 15.4struct實例的動態宣告 • 15.5 指標成員與資料結構 • 15.6union資料型態 • 15.7enum資料型態
struct的宣告和使用 • 組成份子稱為成員(member) 或資料欄位(data field)。 • 成員可以是各種不同的資料型態 (複合式資料型態)。 • 關鍵字struct是英文 structure (結構) 的縮寫,此種資料結構又稱為記錄(record)。
struct資料型態的宣告範例 • Employee包括的資料成員有Name (姓名)、Phone (電話號碼) 以及Id (編號) 三種: struct Employee { char Name[20]; char Phone[10]; int Id; }; // 注意這要用到「;」!
Struct 在記憶體中的儲存方式 • 各成員的儲存位置是連續的:
使用struct 資料型態定義變數 • 可以使用標準的定義敘述。例如: Employee Ea, Eb; • 定義了兩個名稱分別為Ea和Eb的Employee變數。 • 由某一資料型態定義的變數稱為該資料型態的實例 (instance)。
定義變數時一併給予初始值 • 例如,上面的敘述可進一步寫成: EmployeeEa = {"Ann", "02384125", 105}; EmployeeEb = {"Joanne", "03544132", 106};
要存取Employee變數的個別資料欄位 • 必需同時給定變數名稱和資料成員名稱,中間用一個成員運算符號(member operator)「.」隔開。例如: Ea.Name // 其值目前分別為 “Ann” Eb.Phone // 其值目前分別為“02384125” Ea.Id // 其值目前分別為105 • 分別用來代表Ea這個Employee變數的三個成員,其值目前分別為“Ann”,“02384125”和105。這個語法基本上和我們在10.1節介紹的成員函數的語法是一致的。
範例程式TestStruct.cpp • 如何使用struct宣告自訂的資料型態,以及各欄位內的資料如何存取。
範例程式 檔案 TestStruct.cpp // TestStruct.cpp #include <iostream> using namespace std; struct Employee { char Name[20]; char Phone[10]; int Id; }; // ----- 主程式 ----------------------------
int main() { EmployeeEa= {"Ann", "02384125", 105}; Employee Eb = {"Joanne", "03544132", 106}; cout << "Ea 的資料是:\n" << "姓名 : " << Ea.Name << '\n' << "電話號碼: " << Ea.Phone << '\n' << "編號 : " << Ea.Id << endl; cout << "Eb 的資料是:\n" << "姓名 : " << Eb.Name << '\n' << "電話號碼: " << Eb.Phone << '\n' << "編號 : " << Eb.Id << endl; return 0; }
合併struct資料型態的宣告和變數的定義 • 例如: struct { char Name[20]; char Phone[10]; int Id; } Ea, Eb; • 由於粗體字的部份本身就是已經是新定義的資料型態之具體內容,不用再取個名稱來代表它。
由struct構成的陣列 • 結合陣列和struct,可以一次完成很多具有相同結構的struct變數的定義。 • 例如,可以使用 Employee Officer[50]; • 同時定義從Officer[0] 到Officer[49],共50個Employee變數
struct陣列各欄位的資料 cout << Officer[8].Name << endl; cout << Officer[12].Phone << endl; cout << Officer[40].Id << endl;
範例程式StructArray.cpp • 允許使用者逐一輸入各陣列元素的各成員值 (每輸入一個項目後,要按兩次Enter鍵)。
範例程式 檔案 StructArray.cpp // StructArray.cpp #include <iostream> using namespace std; const int NameSize = 20; const int PhoneSize = 10; struct Employee { char Name[NameSize]; char Phone[PhoneSize]; }; int main() { const int Size = 2; Employee Officer[Size]; cout << "共 " << Size << " 個 Officers:\n";
for (int i=0; i<Size; i++) { cout << "請輸入 Officer[" << I<< "] 的姓名: "; cin.getline(Officer[i].Name, NameSize, '\n'); cout << "電話號碼: "; cin.getline(Officer[i].Phone, PhoneSize, '\n'); } for (int i=0; i<Size; i++) { cout << "Officer[" << i << "] 的資料是:\n" << "姓名 : " << Officer[i].Name << '\n' << "電話號碼: " << Officer[i].Phone << '\n'; } return 0; }
struct資料型態與函數參數的傳遞 • 由struct所定義的實例被用來做為參數傳遞時,其預設的語意是傳值(pass-by-value)。 • 也就是說,在「被呼叫函數」內部將另外產生一個複製資料,而不會影響「呼叫函數」內的資料。
用struct所定義的實例來傳遞參數 • 例如,呼叫敘述可以寫成: ShowMember(Ea); • 而被呼叫函數則可以定義成: void ShowMember(Employee A) { cout << "資料的詳細內容是:\n" << "姓名 : " << A.Name << '\n' << "電話號碼: " << A.Phone << '\n' << "編號 : " << A.Id << endl; return; }
使用傳參照 (pass by reference) 改變struct實例的內容 • 例如: ChangeName(Ea, “Jackson”); • 對應的「被呼叫函數」則定義成: void ChangeName (Employee& A, char NewName[]) { strcpy(A.Name, NewName); return; }
範例程式 檔案 StructFnc.cpp // StructFnc.cpp #include <iostream> using namespace std; struct Employee { char Name[20]; char Phone[10]; int Id; }; void ShowMember(Employee A) { cout << "資料的詳細內容是:\n" << "姓名 : " << A.Name << '\n' << "電話號碼: " << A.Phone << '\n' << "編號 : " << A.Id << endl; return; }
void ChangeName (Employee& A, char NewName[]) { strcpy(A.Name, NewName); return; } // ============= 主程式 ======================== int main() { Employee Ea = {"Ann", "02384125", 105}; Employee Eb = {"Joanne", "03544132", 106}; ShowMember(Ea); ShowMember(Eb); ChangeName(Ea, "Jackson"); cout << "執行 ChangeName() 後:\n"; ShowMember(Ea); return 0; }
使用指標改變struct實例的內容 • 使用傳址(pass-by-address) 來達到使用參照的目的: ChangeId(&Ea, 00128); • 「被呼叫函數」則定義為 void ChangeId(Employee* pE, int NewId) { (*pE).Id=NewId; return; }
C++ 的具象指標符號 -> • 將 (*pE).Name寫成: pE->Name • 表示「由pE指向的struct變數內的成員 Name」。
進一步改寫 ChangeId() void ChangeId(Employee* pE, int NewId) { pE->Id = NewId; return; }
範例程式 檔案 StructFnc2.cpp // StructFnc2.cpp #include <iostream> using namespace std; struct Employee { char Name[20]; char Phone[10]; int Id; }; void ShowMember(Employee A) { cout << "資料的詳細內容是:\n" << "姓名 : " << A.Name << '\n' << "電話號碼: " << A.Phone << '\n' << "編號 : " << A.Id << endl; return; }
void ChangeName (Employee& A, char NewName[]) { strcpy(A.Name, NewName); return; } void ChangeId(Employee* pE, int NewId) { pE->Id = NewId; return;} // ========= 主程式 ======================== int main() { Employee Ea = {"Ann", "02384125", 105}; Employee Eb = {"Joanne", "03544132", 106}; ShowMember(Ea); ShowMember(Eb); ChangeId(&Ea, 208); cout << "執行 ChangeId() 後:\n"; ShowMember(Ea); return 0; }
struct實例的動態宣告 • 亦即struct實例的動態記憶體配置(dynamic memory allocation)。 • 下列敘述則可以在執行時才臨時決定陣列的大小: int Size; cin >> Size; Employee* pE = new Employee[Size];
struct實例的動態記憶體配置和回收 • 執行後會依指定的大小在記憶體的特殊區域,稱為記憶堆(heap) 的地方,規劃出需要的記憶空間,並把第一個變數的開頭位址存入指標內。 • 如果此陣列不再需要,可以執行下列的敘述回收記憶體空間: delete [] pE;
使用陣列下標或指標算數存取內部成員 • 例如,要取用第k個陣列元素內的成員Id,下述語法都是正確的: Labor[k].Id (*(Labor + k)).Id (Labor + k)->Id pE[k].Id (*(pE + k)).Id (pE + k)->Id
範例程式DynStruct.cpp • 示範動態產生由struct實例所構成的陣列,稱為Employee,之完整語法,並在事後回收記憶空間。
範例程式 檔案 DynStruct.cpp // DynStruct.cpp #include <iostream> using std::cin; using std::cout; struct Employee { char Name[20]; char Phone[10]; int Id; }; // --------- 主程式 ------------------------
int main() { int Size; cout << "請輸入 Employee 的數目:\n"; cin >> Size; Employee* pE = new Employee[Size]; delete [] pE; return 0; }
指標成員與資料結構 • struct所宣告的資料型態可以使用「指標」做為成員。 • 指標成員可以指向自己所在的struct資料型態,稱為「自我參照」(auto-reference)。例如: Struct Data { int Id; Data* pD; };
串列 (lists) • 定義一串的Data變數: Data D1, D2, D3; D1.pD = &D2; D2.pD = &D3; • 圖示如下: • 串列最後一個元素內的指標值為NULL (亦即 ‘\0’),用來做為檢查串列是否「到此為止」的根據。
串列 (lists) • 「節點」(node): 每一個用來儲存資料的元素第一個節點稱為 • 「開頭」(head): 第一個節點。 • 「結尾」(tail): 最後的節點。
在陣列插入一個元素 int* V = new int [Size]; • 必需同時將V[1] 及其之後的所有元素往右移,且無法應付陣列因長度增加而記憶空間可能不足的問題。
「串列」可以帶來的便利 • 使用串列(linked list) 可以較有效率地完成元素增刪的動作。 • 設想原先有A, B, C 三個元素串接在一起形成一個串列:
一個串列的範例 • 假設使用struct宣告了一個名叫 Element 的自訂資料形態: struct Element { int Value; Element* Next; };
動態產生任意數目的Element (各實例的值在此為0, 2, 4, 6, …): cout << "請輸入 Element 的數目:\n"; cin >> Size; Element* pE = new Element[Size]; for (int i=0; i<(Size-1); i++) pE[i].Next = pE + i +1; pE[Size-1].Next = NULL; for (int i=0; i<(Size); i++) pE[i].Value = i*2;
顯示現有串列元素 Element* pShow; for (pShow = pE; pShow != NULL; pShow=pShow->Next) cout << pShow->Value << ' '; • 不斷更換指標使它指向下一個元素的位址。
以while迴圈顯示現有串列元素 Element* pShow=pE; while (pShow != NULL) { cout << pShow->Value << ' '; pShow = pShow->Next; }
將顯示串列內容的功能封裝到函數中 void ShowElement(Element* pShow) { while (pShow != NULL) { cout << pShow->Value << ' '; pShow = pShow->Next; } }
範例程式 檔案 ListStruct.cpp // ListStruct.cpp #include <iostream> using namespace std; struct Element { int Value; Element* Next; }; void ShowElement(Element* pShow) { while (pShow != NULL) { cout << pShow->Value << ' '; pShow = pShow->Next; } }