2018年2月14日 星期三

《筆記》C語言 - 09 :結構定義、結構變數宣告;初始化、存取結構成員、結構陣列變數宣告、結構內含結構

在設計程式的過程中,經常遇到一組變數需要宣告在一起,比如說學號、姓名、性別、年齡、地址、成績等變數,全都是用來描述一個學生

有時候我們就想要把這組變數綁在一起、讓它看起來更像是一體的,使變數之間的關聯變得更直接

C 語言裡面有一個辦法能做到,叫 struct (結構)

結構( structure ) 會將一些彼此相關的變數結合成相同的名稱,可以含有許多不同資料名別的變數。相較之下,陣列僅能夠含有相同資料型別的元素。

結構是一種衍生的資料型別(也可以說是你自創了一個數據型別),它是以其它型別的物件來進行建構的

建立一個結構的定義

#include "stdio.h"

int main() {

    struct student{
     char name[8];
     int student_id;
     int chinese;
     int english;
     int math;
    };

}

保留字 struct 開始一個結構的定義識別字 student 稱為結構標籤( structure tag ) 

結構的標籤即為結構定義的名稱,它可和保留字 struct 組合起來成為 struct student 用來宣告該結構型別( structure type ) 的變數

在這個例子中, struct student 是一個我們自己創建的資料型別(結構型別)宣告在結構內的變數稱為該結構的成員( member ) 。在同一個結構內的成員彼此之間必須有不同的名稱,每個結構定義都必須在右大括弧後方加上分號代表結構定義結束。

在此我們就完成一個結構型別的建立,但這僅僅只有建立一個型態而已(也就是一個藍圖)

結構變數的宣告

#include "stdio.h"

int main() {

    //結構變數的宣告(法一)
    struct student{
     char name[8];
     int student_id;
     int chinese;
     int english;
     int math;
    };

    struct student Evan;
 
    //結構變數的宣告(法二)
    struct student{
     char name[8];
     int student_id;
     int chinese;
     int english;
     int math;
    }Evan;
 
    struct student Kitty;
 
    //結構變數的宣告(法三)
    struct {
     char name[8];
     int student_id;
     int chinese;
     int english;
     int math;
    }Evan;

}

補充:結構變數的宣告又稱為建立實體 (可以與 C++ 的 Class  進行呼應)

結構變數的宣告有三種方法如上述程式碼,其中 法一&法二 是較好的方法。

法一

結構的定義以及結構變數的宣告分開來進行,如前面所述,我們藉由保留字 struct 以及識別字(也就是結構名稱) student 結合,創建一個新的結構型別為 struct student 

接著利用這個結構型別宣告一個結構變數,名為 Evan (第 14 行)。

法二

結構的定義以及結構變數的合併宣告,我們將結構變數 Evan 與結構的定義進行了合併(第 23 行 ),這樣的方法好處在於我們可以在進行結構定義的同時宣告一個結構變數,並且如果我們之後在程式碼中還需要額外進行這個結構體的結構變數宣告時,可以額外的進行宣告(第 25 行)

法三

結構標籤(名稱)可以被省略,但這種方法需要在結構定義的同時就進行結構變數的宣告,後續在程式碼中就沒辦法在對該結構進行新的結構變數宣告,較不彈性

結構變數宣告的初使化以及存取結構成員

#include "stdio.h"

int main() {

    struct student{
     char name[8];
     int student_id;
     int chinese;
     int english;
     int math;
    };

    struct student Evan = { "Evan" , 1, 30, 55, 80 };
 
 /* 只能在宣告結構變數的同時進行初使値給定
 ** 不得在獨立宣告結構變數(沒給初始値)後又賦予初使値,如下的程式語句是不被允許的
 ** struct student Evan;
 ** Evan = { "Evan" , 1, 30, 55, 80 }; */

 // 存取結構成員
    printf("Evan.name = %s\n", Evan.name);
    printf("Evan.student_id = %d\n", Evan.student_id);
    printf("Evan.chinese = %d\n", Evan.chinese);
    printf("Evan.english = %d\n", Evan.english);
    printf("Evan.math = %d\n", Evan.math);

}


之前在說明陣列時我們有提到,陣列可以在進行宣告的同時進行初始値的賦値,結構同樣可以

但需要注意的是如果想對結構成員的初始値進行"批量"初使化,需要在宣告結構變數的時候就進行,不能獨立宣告了一個結構變數,再使用賦予初始値的語句對結構內的成員"批量"進行賦値。(第 17~18 行)

