Week 09: 数组与结构体
掌握多维数组、结构体定义、内存布局、位域与嵌套结构.
1. 多维数组
1.1 二维数组
// 声明与初始化
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 访问元素
matrix[1][2] = 100; // 第2行第3列
// 遍历
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}1.2 内存布局
C 语言多维数组按行优先 (Row-major) 存储:
matrix[3][4] 在内存中:
[0][0] [0][1] [0][2] [0][3] [1][0] [1][1] [1][2] [1][3] [2][0] ...
地址计算:
&matrix[i][j] = base + (i * 4 + j) * sizeof(int)1.3 二维数组参数
// 必须指定列数
void print_matrix(int mat[][4], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 4; j++) {
printf("%d ", mat[i][j]);
}
printf("\n");
}
}
// 使用数组指针
void print_matrix_ptr(int (*mat)[4], int rows);
// 使用指针和手动计算
void print_matrix_flat(int *mat, int rows, int cols) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", mat[i * cols + j]);
}
printf("\n");
}
}1.4 可变长数组 (VLA, C99)
void process(int rows, int cols, int mat[rows][cols]) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
mat[i][j] *= 2;
}
}
}
int main(void) {
int m[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}};
process(3, 4, m);
return 0;
}2. 结构体基础
2.1 结构体定义
// 定义结构体类型
struct Point {
int x;
int y;
};
// 声明变量
struct Point p1;
struct Point p2 = {3, 4}; // 按顺序初始化
struct Point p3 = {.y = 4, .x = 3}; // 指定成员
// 访问成员
p1.x = 10;
p1.y = 20;
printf("(%d, %d)\n", p1.x, p1.y);2.2 typedef
typedef struct {
int x;
int y;
} Point;
Point p = {3, 4}; // 不需要 struct 关键字
// 或者
typedef struct Point {
int x;
int y;
} Point;2.3 结构体数组
typedef struct {
char name[50];
int age;
} Person;
Person people[3] = {
{"Alice", 25},
{"Bob", 30},
{"Charlie", 35}
};
for (int i = 0; i < 3; i++) {
printf("%s: %d\n", people[i].name, people[i].age);
}2.4 结构体指针
Point p = {3, 4};
Point *ptr = &p;
// 访问成员
printf("x = %d\n", (*ptr).x);
printf("y = %d\n", ptr->y); // -> 是简写
// 动态分配
Point *dyn = malloc(sizeof(Point));
dyn->x = 10;
dyn->y = 20;
free(dyn);3. 结构体内存布局
3.1 对齐规则
struct Example {
char a; // 1 字节, 偏移 0
// 3 字节填充
int b; // 4 字节, 偏移 4
char c; // 1 字节, 偏移 8
// 3 字节填充
};
printf("sizeof = %zu\n", sizeof(struct Example)); // 123.2 offsetof 宏
#include <stddef.h>
struct Example {
char a;
int b;
char c;
};
printf("offsetof(a) = %zu\n", offsetof(struct Example, a)); // 0
printf("offsetof(b) = %zu\n", offsetof(struct Example, b)); // 4
printf("offsetof(c) = %zu\n", offsetof(struct Example, c)); // 83.3 优化布局
// 未优化: 12 字节
struct Bad {
char a; // 1 + 3 padding
int b; // 4
char c; // 1 + 3 padding
};
// 优化: 8 字节
struct Good {
int b; // 4
char a; // 1
char c; // 1 + 2 padding
};3.4 禁用对齐
// GCC 扩展
struct __attribute__((packed)) Packed {
char a;
int b;
char c;
};
printf("sizeof = %zu\n", sizeof(struct Packed)); // 6
// 注意: packed 结构可能导致未对齐访问, 影响性能或在某些架构上崩溃4. 结构体操作
4.1 结构体赋值
Point p1 = {3, 4};
Point p2;
p2 = p1; // 成员逐一复制
// 等价于
memcpy(&p2, &p1, sizeof(Point));4.2 结构体比较
Point p1 = {3, 4};
Point p2 = {3, 4};
// 不能直接比较
// if (p1 == p2) { } // 编译错误
// 方法 1: 逐个成员比较
if (p1.x == p2.x && p1.y == p2.y) {
printf("Equal\n");
}
// 方法 2: memcmp (注意填充字节可能不同)
if (memcmp(&p1, &p2, sizeof(Point)) == 0) {
printf("Equal\n");
}4.3 结构体作为函数参数
// 值传递 (复制)
void print_point(Point p) {
printf("(%d, %d)\n", p.x, p.y);
}
// 指针传递 (高效)
void print_point_ptr(const Point *p) {
printf("(%d, %d)\n", p->x, p->y);
}
// 修改结构体
void move_point(Point *p, int dx, int dy) {
p->x += dx;
p->y += dy;
}4.4 返回结构体
Point make_point(int x, int y) {
Point p = {x, y};
return p; // 返回副本
}
// 或者通过指针参数
void init_point(Point *p, int x, int y) {
p->x = x;
p->y = y;
}5. 嵌套结构体
typedef struct {
int x, y;
} Point;
typedef struct {
Point center;
int radius;
} Circle;
Circle c = {{10, 20}, 5};
// 或
Circle c = {.center = {10, 20}, .radius = 5};
printf("Center: (%d, %d)\n", c.center.x, c.center.y);
printf("Radius: %d\n", c.radius);6. 位域 (Bit-fields)
6.1 定义
struct Flags {
unsigned int flag1 : 1; // 1 位
unsigned int flag2 : 1; // 1 位
unsigned int value : 4; // 4 位
unsigned int : 0; // 强制对齐到下一个单位
unsigned int flag3 : 1;
};
struct Flags f = {0};
f.flag1 = 1;
f.value = 10;
printf("sizeof = %zu\n", sizeof(struct Flags));6.2 应用场景
// 硬件寄存器
struct StatusRegister {
unsigned int ready : 1;
unsigned int error : 1;
unsigned int mode : 3;
unsigned int reserved : 27;
};
// 协议头
struct TCPHeader {
uint16_t src_port;
uint16_t dst_port;
uint32_t seq_num;
uint32_t ack_num;
unsigned int data_offset : 4;
unsigned int reserved : 6;
unsigned int flags : 6;
uint16_t window;
// ...
};6.3 注意事项
- 位域不能取地址
- 跨平台行为可能不同
- 建议使用位掩码操作代替关键场景
6.4 柔性数组成员 (Flexible Array Member, C99)
// 结构体末尾的不完整数组
struct Packet {
uint16_t length;
uint16_t type;
uint8_t data[]; // 柔性数组, 必须是最后一个成员
};
// sizeof 不包含柔性数组
printf("%zu\n", sizeof(struct Packet)); // 4
// 分配时包含数据空间
size_t data_len = 100;
struct Packet *pkt = malloc(sizeof(struct Packet) + data_len);
pkt->length = data_len;
memcpy(pkt->data, src, data_len);
// 使用
for (int i = 0; i < pkt->length; i++) {
printf("%02x ", pkt->data[i]);
}
free(pkt);柔性数组的优势:
- 单次分配, 内存连续
- 缓存友好
- 避免额外的指针间接寻址
6.5 container_of 宏 (内核风格)
#include <stddef.h>
// 从成员指针获取包含它的结构体指针
#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))
struct Node {
int value;
struct Node *next;
};
struct Container {
int id;
struct Node node; // 嵌入的结构体
char name[32];
};
void process_node(struct Node *n) {
// 从 node 成员获取 Container 指针
struct Container *c = container_of(n, struct Container, node);
printf("Container id: %d, name: %s\n", c->id, c->name);
}
int main(void) {
struct Container c = {.id = 42, .node = {.value = 100}, .name = "test"};
process_node(&c.node); // 只传递嵌入成员的指针
return 0;
}container_of 在 Linux 内核中广泛使用:
- 链表实现 (
list_entry) - 红黑树节点
- 设备驱动模型
7. 练习
7.1 复数运算
定义复数结构体, 实现加减乘操作.
7.2 矩阵运算
实现矩阵乘法函数.
7.3 学生成绩管理
设计结构体存储学生信息, 实现排序和统计功能.
8. 思考题
- 为什么结构体需要内存对齐?
- 如何计算结构体成员的偏移量?
- 值传递结构体有什么问题?
- 位域的应用场景是什么?
- 嵌套结构体如何访问?
9. 本周小结
- 多维数组: 行优先存储, 参数传递.
- 结构体: 定义, 初始化, 访问.
- 内存布局: 对齐, offsetof, packed.
- 结构体操作: 赋值, 比较, 参数传递.
- 嵌套结构体: 结构体中的结构体.
- 位域: 紧凑的位级成员.
结构体是组织复杂数据的基础. 理解内存布局, 才能写出高效的代码.