结构体(struct)是C语言中一种重要的用户自定义数据类型,它允许将不同的数据类型组合在一起,形成一个逻辑单元。

一、基本概念

1.1 为什么需要结构体

基本数据类型(int\float\char等)只能表示单一类型的数据。当需要表示一个复杂的对象(如学生、图书、点坐标)时,需要将多个不同的数据组织在一起:

1
2
3
4
5
6
7
8
9
10
11
//使用结构体:数据分散、难以管理
char name[20];
int age;
float score;

//使用结构体:数据聚合、逻辑清晰
struct Student {
char name[20];
int age;
float score;
}

1.2 结构体的定义

1
2
3
4
5
struct 结构体名 {
数据类型 成员名1;
数据类型 成员名2;
// ...
}

示例:

1
2
3
4
5
6
7
8
9
10
11
struct Point {
int x;
int y;
}

struct Student {
char name[20];
int age;
float score;
char gender;
}

二、结构体变量的声明与初始化

2.1 三种声明方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 方式1:先定义结构体、再声明变量
struct Point {
int x;
int y;
};
struct Point p1, p2;

// 方式2:定义结构体的同时声明变量
struct Point {
int x;
int y;
} p1, p2;

// 方式3:匿名结构体(不推荐、无法复用)
struct {
int x;
int y;
} p1, p2;

2.2 初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 按顺序初始化
struct Point p1 = {10, 20};

// 指定成员初始化(C99)
struct Point p2 = {.y = 20, .x = 10};

// 部分初始化,其余为0
struct Point p3 = {10}; // x=10, y=0

// 嵌套结构体初始化
struct Student {
char name[20];
int age;
struct Point location; // 嵌套
};

struct Student s = {"Tom", 18, {3, 4}};

三、访问结构体成员

3.1 点运算符(.)

用于结构体变量:

1
2
3
4
struct (point p = {10,20});
printf("&d,%d\n",p.x,p.y);
p.x = 30;
strcpy(s.name,"jerry");

3.2箭头运算符(->)

用于结构体指针:

1
2
3
4
5
6
struct Point p = {10, 20};
struct Point *ptr = &p;

//以下两种写法等价
print("%d\n",(*ptr).x); //先解引用再用点
print("%d\n, ptr->x"; // 推荐写法

四、结构体内存布局

4.1 内存对齐

结构体在内存中并非简单地将成员大相加,编译器会进行内存对齐,以提高访问效率。

1
2
3
4
5
6
struct A {
char C; // 1字节
int i; // 4字节
char d; // 1字节
};
// sizeof(struct A); 通常为12,而不成6

对其规则:

  • 每个成员的偏移量必须是该成员大小的整数倍(或对齐模数的整数倍)
  • 结构体总大小必须是最大成员对齐数的整倍数

4.2 控制对齐

1
2
3
4
5
6
7
8
// 使用 #pragma pack 修改对齐方式
#pragma pack(push, 1) // 1字节对齐
struct Packed {
char c;
int i;
char d;
}; // sizeof = 6
#pragma pack(pop)

五、结构体与指针

5.1 结构体指针

1
2
3
4
5
6
7
struct Student s = {"Tom", 18, 90.5};
struct Student *p = &s;

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

p->age = 19; // 通过指针修改

5.2 作为函数参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 值传递:会复制整个结构体,开销大
void printStudent(struct Student s) {
printf("%s\n", s.name);
}

// 指针传递:推荐,效率高
void updateAge(struct Student *s, int newAge) {
s->age = newAge;
}

// const指针:防止修改
void printStudent(const struct Student *s) {
printf("%s\n", s->name);
}

六、结构体数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct Student students[3] = {
{"Tom", 18, 90.5},
{"Jerry", 19, 85.0},
{"Spike", 20, 78.5}
};

// 遍历
for (int i = 0; i < 3; i++) {
printf("%s: %.1f\n", students[i].name, students[i].score);
}

// 用指针遍历
struct Student *p = students;
for (int i = 0; i < 3; i++) {
printf("%s\n", (p + i)->name);
}

七、typedef 与结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 不使用typedef
struct Point {
int x, y;
};
struct Point p1; // 必须写 struct

// 使用typedef(推荐)
typedef struct {
int x, y;
} Point;
Point p1; // 更简洁

// 带标签的typedef(可在结构体内引用自身)
typedef struct Node {
int data;
struct Node *next; // 这里必须用 struct Node
} Node;

八、自引用结构体(链表)

1
2
3
4
5
6
7
8
9
10
11
typedef struct Node {
int data;
struct Node *next; // 指向同类型结构体
} Node;

// 使用
Node *head = malloc(sizeof(Node));
head->data = 1;
head->next = malloc(sizeof(Node));
head->next->data = 2;
head->next->next = NULL;

九、位域(Bit Fields)

用于节省内存,精确控制成员占用的位数:

1
2
3
4
5
6
struct Flags {
unsigned int flag1 : 1; // 1位
unsigned int flag2 : 1; // 1位
unsigned int value : 6; // 6位
unsigned int : 0; // 强制对齐到下一个存储单元
};

用途:硬件寄存器、协议报文、位标志等。

十、结构体的复制与比较