C语言结构体详解:从基本定义到高级应用

C语言结构体详解:从基本定义到高级应用

在C语言中,结构体(struct)是组织和存储相关数据的最重要工具之一。结构体允许你将多个不同类型的数据项组合成一个单一的复合数据类型,这对于表示现实世界中的对象(如学生、员工、产品等)特别有用。结构体不仅提供了数据封装的基础,还是构建更复杂数据结构(如链表、树等)的基石。本篇博客将深入探讨C语言结构体的各个方面,从基本定义到高级应用。

一、结构体简介:自定义复合数据类型

结构体是一种用户自定义的数据类型,它允许将多个不同类型的变量组合在一起,形成一个逻辑上的整体。每个结构体内部的变量称为“成员”或“字段”。

基本结构体定义

C
1
2
3
4
5
6
struct book {
char title[50];
char author[50];
float price;
int pages;
};

定义并声明变量

C
1
2
3
4
5
6
struct book {
char title[50];
char author[50];
float price;
int pages;
} book1, book2;

简化定义(C99标准):

C
1
2
3
4
5
6
7
8
9
struct book {
char title[50];
char author[50];
float price;
int pages;
};

typedef struct book Book; // 创建别名
Book book1, book2;

二、结构体成员的访问与赋值

使用点运算符.可以访问结构体的成员,并对其进行赋值或读取。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <string.h>

struct book {
char title[50];
char author[50];
float price;
int pages;
};

int main() {
struct book book1;

// 使用strcpy()为字符数组赋值
strcpy(book1.title, "C Programming Language");
strcpy(book1.author, "Brian W. Kernighan and Dennis M. Ritchie");
book1.price = 59.99;
book1.pages = 272;

printf("书名: %s\n", book1.title);
printf("作者: %s\n", book1.author);
printf("价格: %.2f\n", book1.price);
printf("页数: %d\n", book1.pages);

return 0;
}

注意:对于字符数组类型的成员,必须使用strcpy()函数进行赋值,而不是直接使用赋值运算符=,因为字符数组名在C语言中是地址常量。

三、结构体的初始化

可以在声明结构体变量的同时进行初始化。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>

struct book {
char title[50];
char author[50];
float price;
int pages;
};

int main() {
// 完全初始化
struct book book1 = {
"C Programming Language",
"Brian W. Kernighan and Dennis M. Ritchie",
59.99,
272
};

// 部分初始化(C99标准)
struct book book2 = {
.title = "The C Programming Language",
.price = 49.99
// 其他成员保持默认值(0或空)
};

printf("book1作者: %s\n", book1.author);
printf("book2价格: %.2f\n", book2.price);

return 0;
}

初始化规则

  1. 完全初始化时,值按成员顺序对应
  2. 部分初始化时,未指定的成员自动初始化为0(数值类型)或空字符串(字符数组)
  3. 使用.运算符指定特定成员进行初始化(C99及以上)

四、结构体的复制

C语言允许将一个结构体变量直接赋值给另一个同类型的结构体变量,这称为“结构体的复制”。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <stdio.h>
#include <string.h>

struct book {
char title[50];
char author[50];
float price;
int pages;
};

int main() {
struct book book1;
strcpy(book1.title, "C Programming");
strcpy(book1.author, "Author Name");
book1.price = 39.99;
book1.pages = 300;

// 结构体复制
struct book book2 = book1;

printf("book2标题: %s\n", book2.title);
printf("book2价格: %.2f\n", book2.price);

// 修改book1不影响book2
book1.price = 49.99;
printf("book1新价格: %.2f\n", book1.price);
printf("book2价格: %.2f\n", book2.price); // 仍然是39.99

return 0;
}

重要特性:结构体复制是“值复制”,而不是“引用复制”。复制后两个结构体完全独立,修改其中一个不会影响另一个。

五、结构体指针

可以使用指针来操作结构体,通过指针访问结构体成员需要使用箭头运算符->

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct book {
char title[50];
char author[50];
float price;
int pages;
};

