C语言 · 结构体
2026/7/22 1:53:20 网站建设 项目流程

C语言·结构体

从为什么需要结构体到内存对齐,一次讲透

一、为什么需要结构体

假设你要管理一个学生信息系统。每个学生有姓名、学号、年龄、成绩。用单独的数组?

char names[100][32];

int ids[100];

int ages[100];

float scores[100];

这样写,几个问题:一是同一个学生的数据被拆散在四个数组里,逻辑上不内聚;二是排序时要同步移动四个数据,繁琐;三是参数传递时要传四个指针,冗长。

你想要的是一个能把属性绑在一起的容器——这就是结构体。

二、结构体的基本语法

1. 定义

struct Student {

char name[32];

int id;

int age;

float score;

};

结构体可以看作是你自己定义的新类型。struct 是关键字,Student 是你给这个类型起的名字。花括号里是成员列表。

2. 声明变量

struct Student s1; //直接声明

struct Student s2 = {"张三", 1001, 18, 92.5}; //初始化

typedef struct Student Student; //别名

Student s3; //省略struct

每次写 struct Student 太麻烦,第三行用 typedef 给它起了个简称 Student。也可以在定义时直接写:

typedef struct {

int x, y;

} Point;

3. 访问成员

s1.age = 20; //点操作符

printf("%s", s2.name); //读取

strcpy(s1.name, "李四"); //字符串成员要用strcpy

结构体变量用点操作符 "." 访问成员。如果是指针,则用箭头 "->"。

三、结构体与指针

1. 指针访问结构体

Student s = {"王五", 1002, 19, 88.0};

Student *p = &s;

p->age = 20; //等价(*p).age

p->age 是 (*p).age 的语法糖。为什么要用括号?因为点的优先级比星号高——*p.age 会被解释为 *(p.age)。

2. 动态分配结构体

Student *p = (Student*)malloc(sizeof(Student));

if (p == NULL) { /*分配失败*/ }

strcpy(p->name, "赵六");

p->id = 1003;

free(p);

malloc 返回 void*,必须强制转换为 Student*。用完记得 free。

四、结构体数组

结构体数组是结构体最常见的用法之一:

Student class[50];

for (int i = 0; i < 50; i++) {

scanf("%s %d %d %f", class[i].name,

&class[i].id, &class[i].age, &class[i].score);

}

结构体数组排序用 qsort:

int cmp(const void *a, const void *b) {

return ((Student*)b)->score - ((Student*)a)->score;
}

qsort(class, 50, sizeof(Student), cmp);

qsort 是标准库函数,需要你提供比较函数。不要自己写排序——qsort 已经帮你做好了。

五、内存对齐

结构体成员在内存中并不是紧密排列的。编译器会插入填充字节以满足对齐要求。

struct Demo {

char a; // 1字节

int b; // 4字节

char c; // 1字节

};

// sizeof(struct Demo) = 12,不是6

原因:编译器按最大成员对齐——int 要4字节对齐,所以 a 后面补 3 字节,c 后面补 3 字节。

解决方案:把大的成员放前面:

struct Demo2 {

int b; // 4字节-> sizeof=8

char a, c; // 1+1,后面补2

};

这叫结构体对齐,知道就行,不要迷信 sizeof。

六、嵌套结构体

结构体可以包含另一个结构体:

typedef struct {

int x, y;

} Point;

typedef struct {

Point start, end;

} Line;

Line l = {{0,0}, {10,10}};

printf("%d", l.start.x); //访问嵌套成员

但是不能包含自己类型的实例(无限递归大小)。可以包含自己类型的指针:

typedef struct Node {

int data;

struct Node *next; //指针,大小确定

} Node;

这就是链表的基础。

七、位段与联合体

1. 位段:省内存

struct Flags {

unsigned int is_on : 1; //只占1

unsigned int mode : 3; //只占3

unsigned int : 0; //强制对齐

};

位段在嵌入式开发中很常见,用来表示寄存器中每个 bit 的含义。

2. 联合体:共享内存

union Data {

int i;

float f;

char s[4];

};

union Data d;

d.i = 0x12345678;

// d.fd.s现在被覆盖了

联合体的所有成员共享同一块内存。sizeof(union) = 最大成员的大小。用途:节省内存、类型转换、判断大小端。

八、结构体传参

结构体作为函数参数时有三种方式:

void f1(Student s); //值传递,拷贝整个结构体,很慢

void f2(Student *p); //指针传递,只传8字节

void f3(const Student *p); //只读指针,不能修改

原则:用指针传递,能用 const 就用 const。结构体可能很大,值传递会在栈上拷贝整个结构体,浪费内存和时间。

"TotoI've a feeling we're not in Kansas anymore."

—— The Wizard of Oz

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询