C语言结构体如何定义与使用?

99ANYc3cd6
预计阅读时长 27 分钟
位置: 首页 C语言 正文

struct 是 C 语言中一种非常重要的复合数据类型,它允许你将不同类型的数据项组合成一个单一的类型,你可以把它想象成一个“容器”或者一个“蓝图”,用来描述一个更复杂的数据对象。

c语言 structure
(图片来源网络,侵删)

为什么需要结构体?

在 C 语言中,我们基本的数据类型(如 int, char, float)只能描述单一的信息,但现实世界中的对象通常包含多个属性。

描述一个“学生”的信息,我们需要知道他的学号、姓名、年龄和成绩,这些信息分属不同的数据类型:

  • 学号:可能是整数 (int)
  • 姓名:字符串 (char 数组)
  • 年龄:整数 (int)
  • 成绩:浮点数 (float)

如果用独立的变量来表示,会非常混乱且难以管理:

int id1, id2, id3;
char name1[50], name2[50], name3[50];
int age1, age2, age3;
float score1, score2, score3;

使用结构体,我们可以将所有这些与一个学生相关的信息打包在一起,形成一个清晰、有组织的单元。

c语言 structure
(图片来源网络,侵删)

如何定义和使用结构体?

1 定义结构体类型

使用 struct 关键字来定义一个结构体类型,定义只是创建了一个“蓝图”,并不会在内存中分配任何空间。

struct Student {
    int id;         // 学号
    char name[50];  // 姓名
    int age;        // 年龄
    float score;    // 成绩
};
  • struct 是关键字。
  • Student 是结构体的“标签”(tag),你可以把它看作是这个新类型的名字。
  • 花括号 内部是“成员列表”(member list),每个成员都有自己的类型和名称。

2 声明结构体变量

定义好结构体类型后,你就可以像使用 intchar 一样声明该类型的变量了。

直接声明

struct Student student1, student2; // 声明了两个 Student 类型的变量

在定义时同时声明(更常用)

struct Student {
    int id;
    char name[50];
    int age;
    float score;
} student1, student2; // 在定义结构体后,直接声明了两个变量

3 初始化结构体变量

你可以在声明时对结构体变量进行初始化,就像初始化数组一样。

struct Student student1 = {101, "张三", 20, 95.5};

C99 标准还支持指定成员初始化(更安全):

struct Student student2 = {
    .id = 102,
    .name = "李四",
    .age = 21,
    .score = 88.0
};

4 访问结构体成员

使用成员访问运算符(,英文句点)来访问和修改结构体内部成员的值。

#include <stdio.h>
#include <string.h>
// 定义结构体
struct Student {
    int id;
    char name[50];
    int age;
    float score;
};
int main() {
    // 声明并初始化
    struct Student student1 = {101, "张三", 20, 95.5};
    // 访问成员并打印
    printf("学号: %d\n", student1.id);
    printf("姓名: %s\n", student1.name);
    printf("年龄: %d\n", student1.age);
    printf("成绩: %.2f\n", student1.score);
    // 修改成员的值
    student1.score = 98.0;
    student1.age = 21;
    printf("\n更新后的信息:\n");
    printf("学号: %d\n", student1.id);
    printf("姓名: %s\n", student1.name);
    printf("年龄: %d\n", student1.age);
    printf("成绩: %.2f\n", student1.score);
    return 0;
}

结构体数组

你可以创建一个结构体数组,来存储多个相同类型的结构体对象。

#include <stdio.h>
struct Student {
    int id;
    char name[20];
};
int main() {
    // 定义一个可以容纳 3 个 Student 结构体的数组
    struct Student students[3] = {
        {101, "张三"},
        {102, "李四"},
        {103, "王五"}
    };
    // 遍历并打印数组内容
    for (int i = 0; i < 3; i++) {
        printf("学生 %d: ID=%d, Name=%s\n", i + 1, students[i].id, students[i].name);
    }
    return 0;
}

结构体指针

指向结构体的指针非常常用,尤其是在函数传递大型结构体时,可以避免大量数据拷贝,提高效率。

1 声明和使用

  • 使用 ->(箭头运算符)来通过指针访问成员。
  • pointer->member 等价于 (*pointer).member
#include <stdio.h>
struct Student {
    int id;
    char name[20];
};
int main() {
    struct Student student1 = {101, "赵六"};
    struct Student *ptr; // 声明一个结构体指针
    ptr = &student1; // 让指针指向 student1 的地址
    // 使用 -> 访问成员
    printf("通过指针访问:\n");
    printf("ID: %d\n", ptr->id);
    printf("Name: %s\n", ptr->name);
    // 使用 (*ptr). 访问成员 (效果相同,但更繁琐)
    printf("\n通过解引用访问:\n");
    printf("ID: %d\n", (*ptr).id);
    printf("Name: %s\n", (*ptr).name);
    return 0;
}

结构体作为函数参数

结构体可以作为函数的参数传递,主要有两种方式:

  1. 按值传递:将整个结构体复制一份传给函数,优点是安全,函数内部修改不会影响原结构体,缺点是如果结构体很大,复制会消耗较多时间和内存。

    void printStudent(struct Student s) {
        printf("ID: %d, Name: %s\n", s.id, s.name);
    }
  2. 按指针传递:传递结构体的地址,优点是高效,不会复制数据,缺点是函数内部可以通过指针修改原结构体的内容,需要注意副作用。

    void updateStudentScore(struct Student *s, float new_score) {
        s->score = new_score; // 直接修改原结构体
    }

示例:

