結構體

馨元君發表於2024-04-24

C語言中的結構體

在C語言中,結構體(struct)是一種使用者自定義的資料型別,它允許你將不同型別的資料組合成一個單獨的資料型別。結構體提供了一種方式來封裝一組邏輯上相關的變數(即成員或欄位),並可以將它們視為一個整體。這樣,你就可以像操作一個單獨變數一樣操作這些相關的變數。

結構體定義了一個資料型別,你可以建立該型別的變數(稱為結構體變數或結構體例項),併為每個變數分配儲存空間。每個結構體變數都可以儲存不同的值,但所有的結構體變數都遵循相同的格式,即它們都有相同型別的成員,並以相同的順序排列。

以下是C語言中結構體的基本定義和使用方式:

#include <stdio.h>

// 定義結構體型別
struct Student {
    char name[50];
    int age;
    float score;
};

int main() {
    // 建立結構體變數
    struct Student student1;

    // 為結構體變數的成員賦值
    strcpy(student1.name, "Alice");
    student1.age = 20;
    student1.score = 90.5;

    // 訪問結構體變數的成員並列印
    printf("Name: %s\n", student1.name);
    printf("Age: %d\n", student1.age);
    printf("Score: %.2f\n", student1.score);

    // 也可以使用結構體型別來定義函式引數
    void printStudent(struct Student s) {
        printf("Name: %s\n", s.name);
        printf("Age: %d\n", s.age);
        printf("Score: %.2f\n", s.score);
    }

    // 呼叫函式並傳遞結構體變數
    printStudent(student1);

    return 0;
}

在上面的例子中,我們首先定義了一個名為Student的結構體型別,它包含三個成員:name(一個字元陣列用於儲存名字)、age(一個整數用於儲存年齡)和score(一個浮點數用於儲存分數)。然後,我們建立了一個Student型別的變數student1,併為它的成員賦值。最後,我們列印出這些成員的值,並透過一個函式printStudent來展示如何將結構體作為引數傳遞給函式。

結構體是C語言中一種非常重要的構造,它們使得程式碼更加模組化、可讀性強,並有助於組織和管理複雜的資料結構。

  • 結構體的成員名可以與程式中其他定義為基本型別的變數名同名,同一個程式中不同結構體的成員也名也可以相同,他們代表的是不同的物件,不會出現衝突。
  • 如果結構體型別的定義在函式內部,則這個型別名的作用域僅為該函式;如果定義域在所有函式的外部,則在整個程式中使用。

結構體變數的定義

先定義結構體,後定義變數:

struct 結構體識別符號
{
  資料型別1  成員名1;
  資料型別2  成員名2;
  資料型別3  成員名3;
    ……
  資料型別n  成員名n;
}

void function_one(void){
  
  struct 結構體識別符號 結構體變數名;
}

結構體識別符號我們可以看成類似於基本資料型別中的int等,·struct 結構體識別符號 結構體變數名是宣告一個結構體變數

定義結構體的同時定義變數:

struct 結構體識別符號
{
    資料型別1  成員名1;
  資料型別2  成員名2;
  資料型別3  成員名3;
    ……
  資料型別n  成員名n;
}變數1,變數2,……,變數n;

在實際運用中,此方法適用於臨時定義區域性變數或結構體成員變數。,為什麼呢?因為這種方法用於定義全域性變數相當於在程式執行的時候開闢了新的記憶體空間,當程式用不到這些變數時依舊存在,直到程式結束後才會被釋放。而區域性變數在區域性程式結束後記憶體就會被釋放。

結構體變數的初始化

在C語言中,結構體變數的初始化可以透過多種方式來完成。以下是一些常見的初始化方式:

1. 在定義時直接初始化

你可以在定義結構體變數時直接初始化其成員。這通常是透過在大括號 {} 內列出成員的值來實現的。

#include <stdio.h>

struct Person {
    char name[50];
    int age;
};

int main() {
    // 在定義時直接初始化
    struct Person person1 = {"Alice", 25};
    
    printf("Name: %s\n", person1.name);
    printf("Age: %d\n", person1.age);
    
    return 0;
}

2. 使用指定初始化器(C99及以後)

從C99標準開始,你可以使用指定初始化器(designated initializer)來初始化結構體的特定成員。這對於大型結構體或只需初始化部分成員的情況非常有用。

#include <stdio.h>

struct Person {
    char name[50];
    int age;
    float height;
};

