Skip to content

LV015-结构体指针

一、结构体指针简介

1. 什么是结构体指针

我们可以设定一个指针变量用来指向一个结构体变量。此时该指针变量的值是结构体变量的起始地址,该指针称为结构体指针。

结构体指针与前面的各种指针变量在特性和方法上是相同的。与前述相同,在程序中结构体指针也是通过访问目标运算 * 访问它的对象。

2. 怎么定义?

结构体指针的一般声明形式为:

c
struct 结构体名 *结构体指名;

其中的结构体名必须是已经定义过的结构体类型。例如:

c
/* 定义结构体数据类型 */
struct Student
{
	char *name;      /* 姓名 */
    char gender;     /* 性别 */
    int age;         /* 年龄 */
    char id[11];     /* 学号 */
    float score;     /* 成绩 */
};                   /* ; 不可缺少 */

/* 定义结构体数组 */
struct Student *pStu;

pStu 是指向 struct Student 结构体类型的指针。结构体指针的说明规定了它的数据特性,并为结构体指针本身分配了一定的内存空间。但是指针的内容尚未确定,即它指向随机的对象

3. 怎么访问成员

3.1 *.

可以使用解引用运算符*和点运算符.

c
(*结构体指针名).成员名1
(*结构体指针名).成员名2
...
(*结构体指针名).成员名N

例如:

c
struct student {
   int num;
   char name[20];
   char sex;
} stu1 = {1001, "Tom", 'M'}, *pstu = &stu1;

(*pstu).num;

3.2 箭头运算符->

可以使用箭头运算符来访问成员:

c
结构体指针名->成员名1
结构体指针名->成员名2
...
结构体指针名->成员名N

确保是一个结构体指针的情况下才可以使用 -> 来获取成员。下面是一个简单示例,

c
struct student {
   int num;
   char name[20];
   char sex;
} stu1 = {1001, "Tom", 'M'}, *pstu = &stu1;

pstu->num;

4. 赋值与初始化

与之前的指针变量格式一样。,赋值时要给的是一个地址。

4.1 赋值方式一

  • 先定义结构体指针,再赋值
c
#include <stdio.h>
/* 定义结构体数据类型 */
struct Student
{
	char *name;      /* 姓名 */
    char gender;     /* 性别 */
    int age;         /* 年龄 */
    char id[11];     /* 学号 */
    float score;     /* 成绩 */
};                   /* ; 不可缺少 */

int main(int argc, char *argv[])
{ 
    struct Student stu_v = {"fanhua", 'g', 18, "0000000003", 97.5};
    struct Student *pStu;

    pStu = &stu_v;
    printf("%s %c %d %s %.2f\n", pStu->name, pStu->gender, pStu->age, pStu->id, pStu->score);

    return 0;
}

在终端执行以下命令:

shell
gcc test.c -Wall # 编译程序 
./a.out          # 执行可执行文件

会看到有如下信息输出:

shell
fanhua g 18 0000000003 97.50

4.2 赋值方式二

  • 在定义结构体指针的时候直接进行赋值(这种赋值方式也被称为初始化)
c
/* 语法格式 */
struct 结构体名
{
    数据类型 成员名1;
    数据类型 成员名2;
    ...
    数据类型 成员名N;
};
struct 结构体名 *指针变量名 = address;

示例如下:

c
#include <stdio.h>
/* 定义结构体数据类型 */
struct Student
{
	char *name;      /* 姓名 */
    char gender;     /* 性别 */
    int age;         /* 年龄 */
    char id[11];     /* 学号 */
    float score;     /* 成绩 */
};                   /* ; 不可缺少 */

int main(int argc, char *argv[])
{ 
    struct Student stu_v = {"fanhua", 'g', 18, "0000000003", 97.5};
    struct Student *pStu = &stu_v;

    printf("%s %c %d %s %.2f\n", pStu->name, pStu->gender, pStu->age, pStu->id, pStu->score);

    return 0;
}

