C 語言:結構(struct)自訂不同資料型態綁一起

結構:自訂不同資料型態串在一起

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

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。

 

這和把結構變數作為函數參數的情況一樣。

我們在寫程式的很多時候,都可以利用結構變數這樣的特點。