💓 博客主页:Kitten饭团的CSDN主页
📁 文章专栏:C语言专栏
期待您的关注
目录
前言
在学习变量、数组、指针后,在面对需要记录‘姓名、学号、3门科目成绩、班级’的学生信息时,你是否还在使用 int id; char name[5]; 等等来编写代码?这种方法不仅易错,不便于读取代码,还成为你学习数据结构时的阻碍。
数据结构研究的核心是如何高效组织与管理具有内在联系的复杂数据,链表的一个节点、二叉树的一个分支——这些都不是靠孤立变量能表达的,为了给数据结构打好基础,我们将进入对于结构体的学习。
1. 结构体的基础
在C语言中,可以使用结构体来将多种不同的数据类型组装起来,形成某种现实意义的自定义的变量类型。结构体本质上是一种自定义类型。
1.1 结构体的定义
struct 结构体标签
{
数据类型 成员1;
数据类型 成员2;
// ...
}; // 分号结尾
示例: 📍
学生结构体
struct Student
{
int id;
char name[50];
float score;
int age;
};
1.1.1 定义结构体并同时定义结构体变量
示例代码:
struct Student
{
int id;
char name[50];
float score;
int age;
}stu1, stu2; // 全局变量
int main()
{
return 0;
}
1.1.2 先定义结构体类型,再定义变量
示例代码:
struct Data
{
int year;
int month;
int day;
};
// 全局变量
struct Data time1;
struct Data time2;
int main()
{
// 局部变量
struct Data time3;
struct Data time4;
return 0;
}
1.2 结构体的初始化
由于结构体内部拥有多个不同类型的成员,因此初始化采用与数组类似的列表方式。
结构体的初始化有两种方式:①✨
普通初始化;②🎯指定成员初始化。
为了能适应结构体类型的升级迭代,一般建议采用指定成员初始化。
📝指定成员初始化的好处:
①成员初始化的次序可以改变。
②可以初始化一部分成员。
③结构体新增了成员之后初始化语句仍然可用。
1.2.1 定义时顺序初始化(普通初始化)
示例代码:
// 定义的时候顺序初始化
struct Student stu1 = { 10000, "xiaoming", 90.5, 15 };
1.2.2 定义时乱序初始化(指定成员初始化)
示例代码:
// 定义的时候乱序初始化(指定成员初始化)
struct Student stu2 = { .name = "lihua", .age = 14, .score = 95, .id = 10001};
1.2.3 定义完成后再逐个初始化(相当于赋值)
示例代码:
// 定义完成后,再分别初始化(赋值同理)
struct Student stu3;
stu3.id = 10002;
strcpy( stu3.name, "xiaohua"); // 字符串赋值需要通过strcpy函数
stu3.score = 91.5;
stu3.age = 15;
1.3 结构体的引用
1.3.1 通过点运算符(.)访问
📝 点运算符 (.) 的作用是访问结构体内的成员
可视化说明:
当输入 stu1. 时,现代IDE会智能提示所有可用成员,效果如图:
示例代码:
#include <stdio.h>
#include <string.h>
struct Student
{
int age;
int id;
char name[20];
float score;
};
int main()
{
// 定义并初始化结构体变量
struct Student stu1 = {18, 1001, "张三", 90.5};
// 使用点运算符访问成员
printf("年龄: %d\n", stu1.age); // 访问整型成员
printf("姓名: %s\n", stu1.name); // 访问字符数组成员
// 修改成员值
stu1.score = 95.0; // 修改浮点成员
strcpy(stu1.name, "张小三"); // 修改字符串成员
return 0;
}
打印结果:
1.3.2 通过箭头运算符(->)访问
📝 箭头运算符 (->) 是结构体指针访问成员的专用语法糖,本质是先解引用指针再访问成员的简写形式
可视化说明:
当输入 p2-> 时,现代IDE会智能提示所有可用成员,效果如图:
但是输入 p2. 时 ,有些编译器也会帮我们自动转换为 p2-> 并给出成员提示,实际是语法错误
示例代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct Student
{
int id;
char name[20];
float score;
};
int main()
{
// 场景1:指向栈区结构体变量
struct Student stu = {1001, "李四", 88.5};
struct Student *p1 = &stu; // 结构体指针:指针指向现有结构体
printf("ID: %d\n", p1->id); // 通过指针访问
p1->score = 92.0; // 通过指针修改
// 场景2:动态分配堆区结构体
struct Student *p2 = (struct Student*)malloc(sizeof(struct Student));
p2->id = 1002; // 为指针指向的结构体赋值
strcpy(p2->name, "王五");
free(p2); // 释放内存
}
打印结果:
2. typedef 的运用
当我们在定义结构体变量时,我们能发现这自定义结构体的数据类型每次使用时都得写一遍,显得还是不够方便,所以这里我们用到了 typedef 。
📝 typedef 的核心作用:
通过为结构体创建类型别名,避免重复书写struct关键字,提升代码简洁性和可维护性。
2.1 typedef 基础用法
示例代码:
// 原始写法(需重复写struct)
struct Student
{
int id;
char name[20];
};
struct Student s1; // 每次都要写struct
// 使用typedef优化
typedef struct
{
int id;
char name[20];
} Student; // 创建新类型名
Student s2; // 直接使用别名
2.2 typedef 实际用法
实际使用时,两种结合在一块更为实用,将结构体标签和结构体别名都保留下来
示例代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct Student
{
int id;
char name[50];
float score;
int age;
}Stu_t, *Stu_p; // 取别名
// Stu_t:结构体类型
// Stu_p:结构体指针类型
int main()
{
// 场景1:指向栈区结构体变量
Stu_t stu1 = {1001, "李四", 88.5};
Stu_p p1 = &stu1;
printf("ID: %d\n", p1->id); // 通过指针访问
p1->score = 92.0; // 通过指针修改
// 场景2:动态分配堆区结构体
Stu_p p2 = (Stu_p)malloc(sizeof(Stu_t));
p2->id = 1002; // 为指针指向的结构体赋值
strcpy(p2->name, "王五");
free(p2); // 释放内存
}
3. 结构体的尺寸
3.1 基础概念
示例代码:
typedef struct Example
{
char a; // 1字节
int b; // 4字节
double c; // 8字节
}exa_t;
int main()
{
printf("Size: %zu\n", sizeof(exa_t)); // 输出多少?
return 0;
}
打印结果:
📝 定义:结构体变量占用的连续内存字节数(通过sizeof获取)
📝 关键问题:成员尺寸简单相加(1+4+8=13)≠ 实际大小(通常更大)
3.2 内存对齐原则(核心规则)
📝 每个成员按自身类型大小对齐:
char :1字节对齐
short:2字节对齐
int / float:4字节对齐
double:8字节对齐
结构体整体对齐最终大小必须是最大成员尺寸的整数倍
结合图形分析:
以上我们就了解了结构体的大小到底如何进行计算了,包括其嵌套结构体也是一个道理,把内部结构体当做一个整体就行了比如上面那个当成16个字节放进去,然后按最大成员尺寸的整数倍进行计算即可。
📝 嵌套结构体规则:
内层结构体作为整体参与外层对齐
外层对齐基数 = max(内层结构体最大成员, 外层其他成员)
3.3 结构体内存的优化
📝 降序排列法(经典优化策略)
原则:按成员大小从大到小排列,尽量减少填充空隙。
示例对比:
// 未优化(12字节)
struct Unoptimized
{
char a; // 1 (+3填充)
int b; // 4
char c; // 1 (+3填充)
}; // 总大小: 1+3+4+1+3 = 12
// 优化后(8字节)
struct Optimized
{
int b; // 4
char a; // 1
char c; // 1 (+2填充)
}; // 总大小: 4+1+1+2 = 8
📝 手动填充控制(#pragma pack)
适用场景:网络传输、硬件寄存器映射等需精确控制内存布局时。
示例代码:
#pragma pack(1) // 强制1字节对齐
struct NetworkPacket
{
char header; // 1
int payload; // 4
}; // 大小=5(无填充)
#pragma pack() // 恢复默认对齐
风险:
可能引发性能下降(某些CPU架构无法高效访问未对齐数据)
跨平台兼容性问题
结构体的内容到此结束了,要是有不足希望各位能够在评论区指出,要是文章对你有帮助,可以给个免费的赞和收藏💞💞💞