在终端执行以下命令:

shell
gcc test.c -Wall # 编译程序 
./a.out          # 执行可执行文件

会看到有如下信息输出:

shell
fanhua g 18 0000000003 97.50

二、含有指针?

当结构体成员中含有指针的时候该怎么访问?又怎么获取地址?

1. 访问结构体中的指针

1.1 结构体变量示例

c
#include <stdio.h>
#include <stdlib.h>

// 定义结构体
struct MyStruct {
    int *p; // 结构体成员是一个指针
};

int main(int argc, char *argv[]) {
    int target_data = 100;
    struct MyStruct obj1;
   
    obj1.p = &target_data; // 让结构体成员 p 指向 target_data 的地址
    
    // 访问 p 指向的数据:
    // 1. obj1.p 先取出指针成员 p
    // 2. *(obj1.p) 对该指针进行解引用,得到指向的值
    // 注意:因为 . 的优先级高于 *,所以括号可以省略,写成 *obj1.p
    printf("target_data=%d, &target_data=%p\n", target_data, &target_data);
    printf("*obj1.p=%d, &obj1.p=%p, obj1.p=%p\n", *obj1.p, &obj1.p, obj1.p);

    return 0;
}

将会看到如下打印信息:

shell
sumu@virtual-machine:~/hk/alpha$ gcc main.c -Wall
sumu@virtual-machine:~/hk/alpha$ ./a.out 
target_data=100, &target_data=0x7ffc9acc49bc
*obj1.p=100, &obj1.p=0x7ffc9acc49c0, obj1.p=0x7ffc9acc49bc

1.2 结构体指针变量示例

c
#include <stdio.h>
#include <stdlib.h>

// 定义结构体
struct MyStruct {
    int *p; // 结构体成员是一个指针
};

int main(int argc, char *argv[]) {
    int target_data = 100;
    struct MyStruct obj1;
   	struct MyStruct *p_obj1 = &obj1;
    
    p_obj1->p = &target_data; // 让结构体成员 p 指向 target_data 的地址
    
    // *p_obj1->p 等价于 *(p_obj1->p)
    printf("p 指向的值是: %d\n", *p_obj1->p);
    
    printf("target_data=%d, &target_data=%p\n", target_data, &target_data);
    printf("*p_obj1->p=%d, &p_obj1->p=%p, p_obj1->p=%p\n", *p_obj1->p, &p_obj1->p, p_obj1->p);

    return 0;
}

将会看到如下打印信息:

shell
sumu@virtual-machine:~/hk/alpha$ gcc main.c -Wall
sumu@virtual-machine:~/hk/alpha$ ./a.out 
p 指向的值是: 100
target_data=100, &target_data=0x7fff3eaa2a24
*p_obj1->p=100, &p_obj1->p=0x7fff3eaa2a28, p_obj1->p=0x7fff3eaa2a24

2. 嵌套的指针?

像下面的这种结构体,该怎么访问内部的指针?

c
// 定义结构体
struct MyStruct {
    int *p; // 结构体成员是一个指针
};

// 定义结构体
struct MyStructObj {
    struct MyStruct a;
};

2.1 结构体变量访问嵌套结构体中的指针

当使用结构体变量时,需要通过多个.运算符来访问嵌套的指针成员:

c
#include <stdio.h>
#include <stdlib.h>

// 定义结构体
struct MyStruct {
    int *p; // 结构体成员是一个指针
};

// 定义嵌套结构体
struct MyStructObj {
    struct MyStruct a;
};

int main(int argc, char *argv[]) {
    int target_data = 200;
    struct MyStructObj obj1;
    
    // 访问嵌套结构体中的指针成员
    obj1.a.p = &target_data;
    
    // 访问嵌套指针指向的值
    // 1. obj1.a.p 先取出嵌套结构体中的指针成员 p
    // 2. *(obj1.a.p) 对该指针进行解引用,得到指向的值
    printf("target_data=%d, &target_data=%p\n", target_data, &target_data);
    printf("*obj1.a.p=%d, &obj1.a.p=%p, obj1.a.p=%p\n", *obj1.a.p, &obj1.a.p, obj1.a.p);

    return 0;
}

