結構體指標,C語言結構體指標詳解

2020-07-16 10:04:25
結構體指標,可細分為指向結構體變數的指標指向結構體陣列的指標

指向結構體變數的指標

前面我們通過“結構體變數名.成員名”的方式參照結構體變數中的成員,除了這種方法之外還可以使用指標。

前面講過,&student1 表示結構體變數 student1 的首地址,即 student1 第一個項的地址。如果定義一個指標變數 p 指向這個地址的話,p 就可以指向結構體變數 student1 中的任意一個成員。

那麼,這個指標變數定義成什麼型別呢?只能定義成結構體型別,且指向什麼結構體型別的結構體變數,就要定義成什麼樣的結構體型別。比如指向 struct STUDENT 型別的結構體變數,那麼指標變數就一定要定義成 struct STUDENT* 型別。

下面將前面的程式用指標的方式修改一下:
# include <stdio.h>
# include <string.h>
struct AGE
{
    int year;
    int month;
    int day;
};
struct STUDENT
{
    char name[20];  //姓名
    int num;  //學號
    struct AGE birthday;  //生日
    float score;  //分數
};
int main(void)
{
    struct STUDENT student1; /*用struct STUDENT結構體型別定義結構體變數student1*/
    struct STUDENT *p = NULL;  /*定義一個指向struct STUDENT結構體型別的指標變數p*/
    p = &student1;  /*p指向結構體變數student1的首地址, 即第一個成員的地址*/
    strcpy((*p).name, "小明");  //(*p).name等價於student1.name
    (*p).birthday.year = 1989;
    (*p).birthday.month = 3;
    (*p).birthday.day = 29;
    (*p).num = 1207041;
    (*p).score = 100;
    printf("name : %sn", (*p).name);  //(*p).name不能寫成p
    printf("birthday : %d-%d-%dn", (*p).birthday.year, (*p).birthday.month, (*p).birthday.day);
    printf("num : %dn", (*p).num);
    printf("score : %.1fn", (*p).score);
    return 0;
}
輸出結果是:
name : 小明
birthday : 1989-3-29
num : 1207041
score : 100.0

我們看到,用指標參照結構體變數成員的方式是:

(*指標變數名).成員名

注意,*p 兩邊的括號不可省略,因為成員運算子“.”的優先順序高於指標運算子“*”,所以如果 *p 兩邊的括號省略的話,那麼 *p.num 就等價於 *(p.num) 了。

從該程式也可以看出:因為指標變數 p 指向的是結構體變數 student1 第一個成員的地址,即字元陣列 name 的首地址,所以 p 和 (*p).name 是等價的。

但是,“等價”僅僅是說它們表示的是同一個記憶體單元的地址,但它們的型別是不同的。指標變數 p 是 struct STUDENT* 型的,而 (*p).name 是 char* 型的。所以在 strcpy 中不能將 (*p).name 改成 p。用 %s 進行輸入或輸出時,輸入引數或輸出引數也只能寫成 (*p).name 而不能寫成 p。

同樣,雖然 &student1 和 student1.name 表示的是同一個記憶體單元的地址,但它們的型別是不同的。&student1 是 struct STUDENT* 型的,而 student1.name 是 char* 型的,所以在對 p 進行初始化時,“p=&student1;”不能寫成“p=student1.name”。因為 p 是 struct STUDENT* 型的,所以不能將 char* 型的 student1.name 賦給 p。

此外為了使用的方便和直觀,用指標參照結構體變數成員的方式:

(*指標變數名).成員名

可以直接用:

指標變數名->成員名

來代替,它們是等價的。“->”是“指向結構體成員運算子”,它的優先順序同結構體成員運算子“.”一樣高。p->num 的含義是:指標變數 p 所指向的結構體變數中的 num 成員。p->num 最終代表的就是 num 這個成員中的內容。