在建立一個結構(struct)的"架構"時,不能給裡面的成員進行初使化,事實上是一個很合理的限制。因為你是在創立一個新的"數據型別"(就像你創立一個 int 整數型別),理所當然的一個型別本身就不會具有初使值(數據)。

對結構內的成員進行存取需要使用 結構成員運算子 " . " (structure member operator) ,也稱為點號運算子( dot operator )。

如上述的程式碼就是對 Evan 結構變數裡面的成員 name、student_id ... 等等進行取値,賦値也是同樣的道理,就如同一個普通的變數一般用等號賦値就可以。

#include "stdio.h"

int main() {

    struct student{
     char name[8];
     int student_id;
     int chinese;
     int english;
     int math;
    }Evan = { "Evan" , 1, 30, 55, 80 };

 // 存取結構成員
    printf("Evan.name = %s\n", Evan.name);
    printf("Evan.student_id = %d\n", Evan.student_id);
    printf("Evan.chinese = %d\n", Evan.chinese);
    printf("Evan.english = %d\n", Evan.english);
    printf("Evan.math = %d\n", Evan.math);

}


對於結構的初始化,同樣的可以用更簡潔的方式來進行給値。(上方第 11 行)


#include "stdio.h"

int main() {

    struct student{
     char name[8];
     int student_id;
     int chinese;
     int english;
     int math;
    };

    struct student Evan = { .name = "Evan" , .english = 55, .math = 80, .student_id = 1 };


 // 存取結構成員
    printf("Evan.name = %s\n", Evan.name);
    printf("Evan.student_id = %d\n", Evan.student_id);
    printf("Evan.chinese = %d\n", Evan.chinese);
    printf("Evan.english = %d\n", Evan.english);
    printf("Evan.math = %d\n", Evan.math);

}


若結構內的成員很多,或是我們需要跳著對結構內的成員進行初始値賦値,可以在初始化的時候配合 " . " 來對指定的結構成員進行賦値。若沒有被賦値到的成員則會自動被賦予 0

這樣的好處在於我們可以不管結構成員在結構內的排列順序就可以對指定的結構成員進行初始化。

結構體陣列變數宣告

#include "stdio.h"

int main() {

 struct student{
      char name[8];
      int student_id;
      int chinese;
      int english;
      int math;
     };

 struct student Student[3] = {
   { "Evan" , 1, 30, 55, 80  },
   { "Kitty" , 2, 67, 45, 40 },
   { "John" , 3, 37, 98, 50  }
 };

 for ( int i = 0; i < 3; i++){
  printf("Student.name = %s\n", Student[i].name);
  printf("Student.student_id = %d\n", Student[i].student_id);
  printf("Student.chinese = %d\n", Student[i].chinese);
  printf("Student.english = %d\n", Student[i].english);
  printf("Student.math = %d\n\n", Student[i].math);
 }

}



結構變數的宣告也可以是一個陣列,我們在第 13 行的時候宣告一個名為 Student 的結構變數陣列(內含三個元素),結構型別為 struct student 。

此時該結構變數陣列中的每一個元素,都代表著一個結構

第 13 行我們進行了初始値的賦予。對於陣列中不同元素(結構)的賦値我們同樣使用大括弧配合逗號將之區別開來。

結構內含結構

#include "stdio.h"

int main() {

 struct Date{
    int year;
    int month;
    int day;
   };

 struct student{
      char name[8];
      int student_id;
      int chinese;
      int english;
      int math;
      struct Date birthday;
     };

 struct student Evan = { "Evan" , 1, 30, 55, 80 , {1993, 6, 18} };

 printf("Evan.name = %s\n", Evan.name);
 printf("Evan.student_id = %d\n", Evan.student_id);
 printf("Evan.chinese = %d\n", Evan.chinese);
 printf("Evan.english = %d\n", Evan.english);
 printf("Evan.math = %d\n", Evan.math);

 printf("Evan.birthday.year = %d\n", Evan.birthday.year);
 printf("Evan.birthday.month = %d\n", Evan.birthday.month);
 printf("Evan.birthday.day = %d\n", Evan.birthday.day);

}


結構裡面的成員也可以是另一個結構

我們先定義了一個 Date 的結構,並且在 student 的結構定義內把 Date 結構也囊括進來當做結構成員。

注意,在 student 裡面囊括進 Date 的同時就必須對 Date 結構進行結構變數的宣告(我宣告為 birthday )

接著我們宣告結構變數 Evan ,結構型別為 struct student 。

初始値的內容在 Date 結構的部分使用大括弧來依序對 Date 結構內的成員進行賦値。

存取結構內的結構成員也很簡單,就是一層一層用 " . " 來進行取値就可以