将会看到如下打印信息:

shell
sumu@virtual-machine:~/hk/alpha$ gcc main.c -Wall
sumu@virtual-machine:~/hk/alpha$ ./a.out
target_data=200, &target_data=0x7ffc9acc49bc
*obj1.a.p=200, &obj1.a.p=0x7ffc9acc49c0, obj1.a.p=0x7ffc9acc49bc

2.2 结构体指针访问嵌套结构体中的指针

当使用结构体指针时,需要结合->.运算符来访问嵌套的指针成员:

c
#include <stdio.h>
#include <stdlib.h>

// 定义结构体
struct MyStruct {
    int *p; // 结构体成员是一个指针
};

// 定义嵌套结构体
struct MyStructObj {
    struct MyStruct a;
};

int main(int argc, char *argv[]) {
    int target_data = 200;
    struct MyStructObj obj1;
    struct MyStructObj *p_obj1 = &obj1;
    
    // 访问嵌套结构体中的指针成员
    p_obj1->a.p = &target_data;
    
    // 访问嵌套指针指向的值
    // *p_obj1->a.p 等价于 *(p_obj1->a.p)
    printf("p 指向的值是: %d\n", *p_obj1->a.p);
    
    printf("target_data=%d, &target_data=%p\n", target_data, &target_data);
    printf("*p_obj1->a.p=%d, &p_obj1->a.p=%p, p_obj1->a.p=%p\n", *p_obj1->a.p, &p_obj1->a.p, p_obj1->a.p);

    return 0;
}

将会看到如下打印信息:

shell
sumu@virtual-machine:~/hk/alpha$ gcc main.c -Wall
sumu@virtual-machine:~/hk/alpha$ ./a.out
p 指向的值是: 200
target_data=200, &target_data=0x7fff3eaa2a24
*p_obj1->a.p=200, &p_obj1->a.p=0x7fff3eaa2a28, p_obj1->a.p=0x7fff3eaa2a24

2.3 访问规则总结

访问嵌套结构体中的指针成员时,遵循以下规则:

情况访问方式示例
结构体变量使用.运算符obj1.a.p
结构体指针使用->运算符p_obj1->a.p
解引用指针值在指针前加**obj1.a.p*p_obj1->a.p
获取指针地址使用&运算符&obj1.a.p&p_obj1->a.p

重要提示

  • ->运算符只能用于指针类型,不能用于结构体变量
  • .运算符只能用于结构体变量,不能用于指针(除非先解引用)
  • 访问嵌套成员时,从外到内逐层访问

三、结构体指针与数组

结构体指针变量自然也可以指向数组,帮助我们访问数组中的元素。

1. 基本用法

c
#include <stdio.h>
/* 定义结构体数据类型 */
struct Student
{
	char *name;      /* 姓名 */
    char gender;     /* 性别 */
    int age;         /* 年龄 */
    char id[11];     /* 学号 */
    float score;     /* 成绩 */
};                   /* ; 不可缺少 */

int main(int argc, char *argv[])
{ 
    struct Student stu[3] = {{"fanhua", 'g', 18, "0000000003", 97.5},
                             {"qidaink", 'm', 19, "0000000001", 95.67},
                             {"a", 'm', 16, "0000000002", 87.5}};
    struct Student *pStu = stu;
    int len = sizeof(stu) / sizeof(struct Student);
    printf("Name\tGender\tAge\t    ID\t\tScore\t\n");
    for(pStu = stu; pStu < stu + len; pStu++)
    {
        printf("%s\t  %c\t%d\t%s\t%.2f\n", pStu->name, pStu->gender, pStu->age, pStu->id, pStu->score);
    }

    return 0;
}