int main() {
    // 使用指定初始化器
    struct Person person1 = {
        .name = "Alice",
        .age = 25,
        // height 未被初始化,將保持未定義值
    };
    
    printf("Name: %s\n", person1.name);
    printf("Age: %d\n", person1.age);
    // 注意:未初始化的成員(如height)將包含未定義的值,使用前需確保它們被正確初始化
    
    return 0;
}

3. 透過函式返回值初始化

有時,你可能希望透過函式呼叫返回的結構體來初始化變數。這要求函式返回一個結構體型別的值。

#include <stdio.h>

struct Point {
    int x;
    int y;
};

struct Point create_point(int x, int y) {
    struct Point p = {x, y};
    return p;
}

int main() {
    // 透過函式返回值初始化
    struct Point p = create_point(10, 20);
    
    printf("Point (%d, %d)\n", p.x, p.y);
    
    return 0;
}

4. 使用複合字面量初始化(C99及以後)

在C99及之後的版本中,你可以使用複合字面量(compound literal)來初始化結構體變數。這種方式允許你臨時建立一個匿名結構體例項,並將其用於初始化。

#include <stdio.h>

struct Person {
    char name[50];
    int age;
};

int main() {
    // 使用複合字面量初始化
    struct Person person1 = (struct Person){"Alice", 25};
    
    printf("Name: %s\n", person1.name);
    printf("Age: %d\n", person1.age);
    
    return 0;
}

注意事項

  • 在初始化結構體時,必須確保提供的值的型別和數量與結構體的成員相匹配。
  • 如果某個成員沒有被初始化,它將包含未定義的值。對於基本資料型別(如intfloat等),這通常意味著它們將包含垃圾值。對於包含指標的成員,它們可能包含隨機的記憶體地址,這在使用前需要特別注意。
  • 初始化大型結構體或包含大量成員的結構體時,使用指定初始化器可以使程式碼更清晰、更易於維護。

結構體變數與結構體陣列

在C語言中,結構體型別變數用於儲存單個結構體的例項,而結構體型別陣列則用於儲存多個結構體的例項。這兩種方式都是處理結構體資料的有效手段,下面將分別介紹它們的使用方法和特點。

結構體型別變數

結構體型別變數是結構體型別的單個例項。建立結構體型別變數時,系統會為其分配足夠的記憶體來儲存其所有成員。你可以透過變數名和點運算子(.)來訪問和修改其成員的值。

#include <stdio.h>

struct Person {
    char name[50];
    int age;
};

int main() {
    // 建立結構體型別變數
    struct Person person1;

    // 為結構體變數的成員賦值
    strcpy(person1.name, "Alice");
    person1.age = 30;

    // 訪問結構體變數的成員並列印
    printf("Name: %s\n", person1.name);
    printf("Age: %d\n", person1.age);

    return 0;
}

在這個例子中,person1 是一個 Person 型別的變數,我們為其 nameage 成員分別賦了值,並列印了它們的值。

結構體型別陣列

結構體型別陣列用於儲存多個相同型別的結構體例項。陣列的每個元素都是一個結構體變數,你可以透過陣列索引和點運算子(.)來訪問和修改其成員的值。

#include <stdio.h>
#include <string.h>

struct Person {
    char name[50];
    int age;
};

int main() {
    // 建立結構體型別陣列
    struct Person people[3];

    // 為陣列中的每個結構體例項的成員賦值
    strcpy(people[0].name, "Alice");
    people[0].age = 25;
    strcpy(people[1].name, "Bob");
    people[1].age = 30;
    strcpy(people[2].name, "Charlie");
    people[2].age = 35;

    // 訪問陣列中的結構體例項的成員並列印
    for (int i = 0; i < 3; i++) {
        printf("Name: %s\n", people[i].name);
        printf("Age: %d\n", people[i].age);
    }

    return 0;
}

在這個例子中,people 是一個包含三個 Person 型別元素的陣列。我們為陣列中的每個元素(即每個結構體例項)的 nameage 成員分別賦了值,並使用一個迴圈來訪問和列印它們的值。

注意事項

  • 當建立結構體型別陣列時,需要為陣列中的每個元素分配記憶體。因此,陣列的大小應該根據實際需要來確定,以避免浪費記憶體或記憶體不足的問題。
  • 訪問陣列中的結構體例項的成員時,要注意陣列索引的正確性,以避免越界訪問導致的錯誤。
  • 結構體型別陣列和結構體型別變數在記憶體中的儲存方式是相似的,只是陣列會連續儲存多個結構體例項。因此,它們在使用上的主要區別在於可以處理的資料量的大小和靈活性。

結構體指標

