C语言中文社区
学习路线 | AI编程 | 代码实例 | 实战源码 | 开发工具 | 视频教程 | 面试题 | 电子书 | 专注于C语言编程技术分享
183篇原创内容
公众号
C语言结构体(struct)是C编程中一种强大的用户自定义数据类型,它允许我们将不同类型的数据组合在一起,作为一个整体进行处理。结构体在C语言中扮演着至关重要的角色,为我们提供了灵活的数据组织方式,使我们能够创建更符合现实世界数据模型的数据结构。本报告将全面深入地讲解C语言结构体的相关知识,从基本概念到高级应用,帮助读者系统掌握这一重要编程工具。
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。与基本数据类型(如int、char)不同,结构体允许我们创建自定义的数据类型,以满足特定编程需求[1]。 结构体在C语言中的作用主要是封装。封装的好处是可以再次利用,让使用者不必关心内部实现细节,只需根据定义使用即可[1]。
在实际编程中,我们经常需要处理包含多种数据类型的变量。例如,一个学生的信息需要学号(字符串)、姓名(字符串)、年龄(整型)等。这些数据类型各不相同,但它们共同表示一个整体。结构体就将不同类型的数据存放在一起,作为一个整体进行处理[1]。 在实际项目中,结构体大量存在。研发人员常使用结构体来封装一些属性来组成新的类型。由于C语言无法操作数据库,所以在项目中通过对结构体内部变量的操作将大量数据存储在内存中,以完成对数据的存储和操作[1]。
在C语言中,声明结构体使用关键字struct
,后跟结构体名和花括号包裹的成员列表。
struct Point {
int x;
int y;
};
声明一个结构体类型后,我们可以用这个自定义的结构体类型去定义变量:
struct Point p;
我们也可以在声明结构体的同时定义结构体变量:
struct Point {
int x;
int y;
} p;
这种方式下,p就是我们刚刚定义的Point类型的变量。
C语言还允许我们不指定结构体名,直接定义变量:
struct {
int x;
int y;
} p = {, };
这种方式下,我们创建了一个匿名结构体,并立即定义了变量p。但匿名结构体的缺点是无法再定义其他同类型的变量。
为了简化结构体的使用,我们可以使用typedef
关键字为结构体创建别名:
typedef struct {
int x;
int y;
} Point;
Point p;
p.x = ;
这样,我们就可以直接使用Point作为类型名,而不需要每次都写struct Point
。
我们可以使用花括号包裹的初始化列表来初始化结构体变量:
struct Point p = {, };
这里的初始化顺序必须与结构体成员声明的顺序一致。
从C99标准开始,我们可以使用指定初始化(designated initializers)来初始化结构体的特定成员,而不必按照顺序初始化所有成员:
struct Point p = {.x = , .y = };
这种方式更加灵活,尤其是在处理大型结构体时。
以下是几种不同的初始化方式及其特点:
struct Point p = {, };
struct Point p = {.x = , .y = };
struct Point p = {};
这种方式下,只有第一个成员被显式初始化,其他成员被隐式置零。
对于结构体变量,我们可以使用点运算符(.)来访问其成员:
struct Point p;
p.x = ;
p.y = ;
当我们有一个指向结构体的指针时,可以使用箭头运算符(->)来访问结构体成员:
struct Point *ptr = &p;
ptr->x = ;
ptr->y = ;
使用指针访问结构体成员是C语言中常见的做法,尤其是在处理大型结构体或需要动态分配内存时。
结构体的大小并不是其所有成员的简单相加。在C语言中,结构体的内存分配遵循特定的对齐规则[1]。
以下是一个内存对齐的示例:
struct {
char a; // 占据1字节,但会被填充到4字节
int b; // 占据4字节
} s;
在这个例子中,char类型的a实际会占据4个字节(包括3个填充字节),而int类型的b占据4个字节,所以整个结构体的大小是8个字节,而不是1+4=5个字节。
为了减少结构体的内存占用,可以考虑以下技巧:
struct {
char a; // 放在前面
int b;
} s; // 可能占用8字节
与struct {
int b;
char a;
} s; // 可能占用4字节
#pragma pack(1) // 关闭对齐
struct {
char a;
int b;
} s;
#pragma pack() // 恢复默认对齐
结构体可以包含其他结构体作为其成员,这种结构称为嵌套结构体。嵌套结构体使我们能够创建更复杂的数据模型。
struct Date {
int day;
int month;
int year;
};
struct Employee {
char name[];
struct Date birth_date;
};
struct Employee e;
e.birth_date.day = ;
在这个例子中,Employee结构体包含了一个Date类型的成员birth_date,而Date本身是一个包含day、month和year的结构体。
我们也可以使用匿名结构体来嵌套结构体:
struct Employee {
char name[];
struct {
int day;
int month;
int year;
} birth_date;
};
struct Employee e;
e.birth_date.day = ;
这种方式下,birth_date是一个匿名的Date结构体,我们仍然可以使用点运算符来访问其成员。
结构体指针是指向结构体变量的指针。我们可以使用结构体指针来访问结构体成员。
struct Point p = {, };
struct Point *ptr = &p;
printf("x: %d, y: %d\n", ptr->x, ptr->y);
使用结构体指针可以更灵活地操作结构体,尤其是在需要动态分配内存或传递大型结构体时。
struct Point *ptr = malloc(sizeof(struct Point));
if (ptr == NULL) {
// 处理内存分配失败的情况
exit(EXIT_FAILURE);
}
ptr->x = ;
ptr->y = ;
free(ptr); // 释放内存
将结构体指针作为函数参数可以避免复制整个结构体,提高效率。
void printPoint(struct Point *p) {
printf("x: %d, y: %d\n", p->x, p->y);
}
struct Point p = {, };
printPoint(&p);
我们可以创建结构体数组,将多个结构体变量存储在一个数组中。
struct Point points[10]; // 定义一个包含10个Point元素的数组
points[].x = ; // 访问第一个元素的x成员
我们也可以初始化结构体数组:
struct Point points[] = {
{, },
{, },
{, }
};
C99引入了柔性数组(Flexible Array Member)的概念,允许我们在结构体中声明大小可变的数组。
struct FlexArray {
int len;
int arr[]; // 柔性数组
};
struct FlexArray *fa = malloc(sizeof(struct FlexArray) + 10 * sizeof(int));
fa->len = ;
柔性数组的特点:
函数可以接收结构体作为参数,但需要注意的是,这会导致整个结构体被复制,对于大型结构体来说可能效率不高。
void printPoint(struct Point p) {
printf("x: %d, y: %d\n", p.x, p.y);
}
struct Point p = {, };
printPoint(p); // 整个结构体被复制
为了提高效率,我们通常让函数接收结构体指针作为参数。
void printPoint(struct Point *p) {
printf("x: %d, y: %d\n", p->x, p->y);
}
struct Point p = {, };
printPoint(&p); // 仅传递指针,不复制整个结构体
函数也可以返回结构体,同样需要注意返回的是结构体的拷贝,而不是指针。
struct Point createPoint(int x, int y) {
struct Point p;
p.x = x;
p.y = y;
return p;
}
struct Point p = createPoint(10, 20);
静态分配的结构体在编译时分配内存,其生命周期持续到程序结束。
struct Point p; // 静态分配,通常在栈上
动态分配的结构体使用malloc、calloc等函数在堆上分配内存,需要手动管理内存。
struct Point *p = malloc(sizeof(struct Point)); // 动态分配
if (p == NULL) {
// 处理内存不足的情况
}
p->x = ;
p->y = ;
free(p); // 释放内存
对于结构体数组,我们可以动态分配内存,指定数组的大小。
int n = ;
struct Point *points = malloc(n * sizeof(struct Point));
if (points == NULL) {
// 处理内存不足的情况
}
for (int i = ; i < n; i++) {
points[i].x = i * ;
points[i].y = i * ;
}
free(points);
结构体可以用来创建链表,这是C语言中常用的数据结构。
struct Node {
int data;
struct Node *next;
};
struct Node *head = NULL;
// 添加节点到链表头部
void addNode(int data) {
struct Node *newNode = malloc(sizeof(struct Node));
if (newNode == NULL) {
return;
}
newNode->data = data;
newNode->next = head;
head = newNode;
}
// 遍历链表
void printList() {
struct Node *current = head;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
结构体可以用来组织文件中的数据,使文件读写更加方便。
struct Student {
char name[];
int age;
float grade;
};
// 写结构体到文件
void writeStudentToFile(const struct Student *s, const char *filename) {
FILE *file = fopen(filename, "wb");
if (file == NULL) {
printf("无法打开文件\n");
return;
}
fwrite(s, sizeof(struct Student), , file);
fclose(file);
}
// 从文件读取结构体
void readStudentFromFile(struct Student *s, const char *filename) {
FILE *file = fopen(filename, "rb");
if (file == NULL) {
printf("无法打开文件\n");
return;
}
fread(s, sizeof(struct Student), , file);
fclose(file);
}
虽然C语言不是面向对象的语言,但我们可以使用结构体来模拟面向对象编程的概念,如封装和消息传递。
// 定义一个点结构体
struct Point {
int x;
int y;
};
// 定义一个移动点的函数
void movePoint(struct Point *p, int deltaX, int deltaY) {
p->x += deltaX;
p->y += deltaY;
}
// 使用
struct Point p = {, };
movePoint(&p, , );
printf("新的坐标是(%d, %d)\n", p.x, p.y);
共用体(union)是一种特殊类型的结构体,其中的所有成员共享相同的内存位置。共用体在任何给定时间只能存储其中一个成员的值。
union Data {
int i;
float f;
char c;
};
C语言中的结构体与C++中的类有一些区别:
结构体是实现各种数据结构的基础,如链表、树、图等。
// 定义一个二叉树节点结构体
struct TreeNode {
int value;
struct TreeNode *left;
struct TreeNode *right;
};
// 创建二叉树节点
struct TreeNode *createNode(int value) {
struct TreeNode *node = malloc(sizeof(struct TreeNode));
if (node == NULL) {
returnNULL;
}
node->value = value;
node->left = NULL;
node->right = NULL;
return node;
}
在系统编程中,结构体广泛用于与操作系统API交互。
// 使用Windows API的RECT结构体
typedef struct _RECT {
LONG left;
LONG top;
LONG right;
LONG bottom;
} RECT, *PRECT;
RECT rect;
rect.left = ;
rect.top = ;
rect.right = ;
rect.bottom = ;
在网络编程中,结构体用于组织网络数据包。
// 定义一个简单的网络包结构体
struct NetworkPacket {
unsigned short type;
unsigned short length;
unsigned char data[];
};
// 创建网络包
struct NetworkPacket packet;
packet.type = 0x01;
packet.length = ;
// 填充数据
struct Point p;
p.z = ; // 错误,Point结构体没有z成员
struct Point *p = malloc(sizeof(struct Point));
p->x = ;
// 没有free(p)导致内存泄漏
struct Point *p = malloc(sizeof(struct Point));
free(p); // 释放了p指向的内存
p->x = ; // 错误,p现在是悬挂指针
struct {
char a;
int b;
} s;
// sizeof(s)可能是8而不是5
typedef struct {
int x;
int y;
} Point;
struct Student {
char name[];
int age;
float grade;
};
匿名结构体可以简化代码,特别是在处理层次化数据时。
struct Employee {
char name[];
struct { // 匿名结构体
int day;
int month;
int year;
} birth_date;
};
struct Employee e;
e.birth_date.day = ; // 直接访问匿名结构体的成员
当多个结构体有相同的前几个成员时,可以设计它们有相同的前缀,以便在某些情况下可以互换使用。
struct Person {
char name[];
int age;
};
struct Student {
char name[];
int age;
float grade;
};
// 这两个结构体有相同的前缀,可以互换使用前两个成员
通过调整成员顺序和使用编译器指令,可以优化结构体的内存占用。
// 原始结构体,可能占用12字节
struct {
char a; // 1字节
int b; // 4字节,但因为对齐,前面有3字节填充
short c; // 2字节,前面有2字节填充到4字节边界
} s;
// 优化后的结构体,可能占用8字节
struct {
int b; // 4字节
short c; // 2字节,没有填充
char a; // 1字节,后面有1字节填充到4字节边界
} s_optimized;
// 使用编译器指令调整对齐
#pragma pack(1) // 关闭对齐
struct {
char a;
int b;
short c;
} s_packed;
#pragma pack() // 恢复默认对齐
不同的编译器可能对结构体的处理略有不同,特别是在内存对齐和柔性数组方面。
不同平台上的C语言实现可能对结构体的处理有所不同。
#include <stdint.h>
uint32_t value = 0x12345678;
#ifdef _WIN32
// Windows平台的实现
#elif __linux__
// Linux平台的实现
#endif
C语言标准在不断演进,每个新版本都为结构体增加了新的功能。
随着编程语言和编程范式的不断发展,结构体的角色也在不断演变。
C语言结构体是一种强大的数据组织工具,它允许我们将不同类型的数据组合在一起,作为一个整体进行处理。通过本报告的详细讲解,我们系统地学习了结构体的基本概念、声明与定义、初始化、成员访问、内存管理、嵌套、指针、数组、函数交互以及高级应用等方面的知识。 结构体在C语言中扮演着至关重要的角色,它为我们提供了灵活的数据组织方式,使我们能够创建更符合现实世界数据模型的数据结构。无论是简单的数据集合,还是复杂的数据结构,结构体都能胜任。 在实际编程中,结构体的应用非常广泛,从数据结构的实现到系统编程、网络编程等。通过合理使用结构体,我们可以编写出更高效、更易于维护的代码。 随着C语言标准的不断演进和编程范式的不断发展,结构体的角色也在不断演变。虽然C语言不是面向对象语言,但通过结构体与函数的结合,我们可以模拟面向对象编程,实现封装和消息传递等概念。 总之,掌握C语言结构体是每个C语言程序员的必备技能。通过本文的学习,希望你能够系统地掌握结构体的相关知识,并在实际编程中灵活运用。
[1] C语言结构体(struct)最全的讲解(万字干货). https://zhuanlan.zhihu.com/p/520770506.