在终端执行以下命令:

shell
gcc test.c -Wall # 编译程序 
./a.out          # 执行可执行文件

会看到有如下信息输出:

shell
Name    Gender  Age         ID          Score
fanhua    g     18      0000000003      97.50
qidaink   m     19      0000000001      95.67
a         m     16      0000000002      87.50

2. 作为数组名使用

当一个结构体指针指向数组后,它可以作为数组名使用,但是无法再像结构体指针那样可以使用 -> 来取相应元素的成员,但是可以使用 . 来获取相应元素的成员。

c
#include <stdio.h>
/* 定义结构体数据类型 */
struct Student
{
	char *name;      /* 姓名 */
    char gender;     /* 性别 */
    int age;         /* 年龄 */
    char id[11];     /* 学号 */
    float score;     /* 成绩 */
};                   /* ; 不可缺少 */

int main(int argc, char *argv[])
{ 
    struct Student stu[3] = {{"fanhua", 'g', 18, "0000000003", 97.5},
                             {"qidaink", 'm', 19, "0000000001", 95.67},
                             {"a", 'm', 16, "0000000002", 87.5}};
    struct Student *pStu = stu;

    printf("%s\t  %c\t%d\t%s\t%.2f\n", pStu[0]->name, pStu[0]->gender, pStu[0]->age, pStu[0]->id, pStu[0]->score);

    return 0;
}

一般来说应该会报这样的一个错误,而且是有一个报一个😂,能给报一堆:

shell
test.c: In function ‘main’:
test.c:20:47: error: invalid type argument of-> (have ‘struct Student’)
     printf("%s\t  %c\t%d\t%s\t%.2f\n", pStu[0]->name, pStu[0]->gender, pStu[0]->age, pStu[0]->id, pStu[0]->score);

四、结构体指针与函数

结构体变量名代表的是整个集合本身,作为函数参数时传递的整个集合,也就是所有成员,而不是像数组一样被编译器转换成一个指针。

如果结构体成员较多,尤其是成员为数组时,传送的时间和空间开销会很大,影响程序的运行效率。所以最好的办法就是使用结构体指针,这时由实参传向形参的只是一个地址,速度将会有所提升。

c
#include <stdio.h>
/* 定义结构体数据类型 */
struct Student
{
	char *name;      /* 姓名 */
    char gender;     /* 性别 */
    int age;         /* 年龄 */
    char id[11];     /* 学号 */
    float score;     /* 成绩 */
};                   /* ; 不可缺少 */

void average(struct Student *ps, int len);

int main(int argc, char *argv[])
{ 
    struct Student stu[3] = {{"fanhua", 'g', 18, "0000000003", 97.5},
                             {"qidaink", 'm', 19, "0000000001", 95.67},
                             {"a", 'm', 16, "0000000002", 87.5}};
    struct Student *pStu = stu;
    int len = sizeof(stu) / sizeof(struct Student);
    
    printf("Name\tGender\tAge\t    ID\t\tScore\t\n");
    for(pStu = stu; pStu < stu + len; pStu++)
    {
        printf("%s\t  %c\t%d\t%s\t%.2f\n", pStu->name, pStu->gender, pStu->age, pStu->id, pStu->score);
    }

    average(stu, len);

    return 0;
}

void average(struct Student *ps, int len)
{
    int i = 0;
    float average, sum = 0;
    for(i = 0; i < len; i++)
    {
        sum += (ps + i) -> score;
    }

    average = sum / len;

    printf("average = %.2f\n", average);
}

在终端执行以下命令:

shell
gcc test.c -Wall # 编译程序 
./a.out          # 执行可执行文件

会看到有如下信息输出:

shell
Name    Gender  Age         ID          Score
fanhua    g     18      0000000003      97.50
qidaink   m     19      0000000001      95.67
a         m     16      0000000002      87.50
average = 93.56