结构体:定义、使用与内存布局

个人主页流年如夢

专栏《C语言》

文章目录

本篇文章为将学习 结构体声明、变量创建、成员访问、内存对齐、结构体传参、位段 六大核心知识点,全程高能,不容错过!!!

前言

在C语言中,基本数据类型只能描述单一属性,而结构体可以将不同类型的数据组合在一起,用来描述复杂对象(如学生、书籍、员工等),是C语言自定义类型的核心。本篇文章带领大家从结构体基础语法入手,重点攻克内存对齐,并深入分析结构体传参与位段用法,帮大家熟练掌握复杂数据的封装与设计

一.结构体类型的声明

在之前的操作符文章简单了解过结构体,结构是一些值的集合,这些值称为成员变量 ,结构的每个成员可以是不同类型的数据。

1.1普通结构体声明

举个例子(以学生为例):

c 复制代码
struct Stu
{
    char name[20];//名字
    int age;//年龄
    char sex[5];//性别
    char id[20];//学号
};

1.2结构体变量创建与初始化

根据上面对学生的结构体声明在进行变量创建与初始化,如下所示:

c 复制代码
#include <stdio.h>

struct Stu
{
    char name[20];
    int age;
    char sex[5];
    char id[20];
};

int main()
{
    struct Stu s = { "张三", 20, "男", "20230818001" };
    printf("name: %s\n", s.name);
    printf("age : %d\n", s.age);
    printf("sex : %s\n", s.sex);
    printf("id  : %s\n", s.id);

    struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "女" };
    printf("name: %s\n", s2.name);
    printf("age : %d\n", s2.age);
    printf("sex : %s\n", s2.sex);
    printf("id  : %s\n", s2.id);

    return 0;
}