下面再將程式用“->”修改一下:
# include <stdio.h>
# include <string.h>
struct AGE
{
    int year;
    int month;
    int day;
};
struct STUDENT
{
    char name[20];  //姓名
    int num;  //學號
    struct AGE birthday;  /*用struct AGE結構體型別定義結構體變數birthday, 生日*/
    float score;  //分數
};
int main(void)
{
    struct STUDENT student1; /*用struct STUDENT結構體型別定義結構體變數student1*/
    struct STUDENT *p = NULL;  /*定義struct STUDENT結構體型別的指標變數p*/
    p = &student1;  /*p指向結構體變數student1的首地址, 即第一項的地址*/
    strcpy(p->name, "小明");
    p->birthday.year = 1989;
    p->birthday.month = 3;
    p->birthday.day = 29;
    p->num = 1207041;
    p->score = 100;
    printf("name : %sn", p->name);  //p->name不能寫成p
    printf("birthday : %d-%d-%dn", p->birthday.year, p->birthday.month, p->birthday.day);
    printf("num : %dn", p->num);
    printf("score : %.1fn", p->score);
    return 0;
}
輸出結果是:
name : 小明
birthday : 1989-3-29
num : 1207041
score : 100.0

但是要注意的是,只有“指標變數名”後面才能加“->”,千萬不要在成員名如 birthday 後面加“->”。
綜上所述,以下 3 種形式是等價的:
  • 結構體變數.成員名。
  • (*指標變數).成員名。
  • 指標變數->成員名。

其中第 3 種方式很重要,通常都是使用這種方式,另外兩種方式用得不多。後面講連結串列的時候用的也都是第 3 種方式。

指向結構體陣列的指標

在前面講數值型陣列的時候可以將陣列名賦給一個指標變數,從而使該指標變數指向陣列的首地址,然後用指標存取陣列的元素。結構體陣列也是陣列,所以同樣可以這麼做。

我們知道,結構體陣列的每一個元素都是一個結構體變數。如果定義一個結構體指標變數並把結構體陣列的陣列名賦給這個指標變數的話,就意味著將結構體陣列的第一個元素,即第一個結構體變數的地址,也即第一個結構變數中的第一個成員的地址賦給了這個指標變數。比如:
# include <stdio.h>
struct STU
{
    char name[20];
    int age;
    char sex;
    char num[20];
};
int main(void)
{
    struct STU stu[5] = {{"小紅", 22, 'F', "Z1207031"}, {"小明", 21, 'M', "Z1207035"}, {"小七", 23, 'F', "Z1207022"}};
    struct STU *p = stu;
    return 0;
}
此時指標變數 p 就指向了結構體陣列的第一個元素,即指向 stu[0]。我們知道,當一個指標指向一個陣列後,指標就可以通過移動的方式指向陣列的其他元素。

這個原則對結構體陣列和結構體指標同樣適用,所以 p+1 就指向 stu[1] 的首地址;p+2 就指向 stu[2] 的首地址……所以只要利用 for 迴圈,指標就能一個個地指向結構體陣列元素。

同樣需要注意的是,要將一個結構體陣列名賦給一個結構體指標變數,那麼它們的結構體型別必須相同。

下面編寫一個程式:
# include <stdio.h>
struct STU
{
    char name[20];
    int age;
    char sex;
    char num[20];
};
int main(void)
{
    struct STU stu[3] = {{"小紅", 22, 'F', "Z1207031"}, {"小明", 21, 'M', "Z1207035"}, {"小七", 23, 'F', "Z1207022"}};
    struct STU *p = stu;
    for (; p<stu+3; ++p)
    {
        printf("name:%s; age:%d; sex:%c; num:%sn", p->name, p->age, p->sex, p->num);
    }
    return 0;
}
輸出結果是:
name:小紅; age:22; sex:F; num:Z1207031
name:小明; age:21; sex:M; num:Z1207035
name:小七; age:23; sex:F; num:Z1207022

此外同前面“普通陣列和指標的關係”一樣,當指標變數 p 指向 stu[0] 時,p[0] 就等價於 stu[0];p[1] 就等價於 stu[1];p[2] 就等價於 stu[2]……所以 stu[0].num 就可以寫成 p[0].num,其他同理。下面將上面的程式用 p[i] 的方式修改一下:
# include <stdio.h>
struct STU
{
    char name[20];
    int age;
    char sex;
    char num[20];
};
int main(void)
{
    struct STU stu[3] = {{"小紅", 22, 'F', "Z1207031"}, {"小明", 21, 'M', "Z1207035"}, {"小七", 23, 'F', "Z1207022"}};
    struct STU *p = stu;
    int i = 0;
    for (; i<3; ++i)
    {
        printf("name:%s; age:%d; sex:%c; num:%sn", p[i].name, p[i].age, p[i].sex, p[i].num);
    }
    return 0;
}
輸出結果是:
name:小紅; age:22; sex:F; num:Z1207031
name:小明; age:21; sex:M; num:Z1207035
name:小七; age:23; sex:F; num:Z1207022