int main() {
struct book book1;
struct book *ptr;

ptr = &book1;

// 通过指针访问结构体成员
strcpy(ptr->title, "Advanced C Programming");
strcpy(ptr->author, "Expert Author");
ptr->price = 79.99;
ptr->pages = 450;

// 等价写法
(*ptr).price = 69.99; // 使用解引用和点运算符

printf("书名: %s\n", ptr->title);
printf("价格: %.2f\n", ptr->price);

return 0;
}

指针运算符总结

  1. ptr->member:通过指针访问成员(推荐)
  2. (*ptr).member:先解引用再访问成员
  3. 两种写法等价,但->运算符更简洁直观

六、动态分配结构体内存

可以使用malloc()函数动态分配结构体内存,这在程序运行时需要不确定数量的结构体时非常有用。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct book {
char title[50];
char author[50];
float price;
int pages;
};

int main() {
struct book *book_ptr;

// 动态分配内存
book_ptr = malloc(sizeof(struct book));

if (book_ptr == NULL) {
printf("内存分配失败!\n");
return 1;
}

// 使用分配的内存
strcpy(book_ptr->title, "Dynamic Memory in C");
strcpy(book_ptr->author, "Memory Expert");
book_ptr->price = 45.50;
book_ptr->pages = 320;

printf("动态分配的书籍信息:\n");
printf("标题: %s\n", book_ptr->title);
printf("价格: %.2f\n", book_ptr->price);

// 释放内存
free(book_ptr);
book_ptr = NULL;

return 0;
}

七、结构体的嵌套

结构体可以包含其他结构体作为成员,这称为“结构体嵌套”。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <string.h>

struct name {
char first[20];
char last[20];
};

struct student {
struct name name; // 嵌套结构体
short age;
char sex;
};

int main() {
struct student student1;

// 访问嵌套结构体成员
strcpy(student1.name.first, "Harry");
strcpy(student1.name.last, "Potter");
student1.age = 18;
student1.sex = 'M';

printf("学生姓名: %s %s\n",
student1.name.first,
student1.name.last);
printf("年龄: %d\n", student1.age);

// 另一种初始化方式
struct name myname = {"Hermione", "Granger"};
struct student student2 = {myname, 18, 'F'};

printf("学生2姓名: %s %s\n",
student2.name.first,
student2.name.last);

return 0;
}

嵌套结构体访问:使用多个.运算符,如student1.name.first

八、结构体的自引用:构建链表

结构体可以包含指向自身类型的指针,这是构建链表、树等动态数据结构的基础。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <stdlib.h>

struct node {
int data;
struct node* next; // 指向自身的指针
};

int main() {
struct node* head;

// 创建三个节点的链表: (11)->(22)->(33)
head = malloc(sizeof(struct node));
head->data = 11;

head->next = malloc(sizeof(struct node));
head->next->data = 22;

head->next->next = malloc(sizeof(struct node));
head->next->next->data = 33;
head->next->next->next = NULL;

// 遍历链表
printf("链表内容: \n");
for (struct node *cur = head; cur != NULL; cur = cur->next) {
printf("%d -> ", cur->data);
}
printf("NULL\n");

// 释放链表内存(简化版)
struct node *temp;
while (head != NULL) {
temp = head;
head = head->next;
free(temp);
}

return 0;
}

自引用结构体的应用

  1. 单向链表、双向链表
  2. 二叉树、多叉树
  3. 图结构
  4. 其他递归数据结构

九、位字段(Bit Fields)

位字段允许在结构体中定义精确的二进制位宽度,这对于操作底层硬件或节省内存空间非常有用。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

struct {
unsigned int flag1 : 1; // 1位宽度
unsigned int flag2 : 1;
unsigned int flag3 : 1;
unsigned int flag4 : 1;
} status;

int main() {
status.flag1 = 0;
status.flag2 = 1;
status.flag3 = 0;
status.flag4 = 1;

printf("状态标志: %d%d%d%d\n",
status.flag1, status.flag2,
status.flag3, status.flag4);

return 0;
}

位字段的高级用法

