目录
[1.1 定义类型时直接创建变量:](#1.1 定义类型时直接创建变量:)
[1.2 先声明类型,再创建变量:](#1.2 先声明类型,再创建变量:)
[1.3 匿名结构体(仅用于一次性变量):](#1.3 匿名结构体(仅用于一次性变量):)
[2.1 成员访问: . 与 -> 的区别](#2.1 成员访问: . 与 -> 的区别)
[2. 2结构体传参:值传递 vs 地址传递](#2. 2结构体传参:值传递 vs 地址传递)
[3.1 对齐规则:](#3.1 对齐规则:)
[3.2 修改对齐数](#3.2 修改对齐数)
[四、 结构体实现位段](#四、 结构体实现位段)
[4.1 什么是位段](#4.1 什么是位段)
[4.2 位段的内存分配](#4.2 位段的内存分配)
[4.3 位段的跨平台问题](#4.3 位段的跨平台问题)
前言:
在C语言的编程体系中,"结构体"是实现自定义复合数据类型的核心机制,它让开发者能够将不同类型的数据封装为一个逻辑整体,是构建复杂数据结构(如链表、树)与贴近现实场景数据模型的基础。本文将结合"自定义类型"的学习框架,拆解结构体的核心用法与底层逻辑。
一、结构体:从"声明"到"初始化"的基础逻辑
结构体的使用首先从类型声明开始------通过 struct 关键字定义新的类型模板,例如描述学生信息的结构体:
// 声明结构体类型
struct Student {
char name[20]; // 字符数组存储姓名
int age; // 整型存储年龄
float score; // 浮点型存储成绩
};
声明仅定义"模板",需创建变量并初始化才能实际存储数据(此时并没有开辟新的空间)
1.1 定义类型时直接创建变量:
struct Student {
char name[20];
int age;
float score;
} stu1 = {"Zhang", 18, 92.5}; // 同时创建并初始化变量stu1
1.2 先声明类型,再创建变量:
struct Student stu2;
// 逐个成员赋值
strcpy(stu2.name, "Li");
stu2.age = 19;
stu2.score = 88.0;
1.3 匿名结构体(仅用于一次性变量):
struct {
int x;
int y;
} point = {3, 4}; // 无类型名,仅能在此处创建变量
二、结构体的核心操作:成员访问与传参
2.1 成员访问: . 与 -> 的区别
访问结构体成员需使用成员访问操作符:
-
当操作结构体变量时,用 (.):
printf("姓名:%s\n", stu1.name); // 输出"Zhang"
-
当操作结构体指针时,用 (-> ):
struct Student *p = &stu2;
printf("年龄:%d\n", p->age); // 输出19
数据在上面创建中已经初始化
2. 2结构体传参:值传递 vs 地址传递
结构体作为函数参数传递时,有两种方式:
-
值传递:将整个结构体拷贝一份传入函数,修改不会影响原变量(内存开销大):
void printStudent(struct Student s) {
printf("成绩:%.1f\n", s.score);
}
printStudent(stu1); // 传入stu1的拷贝 -
地址传递:传入结构体指针,修改会影响原变量(内存开销小):
void updateScore(struct Student *p, float newScore) {
p->score = newScore;
}
updateScore(&stu2, 90.0); // 修改stu2的成绩为90.0
实际开发中优先选择地址传递,尤其是结构体成员较多时。
三、底层细节:内存对齐与位段
结构体的内存布局并非简单的成员大小相加,而是遵循内存对齐规则------这是编译器为了提升CPU访问效率的优化策略:
3.1 对齐规则:
首先得掌握结构体的对齐规则:
- 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处 2
. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的⼀个对齐数 与 该成员变量大小的较齐值。
- VS 中默认的值为 8 -
Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小
-
结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的整数倍。
-
如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
struct Demo {
char a; // 占1字节,偏移0
int b; // 占4字节,偏移4(补3字节)
char c; // 占1字节,偏移8(补3字节)
};
// 总大小:12字节(而非1+4+1=6字节)
3.2 修改对齐数
可通过 #pragma pack(n) 指定对齐数,以平衡内存效率与空间占用。
若需进一步节省内存,可使用结构体位段------通过指定成员占用的二进制位数实现精细化存储:
#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{
//输出的结果是什么?
printf("%d\n", sizeof(struct S));
return 0;
}
四、 结构体实现位段
4.1 什么是位段
位段的声明和结构是类似的,有两个不同:
-
位段的成员必须是 int、unsigned int 或signed int ,在C99中位段成员的类型也可以选择其他类型。
-
位段的成员名后边有⼀个冒号和⼀个数字
struct BitField {
int a : 2; // 占用2位
int b : 5; // 占用5位
int c : 10; // 占用10位
};
4.2 位段的内存分配
-
位段的成员可以是 int unsigned int signed int 或者是 char 等类型
-
位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
-
位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
4.3 位段的跨平台问题
-
int 位段被当成有符号数还是无符号数是不确定的。
-
位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会
出问题。
-
位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
-
当⼀个结构包含两个位段,第⼆个位段成员比较大,无法容纳于第⼀个位段剩余的位时,是舍弃
剩余的位还是利用,这是不确定的。
总结:
跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但位段的跨平台兼容性较差(不同编译器的存储规则可能不同),需谨慎使用
总结:
结构体是C语言脱离"单一基础类型"、实现灵活数据组织的关键工具,其语法虽简单,但内存对齐、传参方式等细节直接影响程序的效率与稳定性。掌握结构体,是从"基础语法"迈向"实际开发"的重要一步。