2018年2月14日 星期三

《筆記》C語言 - 09_2 :結構 struct 傳入函式(傳值呼叫)、結構傳入函式(傳址呼叫)、結構定義與 typedef、結構指標與 typedef

結構傳入函式(傳值呼叫)

#include "stdio.h"

// 建立一個結構定義(藍圖)。創建一個新的結構型別 struct student
// 結構的定義須宣告在 print_student 函式原型前面
struct student{
      char name[8];
      int student_id;
      int chinese;
      int english;
      int math;
      };

// 輸入引數的型別為 struct studnet
void print_student( struct student student_NAME);

int main() {
    struct student Evan = { "Evan" , 1, 30, 55, 80 };
    print_student(Evan);
    printf("\n");
    struct student Amy = { "Amy" , 2, 20, 70 };
    print_student(Amy);
}

void print_student( struct student student_NAME){
 printf("name = %s\n", student_NAME.name);
 printf("student_id = %d\n", student_NAME.student_id);
 printf("chinese = %d\n", student_NAME.chinese);
 printf("english = %d\n", student_NAME.english);
 printf("math = %d\n", student_NAME.math);
}



結構當然可以當作一個函式的輸入引數,上方的程式碼就實現了這樣子的行為

函式 print_student 在函式標頭指名輸入引數的型別為 結構型別 struct student ,接著在第 17, 20 行的時候就能夠進行函式的呼叫將結構傳入函式裡了。

値得注意的是上述的方法屬於傳値呼叫

若傳遞結構使用傳値呼叫事實上是一個不好的選擇,其原因在於我們會將資料包在一個結構裡面,肯定是因為資料量很大的關係才需要做這樣的處理。

既然資料量很大,我們卻又用傳値呼叫這種會建立副本的方式來進行傳遞給函式,理所當然的會造成整個程式的執行效率低落以及記憶體的沉重負擔

結構傳入函式(傳址呼叫)

#include "stdio.h"

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

void print_student( struct student* student_NAME );

int main() {
    struct student Evan = { "Evan" , 1, 30, 55, 80 };
    print_student( &Evan );
    printf("\n");
    struct student Amy = { "Amy" , 1, 30, 70 };
    print_student( &Amy );
}

void print_student( struct student *student_NAME ){
    printf("name = %s\n", (*student_NAME).name); // 先參考到結構的位址,再取用成員函式
    printf("student_id = %d\n", student_NAME->student_id); // 使用 -> 較簡便
    printf("chinese = %d\n", student_NAME->chinese);
    printf("english = %d\n", student_NAME->english);
    printf("math = %d\n", student_NAME->math);
}



傳址呼叫是一個較好的做法,我們前面提過 陣列、字串、函式 都可以跟指標扯上關係,同樣的結構也可以用指標的方式來對該結構進行存取

這樣的做法可以讓傳遞一個結構到函式內部只需要傳遞該結構的指標(位址),而不需要傳遞整個結構內的所有資料

這是一個傳遞 4bytes 與 上百甚至上千 bytes 資料量的區別

在上述程式碼中,對結構的定義以及結構變數的宣告沒有進行任何的改變,有改變的地方是 print_student 這個函式接收結構的方法。

第 11 行指名該函式的輸入引數為一個 結構型別指標
第 15,18 行則是將該結構的位址用取址運算子 "&" 來進行擷取

在 print_student 函式的主體部分就有些不同,我們不能使用結構成員運算子 "." 來對結構的成員進行存取;取而代之的是我們使用 結構指標運算子(->) (structure pointer operator ) 也稱為箭號運算子(arrow operator) 來對結構內的成員進行存取

事實上這個取結構成員的過程更正確的使用方式的是用 "(*)." 來進行( 第 22 行 ) ,但因為這樣看了令人昏頭,所以 C 語言才發展出箭號運算子 (->) 來代替。

結構定義與 typedef

#include "stdio.h"

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

// 對 struct student 進行 typedef 取別名
typedef struct student STU;

void print_student( STU student_NAME);