#include <stdio.h>
struct Student {
    int id;
    char name[20];
    float score;
};
// 按值传递,打印信息
void printByValue(struct Student s) {
    printf("[按值传递] ID: %d, Name: %s, Score: %.1f\n", s.id, s.name, s.score);
}
// 按指针传递,修改成绩
void updateByPointer(struct Student *s, float new_score) {
    s->score = new_score;
    printf("[按指针传递] 已将 %s 的成绩修改为 %.1f\n", s->name, s->score);
}
int main() {
    struct Student s1 = {101, "钱七", 85.5};
    printByValue(s1); // 传递副本
    updateByPointer(&s1, 92.0); // 传递地址
    printByValue(s1); // 再次打印,可以看到成绩已被修改
    return 0;
}

结构体的大小与内存对齐

结构体的大小不一定是其所有成员大小之和,这涉及到内存对齐的概念。

为什么需要内存对齐?

  1. 平台原因:不是所有的硬件平台都能在任何地址上访问任意类型的数据,某些硬件平台只能在特定地址(如偶数地址)访问特定类型的数据(如 int)。
  2. 性能原因:如果数据对齐,CPU 可以一次性读取一个完整的数据(如一个 4 字节的 int),否则可能需要两次内存访问,效率降低。

规则(简化的 GCC/Clang 规则):

  1. 第一个成员变量的偏移量为 0。
  2. 其他成员变量的偏移量必须是它自身大小的整数倍。
  3. 结构体的总大小必须是其中“最大成员”大小的整数倍。

示例:

#include <stdio.h>
// 结构体 A
struct A {
    char c1;    // 1字节
    int i;      // 4字节
    char c2;    // 1字节
};
// 结构体 B
struct B {
    int i;      // 4字节
    char c1;    // 1字节
    char c2;    // 1字节
};
int main() {
    printf("sizeof(struct A) = %zu\n", sizeof(struct A)); // 输出通常是 12
    printf("sizeof(struct B) = %zu\n", sizeof(struct B)); // 输出通常是 8
    return 0;
}

分析 struct A

  1. c1 放在偏移量 0 处,占 1 字节。
  2. i 需要从 4 的倍数地址开始,在 c1 后面填充了 3 个字节(padding),i 从偏移量 4 开始,占 4 字节。
  3. c2 放在偏移量 8 处,占 1 字节。
  4. 结构体总大小需要是最大成员(i, 4字节)的整数倍,当前总大小为 9,所以需要在末尾填充 3 个字节。
  5. 最终大小为 1 + 3 + 4 + 1 + 3 = 12 字节。

优化建议: 将结构体中占用空间较大的成员放在一起,可以减少填充字节,节省内存。

// 优化后的结构体
struct Optimized {
    int i;      // 4字节
    char c1;    // 1字节
    char c2;    // 1字节
    // char padding[2]; // 编译器自动填充
};
// sizeof(struct Optimized) = 8

typedef 与结构体

typedef 可以为一个已有的类型(包括 struct)创建一个新的别名,使代码更简洁易读。

不使用 typedef

struct Point {
    int x;
    int y;
};
int main() {
    struct Point p1; // 每次都要写 struct
    return 0;
}

使用 typedef

// 定义结构体并同时为其创建别名 Point
typedef struct {
    int x;
    int y;
} Point;
int main() {
    Point p1; // 直接使用 Point,无需 struct 关键字,更简洁
    Point p2;
    return 0;
}

或者,在已有结构体定义的基础上使用 typedef

struct Point {
    int x;
    int y;
};
typedef struct Point Point; // 为 struct Point 创建别名 Point

结构体嵌套

结构体的成员也可以是另一个结构体类型。

#include <stdio.h>
#include <string.h>
// 日期结构体
struct Date {
    int year;
    int month;
    int day;
};
// 学生结构体,嵌套了 Date 结构体
struct Student {
    int id;
    char name[50];
    struct Date birthday; // 嵌套结构体成员
    float score;
};
int main() {
    struct Student student1;
    student1.id = 101;
    strcpy(student1.name, "孙八");
    student1.birthday.year = 2003;
    student1.birthday.month = 5;
    student1.birthday.day = 20;
    student1.score = 91.5;
    printf("姓名: %s\n", student1.name);
    printf("生日: %d-%d-%d\n", student1.birthday.year, student1.birthday.month, student1.birthday.day);
    return 0;
}

特性 描述 示例
定义 使用 struct 关键字创建复合数据类型的蓝图。 struct Student {...};
声明 根据蓝图创建变量。 struct Student s1;
初始化 在声明时赋初值。 struct Student s = {101, "Tom"};
成员访问 使用 运算符访问变量成员。 s.id = 102;
指针访问 使用 -> 运算符通过指针访问成员。 ptr->id = 102;
数组 存储多个相同结构体。 struct Student arr[10];
函数参数 可按值传递(安全,低效)或按指针传递(高效,可修改原数据)。 void func(struct Student s) / void func(struct Student *s)
内存对齐 结构体大小受对齐规则影响,不一定是成员大小之和。 sizeof(struct A) 可能大于 sizeof(c1) + sizeof(i)
typedef 为结构体类型创建简洁别名。 typedef struct {...} Point;
嵌套 结构体成员可以是另一个结构体。 struct Student { struct Date birthday; };

结构体是 C 语言进行数据组织和面向对象编程思想的基础,掌握它对于编写复杂、高效的 C 程序至关重要。

-- 展开阅读全文 --
头像
C语言如何连接MySQL数据库API?
« 上一篇 03-16
dede增加栏目没反应怎么办?
下一篇 » 03-16
取消
微信二维码
支付宝二维码

目录[+]