在C語言中,結構體指標是一種特殊型別的指標,用於指向結構體變數或結構體陣列的記憶體地址。透過使用結構體指標,你可以間接地訪問和修改結構體變數的成員。

定義結構體指標

定義結構體指標的方式與定義其他型別的指標類似,只需將指標型別指定為相應的結構體型別即可。

struct Person {
    char name[50];
    int age;
};

int main() {
    // 定義結構體變數
    struct Person person = {"Alice", 25};

    // 定義結構體指標
    struct Person *personPtr = &person;

    // 透過指標訪問結構體成員
    printf("Name: %s\n", personPtr->name);
    printf("Age: %d\n", personPtr->age);

    return 0;
}

在上面的程式碼中,personPtr 是一個指向 Person 型別結構體的指標。我們透過取 person 變數的地址 &person 來初始化這個指標。

在C語言中,使用結構體指標給結構體變數賦值可以透過幾種不同的方式來實現。這裡有幾個示例:

示例1:直接透過指標訪問成員並賦值

#include <stdio.h>

struct Person {
    char name[50];
    int age;
};

int main() {
    // 定義結構體變數
    struct Person person;
    
    // 定義結構體指標,並指向結構體變數
    struct Person *personPtr = &person;
    
    // 透過指標訪問結構體成員並賦值
    strcpy(personPtr->name, "Alice"); // 使用strcpy複製字串
    personPtr->age = 25;               // 直接給整型成員賦值
    
    // 輸出結構體變數的值
    printf("Name: %s\n", person.name);
    printf("Age: %d\n", person.age);
    
    return 0;
}

在這個例子中,我們首先定義了一個 Person 型別的結構體變數 person,然後定義了一個指向 Person 的指標 personPtr,並將其初始化為指向 person。接著,我們使用 -> 運算子透過 personPtr 訪問 person 的成員,並使用 strcpy 函式給 name 成員賦值,直接給 age 成員賦值。

示例2:透過另一個結構體變數賦值

#include <stdio.h>
#include <string.h>

struct Person {
    char name[50];
    int age;
};

int main() {
    // 定義並初始化一個結構體變數
    struct Person source = {"Bob", 30};
    
    // 定義另一個結構體變數
    struct Person destination;
    
    // 定義指向目標結構體變數的指標
    struct Person *destPtr = &destination;
    
    // 使用指標複製源結構體的值到目標結構體
    strcpy(destPtr->name, source.name);
    destPtr->age = source.age;
    
    // 輸出目標結構體變數的值
    printf("Name: %s\n", destPtr->name);
    printf("Age: %d\n", destPtr->age);
    
    return 0;
}

在這個例子中,我們首先定義並初始化了一個 source 結構體變數。然後定義了一個 destination 結構體變數,以及一個指向 destination 的指標 destPtr。接著,我們使用 destPtr 來訪問 destination 的成員,並將 source 的成員值複製到 destination 中。

示例3:使用指標直接賦值(如果結構體變數相鄰)

如果兩個結構體變數在記憶體中相鄰儲存,並且你想要複製整個結構體的內容(注意:這樣做可能不安全,除非你確定兩個變數確實是相鄰的),你可以這樣做:

#include <stdio.h>
#include <string.h>

struct Person {
    char name[50];
    int age;
};

int main() {
    // 定義並初始化一個結構體陣列(確保它們是相鄰的)
    struct Person people[2] = {
        {"Alice", 25},
        {"Bob", 30}
    };
    
    // 定義指向第一個和第二個結構體變數的指標
    struct Person *firstPersonPtr = &people[0];
    struct Person *secondPersonPtr = &people[1];
    
    // 使用指標直接複製結構體(不安全,僅作為示例)
    memcpy(secondPersonPtr, firstPersonPtr, sizeof(struct Person));
    
    // 輸出第二個結構體變數的值,它現在應該是第一個結構體的副本
    printf("Name: %s\n", secondPersonPtr->name);
    printf("Age: %d\n", secondPersonPtr->age);
    
    return 0;
}

在這個例子中,我們定義了一個包含兩個 Person 結構體的陣列 people。我們使用 memcpy 函式透過指標直接複製整個 firstPerson 結構體的內容到 secondPerson 中。注意,使用 memcpy 進行這種操作是不安全的,除非你完全確定目標記憶體區域足夠大,並且沒有與其他重要的資料重疊。在實際程式設計中,應該儘量避免這樣做,除非你完全瞭解潛在的風險。