int main() {
    STU Evan = { "Evan" , 1, 30, 55, 80 };
    print_student(Evan);
    printf("math = %d\n", Evan.math);
}

void print_student( STU student_NAME){
 printf("name = %s\n", student_NAME.name);
 printf("student_id = %d\n", student_NAME.student_id);
 printf("chinese = %d\n", student_NAME.chinese);
 printf("english = %d\n", student_NAME.english);
 printf("math = %d\n", student_NAME.math);

 student_NAME.math = 20;
 printf("math = %d\n", student_NAME.math);
}

在一般情況下我們對結構變量進行宣告時需要使用保留字 struct 加上結構名稱來進行結構變數的宣告

但這樣太長了

所以我們可以用 typedef 對 struct student 這個結構型別取一個別名例如 STU (第 12 行)

typedef struct student STU

之後的程式碼中若要使用這個結構型別就都可以用 STU 進行替代就好

甚至可以更簡化,把第 12 行取別名的地方跟結構定義合併起來,如下

#include "stdio.h"

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

// 在結構定義的地方就加上 typedef ,並且把別名放在右大括弧後
// 事實上連上述中 typedef struct student 也可以只寫成 typedef struct 就好
// 但這樣子寫,就一定要在右大括弧後加上別名

void print_student( STU student_NAME);

int main() {
    STU Evan = { "Evan" , 1, 30, 55, 80 };
    print_student(Evan);
}

void print_student( STU student_NAME){
    printf("name = %s\n", student_NAME.name);
    printf("student_id = %d\n", student_NAME.student_id);
    printf("chinese = %d\n", student_NAME.chinese);
    printf("english = %d\n", student_NAME.english);
    printf("math= %d\n", student_NAME.math);
}

結構指標與 typedef

#include "stdio.h"

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

int main() {

    // 宣告結構變數 Evan
    STU Evan = { "Evan" , 1, 30, 55, 80 };

    // 法一:結構變量.成員名稱
    printf("math = %d\n", Evan.math);

    // 定義一個指向結構體的指標變數,並指向結構體 Evan (第14行的)
    STU *EvanPtr = &Evan;

    // 法二:(*結構變數名稱).成員名稱
    printf("math = %d\n", (*EvanPtr).math);

    // 法三:結構變數名稱->成員名稱
    printf("math = %d\n", EvanPtr->math);
}


我們將上一章結構指標與 typedef 做結合,看起來就簡潔多了

我們能更進一步將第 20 行的結構指標也進行 typedef 如下

#include "stdio.h"

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

int main() {

    // 宣告結構變數 Evan
    STU Evan = { "Evan" , 1, 30, 55, 80 };

    // 法一:結構變量.成員名稱
    printf("math = %d\n", Evan.math);

    // 將結構指標也進行 typedef ,使 STUPtr 代表一個指向 struct student 的結構指標
    typedef STU* STUPtr;

    // 定義一個指向結構的結構指標變數,並指向結構 Evan
    STUPtr EvanPtr = &Evan;

    // 法二:(*指針變數名稱).成員名稱
    printf("math = %d\n", (*EvanPtr).math);

    // 法三:指針變數名稱->成員名稱
    printf("math = %d\n", EvanPtr->math);
}

更好的方式是把指向該結構的別名、指標直接放在結構定義,如下

#include "stdio.h"

typedef struct{
      char name[8];
      int student_id;
      int chinese;
      int english;
      int math;
     }STU,*STUPtr;

int main() {

    // 宣告結構變數 Evan
    STU Evan = { "Evan" , 1, 30, 55, 80 };

    // 法一:結構變量.成員名稱
    printf("math = %d\n", Evan.math);

    // 利用 STUPtr 定義一個指標變數(結構),指向結構變數 Evan
    STUPtr EvanPtr = &Evan;

    // 法二:(*指針變數名稱).成員名稱
    printf("math = %d\n", (*EvanPtr).math);

    // 法三:指針變數名稱->成員名稱
    printf("math = %d\n", EvanPtr->math);
}