其中,struct Stu s按顺序初始化 ;而 struct Stu s2按指定成员初始化 (使用结构体访问操作符 .

运行结果:

🧐分析 :先定义学生结构体,包含姓名、年龄、性别、学号四个成员;再分别使用顺序初始化与指定成员初始化两种方式;使用 . 操作符访问结构体成员

1.3匿名结构体(结构体的特殊声明)

与普通结构体不同的是,在声明结构的时候,可以不完全的声明,如下所示:

c 复制代码
//1
struct
{
    int a;
    char b;
    float c;
} x;
//2
struct
{
    int a;
    char b;
    float c;
} a[20], *p;

我们可以看见,匿名结构体没有标签(如struct Stu是有标签Stu);然后我写了两个匿名结构体 1 和 2 ,那么问题来了,x 会等于 *p

答案是不会的,因为编译器会把上⾯的两个声明当成完全不同的两个类型,所以是非法的

值得注意的是,匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次

1.4结构体自引用

举例(正确的自引用:存指针):

c 复制代码
struct Node
{
    int data;
    struct Node* next;
};

🧐分析 :因为结构体内部不能包含自身类型变量 ,否则大小无限;所以正确方式是包含自身类型指针,用于链表、树等结构(数据结构)

二.结构体成员访问操作符

两个结构体成员访问操作符:

  1. 结构体变量 . 成员名
  2. 结构体指针 -> 成员名

举个例子:

c 复制代码
#include <stdio.h>

struct Stu
{
    char name[20];
    int age;
};

int main()
{
    struct Stu s = { "张三", 20 };
    struct Stu* ps = &s;

    printf("%s\n", s.name);
    printf("%d\n", ps->age);

    return 0;
}

🧐分析直接访问结构体变量的时候用 .;通过指针访问的时候用 ->

运行结果:

三.结构体内存对齐(重难点❗)

3.1规则

对齐规则:

  1. 第一个成员对齐到偏移量为0的位置
  2. 从第二个成员开始,对齐到对齐数的整数倍
  3. 结构体总大小是最大对齐数的整数倍
  4. 嵌套结构体时,嵌套结构体对齐到自己的最大对齐数

其中,对齐数 = 成员大小对比默认对齐数(vs2022=8,gcc = 自身大小)的较小值(我用的是vs2022)

3.2举例

c 复制代码
#include <stdio.h>

struct S1{char c1;int i;char c2;};

struct S2{char c1;char c2;int i;};

struct S3{double d;char c;int i;};

struct S4{char c1;struct S3 s3;double d;};

int main()
{
    printf("%zu\n", sizeof(struct S1));
    printf("%zu\n", sizeof(struct S2));
    printf("%zu\n", sizeof(struct S3));
    printf("%zu\n", sizeof(struct S4));

    return 0;
}

🧐分析 :其中 S1 成员分散,内存浪费多需要12 字节;S2 小成员集中,所以比较节省空间需要8 字节;总而言之,结构体内存对齐是用空间换时间

运行结果:

3.3如何修改默认对齐数

我们可以插入#pragma pack(n)手动修改对齐数,当 n = 1 时表示1字节对齐表示不使用对齐,紧凑排布;如下所示:

c 复制代码
#include <stdio.h>

#pragma pack(1)
struct S
{
    char c1;
    int i;
    char c2;
};
#pragma pack()

int main()
{
    printf("%zu\n", sizeof(struct S));
    
    return 0;
}

四.结构体传参

结构体传参有两种:传值、传地址

如下所示:

c 复制代码
#include <stdio.h>

struct S
{
    int data[1000];
    int num;
};

struct S s = { {1,2,3,4}, 1000 };

//传值
void print1(struct S s)
{
    printf("%d\n", s.num);
}

//传地址
void print2(struct S* ps)
{
    printf("%d\n", ps->num);
}

int main()
{
    print1(s);
    print2(&s);
    
    return 0;
}

🧐分析:传值会完整拷贝结构体,大数据量效率低;传地址仅传指针,效率高,更推荐使用传地址

五.结构体实现位段

5.1什么是位段

➡️位段可以按位分配空间,极大节省内存

5.2位段声明

举例:

c 复制代码
struct A
{
    int _a : 2;
    int _b : 5;
    int _c : 10;
    int _d : 30;
};

🧐分析成员类型必须是整型 , 比如 int、char、unsigned int 等;其中 :数字 表示该成员占多少个 bit 位(例如int _a : 2占了2个bit位)

网上分析位段的内存分配的相关截图:

5.3位段陷阱(注意事项⚠️)

不能直接对位段成员取地址

举个例子:

c 复制代码
#include <stdio.h>

struct A
{
    int _a : 2;
    int _b : 5;
    int _c : 10;
    int _d : 30;
};

int main()
{
    struct A sa = { 0 };
    int b = 0;
    scanf("%d", &b);//错误写法:scanf("%d", &sa._b);
    sa._b = b;

    return 0;
}

🧐分析:因为位段成员没有独立地址,所以不能用 & 取地址;赋值需先存入普通变量,再赋值给位段成员

🎯总结

  1. 结构体是C语言自定义类型,可封装不同类型成员
  2. 支持顺序初始化、指定成员初始化、匿名结构体、自引用
  3. 内存对齐是按规则计算大小,用空间换时间
  4. 结构体传参优先传地址,效率更高
  5. 位段按 bit 分配空间,节省内存,但不可移植、不可取地址

⚠️易错点

  1. 结构体声明末尾忘记写分号,编译报错
  2. 结构体自引用写成 struct Node next ; 而非指针
  3. 匿名结构体被编译器视为不同类型,赋值报错
  4. 结构体内存对齐计算错误
  5. 结构体大数据传值,导致效率低下
  6. 对位段成员使用 & 取地址,程序出错
  7. 忘记嵌套结构体也参与最大对齐数计算

👀 关注 我们一路同行,从入门到大师,慢慢沉淀、稳步成长
❤️ 点赞 鼓励原创,让优质内容被更多人看见
⭐ 收藏 收好核心知识点与实战技巧,需要时随时查阅
💬 评论 分享你的疑问或踩坑经历,一起交流避坑、共同进步

相关推荐
三品吉他手会点灯2 小时前
C语言学习笔记 - 6.C概述 - C的重要性
c语言·笔记·学习
thankseveryday2 小时前
Three.js 把 Blender 绘制的曲线(Bezier / 曲线) 导入 Three.js 并作为运动路径 / 动画路径使用
开发语言·javascript·blender
Ulyanov2 小时前
《玩转QT Designer Studio:从设计到实战》 QT Designer Studio动画与动效系统深度解析
开发语言·python·qt·系统仿真·雷达电子对抗仿真
兩尛2 小时前
struct,union,Class,bitfield各自的作用和区别
java·开发语言
wuminyu2 小时前
专家视角看 Java 字节码与Class 文件格式
java·linux·c语言·jvm·c++
Gauss松鼠会2 小时前
【openGauss】openGauss 磁盘引擎之 ustore
java·服务器·开发语言·前端·数据库·经验分享·gaussdb
键盘会跳舞2 小时前
【Qt】分享一个笔者持续更新的项目: https://github.com/missionlove/NQUI
c++·qt·用户界面·qwidget
YSF2017_32 小时前
C语言-13-制作动态库
c语言·开发语言
John.Lewis2 小时前
Python小课(6)基础语法⑤
开发语言·python