通常,更推薦的做法是使用 strcpy 來複制字串成員,並單獨複製其他型別的成員,或者使用標準庫函式 memcpy 來複制整個結構體(但僅限於當你知道目標區域足夠大且沒有重疊時)。如果結構體包含指標或其他複雜型別的成員,直接複製可能會導致問題,因為這些成員指向的資料並沒有被複制。在這種情況下,你需要實現一個自定義的複製函式來正確複製結構體

使用結構體指標訪問成員

透過結構體指標訪問結構體成員時,需要使用 -> 運算子。這個運算子用於解引用指標並訪問其指向的結構體的成員。

// 假設 personPtr 是一個指向 Person 型別結構體的指標
printf("Name: %s\n", personPtr->name);
printf("Age: %d\n", personPtr->age);

修改結構體成員的值

你也可以透過結構體指標來修改結構體成員的值。

// 修改透過指標訪問的成員的值
personPtr->age = 26;
printf("Updated Age: %d\n", personPtr->age);

在這個例子中,我們透過 personPtr 指標修改了 age 成員的值。

結構體指標和函式

結構體指標在函式傳遞引數和返回結果時特別有用,因為傳遞指標通常比傳遞整個結構體更快,且佔用的記憶體更少(只傳遞地址而不是整個資料)。

#include <stdio.h>

struct Person {
    char name[50];
    int age;
};

void printPerson(struct Person *personPtr) {
    printf("Name: %s\n", personPtr->name);
    printf("Age: %d\n", personPtr->age);
}

int main() {
    struct Person person = {"Alice", 25};
    printPerson(&person); // 傳遞結構體變數的地址給函式
    return 0;
}

在這個例子中,printPerson 函式接受一個指向 Person 型別結構體的指標作為引數,並透過這個指標來訪問和列印結構體的成員。

注意事項

  • 在使用結構體指標之前,確保它已經被正確地初始化為指向一個有效的結構體變數或陣列。未初始化的指標包含垃圾值,解引用這樣的指標會導致未定義行為。
  • 當透過函式傳遞結構體指標時,確保在函式內部不會修改指標本身的值(即不要改變它指向的地址),除非你確實有這樣的意圖。函式內部通常應該只透過指標來訪問和修改它所指向的結構體的內容。

練習為結構體變數賦值

#include "pritect.h"
#include <stdio.h>
#include <string.h>


void pritect(void){
    
    struct judg_one {
        char exit_condition[5];
        double con_dondition;
    }judg_condition;
    
    strcpy(judg_condition.exit_condition,"exit");
    judg_condition.con_dondition = 0;
    
    printf("%s\n",judg_condition.exit_condition);
    printf("%.2lf\n",judg_condition.con_dondition);
    
}

在為結構體變數中的一個字串型別賦值時,應該使用strcpy將要賦值給字串型別的字串賦值到結構體變數對應的成員中。strcpy的使用應當宣告標頭檔案<string.h>

judg_condition.exit_condition表示結構體變數下的exit_condition成員。

關於strcpy

在C語言中,strcpy 是一個標準庫函式,用於複製字串。這個函式定義在 <string.h> 標頭檔案中。strcpy 的功能是將源字串(包括終止的空字元 '\0')複製到目標字串中。

函式原型如下:

char *strcpy(char *dest, const char *src);
  • dest 是目標字串的指標,即你要將源字串複製到的位置。
  • src 是源字串的指標,即你要從中複製字串的位置。

strcpy 函式返回目標字串 dest 的指標。

這裡有一個簡單的使用示例:

#include <stdio.h>
#include <string.h>

int main() {
    char source[] = "Hello, World!";
    char destination[50];

    // 使用 strcpy 複製字串
    strcpy(destination, source);

    // 輸出複製後的字串
    printf("Copied string: %s\n", destination);

    return 0;
}

在上面的例子中,source 字串被複制到 destination 字串中,然後輸出複製後的字串。

需要注意的是,strcpy 函式不會檢查目標字串 dest 是否有足夠的空間來容納源字串 src,包括其終止的空字元。因此,如果 dest 的大小小於 src 的大小(不包括終止的空字元),就會發生緩衝區溢位,這可能導致程式崩潰或更嚴重的安全問題。

為了避免這種情況,你可以使用 strncpy 函式,它允許你指定一個最大字元數來複制,從而防止緩衝區溢位:

char *strncpy(char *dest, const char *src, size_t n);

在這個函式中,n 是最多要複製的字元數(不包括終止的空字元)。如果源字串的長度小於 n,則目標字串將用額外的空字元填充,直到總共複製了 n 個字元為止。如果源字串的長度大於或等於 n,則目標字串將不會被空字元終止,因此在使用 strncpy 後,通常需要手動新增終止的空字元。

相關文章