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名稱,而可以直接在定義結構之後,直接宣告實例。

您可以使用structs定義的自訂型態來宣告指標變數,例如:
#include <stdio.h>

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

int main(void) {
struct Ball ball = {"red", 4.0};
struct Ball *ptr;
ptr = &ball;

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

return 0;
}

這個例子示範了如何宣告struct指標,以及如何使用&對struct實例取位址值,如果使用struct的指標來存取其成員,則必須使用->運算子,執行結果如下:
ball: red       4.00

 struct 簡介 曾經談過,如果要將struct實例作為引數傳遞,則會直接進行成員值的複製,如果您並不想要複製實例,您可以直接傳遞struct實例的位址值,例如:
struct Ball {
char color[10];
double radius;
};

void show(struct Ball *ball);

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

show(&ball);

return 0;
}

void show(struct Ball *ball) {
printf("ball: %s\t%.2f\n", ball->color, ball->radius);
}

執行結果如下:
ball: red       4.00

注意到,以下的語法取得的是struct實例的成員位址值,而不是struct實例的位址值:
struct Ball ball = {"red", 4.0};
printf("%X\n", &ball.color);

為了避免誤會,建議加上括號比較清楚:
struct Ball ball = {"red", 4.0};
printf("%X\n", &(ball.color));

類似的,如果ptr是struct的指標,並儲存某個實例的位址值,那麼以下取得的是實例的成員位址值,而不是實例的位址值,建議還是如第二行的,加上括號比較清楚:
printf("%X\n", &ptr->color);
printf("%X\n", &(ptr->color));

struct指標的應用之一,可以參考 
堆疊 - 使用鏈結實作(C 語言動態記憶體宣告) 與 佇列 - 使用鏈結實作(C語言動態記憶體宣告),當中的鏈結資料結構,即使用struct指標來連結下一個節點實例。

下面這個例子是個比較進階的例子,程式中使用 函 式指標,讓 structs 實例擁有可操作的函式,而在操作struct實例所擁有的函式時,傳入實例的位址值,如此該函式可以取得實例成員值並進行運算,這是在
模擬物件導向中,物件實例擁有屬性及方法(method)的特性:
#include <stdio.h>

typedef struct Ball CBall;
struct Ball {
char *color;
double radius;
double (*volumn)(CBall*); // 函式指標
};

// 計算實例體積
double volumn(CBall *this) {
double r = this->radius; // 模擬 this 指標 的行為
return r * r * 3.14;
}

void initBall(CBall *ball, char *color, double radius) {
ball->volumn = volumn; // 連結函式
ball->color = color;
ball->radius = radius;
}

int main(void) {
CBall ball;
initBall(&ball, "red", 5.0);

printf("ball 實例的體積: %.2f\n", ball.volumn(&ball));

return 0;
}

執行結果:
ball 實例的體積: 78.50

在C中列舉(Enumeration)型別,就是以關鍵字enum開始加上一個列舉名稱,並以大括號括住要群組管理的常數,例如:

enum Action{stop, sit, stand, walk, run};


上例中宣告一個列舉型態Action,大括號中每一個元素稱為列舉元(enumerator),預設上列舉元從第一個開始的實際數值是0,然後依次遞 增,以上例而言,stop為0、sit為1、stand為2、walk為3、run為4,您也可以自行為列舉元設定數值,例如:

enum Action{stop = 1, sit, stand, walk, run};


上例來說,stop為1,下一個列舉元如果沒有設定數值的話,則自動遞增1,所以sit為2、stand為3、walk為4、run為5,列舉的常數值不 需獨一無二,例如下例:

enum Action{stop = 1, sit, stand = 2, walk, run};


下一個列舉元如果沒有設定數值的話,則自動遞增1,所以上例中stop為1、sit為2、stand為2、walk為3、run為4。

宣告列舉之後,您可以用它來宣告列舉變數,例如:

enum Action action = stop;


上例中action可接受的數值範圍為列舉元中的數值範圍,超出的話可能會有不可預期的行為。

在必要的時候,列舉元的數值可用來與其它數值作運算,例如:

enum Action action = stand;
printf("%d\n", action + 2);


列舉值的數值被取出再進行加2,所以上例中會在文字模式下顯示數值4

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 mark831102 的頭像
    mark831102

    聶離的部落格

    mark831102 發表在 痞客邦 留言(0) 人氣()