C
1
2
3
4
5
6
7
struct {
unsigned int field1 : 1;
unsigned int : 2; // 未命名位字段,占2位
unsigned int field2 : 1;
unsigned int : 0; // 宽度为0,强制下一个字段在新字节开始
unsigned int field3 : 1;
} bits;

位字段规则

  1. 只能用于整数类型(int、unsigned int等)
  2. 位字段的总宽度不能超过其基础类型的宽度
  3. 未命名位字段可用于占位或对齐
  4. 宽度为0的字段强制下一个字段在新字节开始

十、弹性数组成员(Flexible Array Member)

弹性数组成员允许在结构体末尾定义大小不确定的数组,这在运行时需要动态大小的数组时非常有用。

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct vstring {
int len;
char chars[]; // 弹性数组成员,必须在最后
};

int main() {
int n = 20; // 数组长度在运行时确定

// 分配结构体内存 + 数组内存
struct vstring* str = malloc(sizeof(struct vstring) + n * sizeof(char));

if (str == NULL) {
printf("内存分配失败!\n");
return 1;
}

str->len = n;
strcpy(str->chars, "Hello, Flexible Array!");

printf("长度: %d\n", str->len);
printf("内容: %s\n", str->chars);

free(str);
return 0;
}

弹性数组成员规则

  1. 必须是结构体的最后一个成员
  2. 结构体必须至少还有一个其他成员
  3. 不能对弹性数组成员使用sizeof
  4. 需要手动计算总内存大小进行分配

十一、结构体作为函数参数和返回值

结构体作为函数参数(值传递)

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

struct point {
int x;
int y;
};

void print_point(struct point p) {
printf("坐标: (%d, %d)\n", p.x, p.y);
}

int main() {
struct point pt = {10, 20};
print_point(pt);
return 0;
}

结构体作为函数参数(指针传递,更高效)

C
1
2
3
4
5
6
7
8
void modify_point(struct point *p) {
p->x += 5;
p->y += 5;
}

void print_point_ptr(const struct point *p) {
printf("坐标: (%d, %d)\n", p->x, p->y);
}

结构体作为函数返回值

C
1
2
3
4
struct point create_point(int x, int y) {
struct point p = {x, y};
return p;
}

十二、结构体数组

C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

struct student {
char name[50];
int score;
};

int main() {
struct student class[3] = {
{"Alice", 95},
{"Bob", 87},
{"Charlie", 92}
};

for (int i = 0; i < 3; i++) {
printf("学生%d: %s, 分数: %d\n",
i+1, class[i].name, class[i].score);
}

return 0;
}

十三、结构体最佳实践

  1. 使用typedef简化

    C
    1
    2
    3
    4
    5
    6
    typedef struct {
    char title[50];
    float price;
    } Book;

    Book book1; // 无需写struct关键字
  2. 优先使用指针传递:传递大型结构体时,使用指针避免复制开销

  3. const保护数据:对于只读访问,使用const指针

  4. 内存对齐考虑:结构体成员会自动对齐,但可以通过#pragma pack控制

  5. 灵活使用位字段:需要精确控制内存布局时使用位字段

十四、总结

结构体是C语言中最强大的数据组织工具之一,掌握结构体的使用对于编写高质量的C程序至关重要:

  1. 基本操作:定义、声明、初始化、成员访问
  2. 复制特性:值复制,完全独立
  3. 指针操作:使用->运算符访问成员
  4. 嵌套结构体:构建复杂的数据层次
  5. 自引用结构体:构建链表、树等动态数据结构
  6. 位字段:精确控制二进制位布局
  7. 弹性数组成员:运行时确定数组大小
  8. 函数使用:作为参数和返回值
  9. 结构体数组:处理多个相关数据项

结构体不仅是数据存储的工具,更是抽象思维在代码中的体现。通过结构体,我们可以将现实世界的对象映射到程序中,使代码更加清晰、可维护。从简单的数据记录到复杂的数据结构,结构体都是C语言程序员不可或缺的利器。


C语言结构体详解:从基本定义到高级应用
https://www.edenzeng.online/2015/10/28/0.技术栈/01.开发语言/01.C语言/11-结构体详解/
作者
Edenzeng
发布于
2015年10月28日
许可协议