From Gossip@caterpillar

C Gossip: struct 簡介

struct 是C中用來包裝資料的關鍵字,當您使用struct來包資料時,您考慮這些件可能擁有的相關性,將之包裝在一起,例如學生會有學號、姓名、住址、電話等, 您可以使用struct來定義一個Student型態,這個型態中包括了學號、姓名、住址、電話等資訊,接著您可以使用Student來宣告新的資料,進 行資料指定或取出等。

舉個實例來說,您可以定義一個「球」的模子,考慮球有各種不同的顏色,以及球最基本的球半徑資訊,您想到這些資訊應該可以定義一個Ball資料型態並從中取得,當您在C中要包裝這些資訊時,您可以如下進行定義:
  • CustomType.h
struct Ball {
char color[10];
double radius;
};

struct是C中用來定義struct的關鍵字,Ball是自定義的資料型態名稱:
struct Ball {
    // 成員定義
};

最重要的是別忘了在最後加上分號,初學C的新手很常犯這個錯誤;接下來如果要使用這個Ball的話,可以如下建立實例並初始化:
struct Ball ball1 = {"red", 5.0};

這樣的話,ball1將包括color與radius兩個資料成員,以上的寫法將color成員初始化為"red",而radius初始化為5.0,您也可以先宣告,後來再設定成員資料,例如:
struct Ball ball2;
strcpy(ball2.color, "green");
ball2.radius = 10.0;

在存取struct成員時,必須透過所宣告的名稱加上 . 運算子,以下寫個示範程式,看看Ball的使用:
  • main.c
#include <stdio.h>
#include <string.h>
#include "CustomType.h"

int main(void) {
struct Ball ball1 = {"red", 5.0};

struct Ball ball2;
strcpy(ball2.color, "green");
ball2.radius = 10.0;

printf("ball1: %s,\t%.2f\n", ball1.color, ball1.radius);
printf("ball2: %s,\t%.2f\n", ball2.color, ball2.radius);

return 0;
}

執行結果:
ball1: red,     5.00
ball2: green,   10.00


您也可以在定義struct時,直接宣告struct實例,例如:
struct Ball {
    char color[10];
    double radius;
} ball1 = {"red", 5.0}, ball2;

如果要宣告struct陣列並初始化每個結構成員,則可以如下:
struct Ball balls[] = {{"red", 3.0},
                       {"green", 5.0},
                       {"blue", 10.0}};

int i;                      
for(i = 0; i < 3; i++) {
    printf("ball1: %s,\t%.2f\n", balls[i].color, balls[i].radius);
}

為了方便起見,您可以使用typedef定義structs的名稱,如此一來,宣告並產生實例時,就不用再寫struct關鍵字,例如:
#include <stdio.h>
#include <string.h>

struct Ball {
char color[10];
double radius;
};
typedef struct Ball CBall;

int main(void) {
CBall ball1 = {"red", 5.0};

CBall ball2;
strcpy(ball2.color, "green");
ball2.radius = 10.0;

printf("ball1: %s,\t%.2f\n", ball1.color, ball1.radius);
printf("ball2: %s,\t%.2f\n", ball2.color, ball2.radius);

return 0;
}

或者是這樣重新命名:
typedef struct {
    char color[10];
    double radius;
} Ball;

您可以直接使用指定運算子,將一個struct的實例指定給另一個實例,這會將struct實例的成員值,一個一個「複製」給另一個被指定的對象,例如:
#include <stdio.h>

#include <stdio.h>

typedef struct {
char color[10];
double radius;
} Ball;

int main(void) {
Ball ball1 = {"red", 5.0};

Ball ball2 = ball1; // 指定運算子'='會複製成員值
ball1.radius = 10.0; // 改變ball1成員值並不會改變ball2成員值

printf("ball1: %s,\t%.2f\n", ball1.color, ball1.radius);
printf("ball2: %s,\t%.2f\n", ball2.color, ball2.radius);

return 0;
}

執行結果:
ball1: red,     10.00
ball2: red,     5.00


同樣的道理,如果在函式的引數傳遞時,同樣也是將struct的成員值一個一個「複製」給函式上的參數,例如:
#include <stdio.h>

typedef struct {
char color[10];
double radius;
} Ball;

void foo(Ball);

int main(void) {
Ball ball = {"red", 5.0};

foo(ball);
printf("ball: %s,\t%.2f\n", ball.color, ball.radius);

return 0;
}

void foo(Ball ball) { // ball 成員值被複製過來
ball.radius = 100.0;
}

在程式的foo()呼叫中,將ball傳遞給foo()上的參數,並在foo()中改變radius,但由於是複製成員值,這並不影響main()當中的ball實例之成員值。

在struct中,也可以再宣告struct,例如:
struct Student {
    char *name;
    int number;
   
    struct {
        char *color;
        double radius;
    } ball;
};

struct Student student1;
student1.name = "caterpillar";
student1.number = 1;
student1.ball.color = "red";
student1.ball.radius = 5.0;

您也可以看到,定義struct時,不一定要定義struct名稱,而可以直接在定義結構之後,直接宣告實例。