C 结构体
C - 结构体 (struct)
Section titled “C - 结构体 (struct)”数组将相同数据类型的项目组合在一起,而结构体(struct)是用户自定义的类型,允许您将不同数据类型的项目组合到单个名称下。它们对于组织相关数据至关重要。
想象一下,您需要表示二维平面上一个点的信息。您需要一个 x 坐标和一个 y 坐标,它们可能具有不同的类型(例如,double)。结构体非常适合这种情况:
- X 坐标(例如,
double x;) - Y 坐标(例如,
double y;)
您使用 struct 关键字定义结构体,后面跟着一个可选的标签名称和一个包含成员定义的 {} 块。
struct [structure_tag] { type member1_name; type member2_name; // ... other members} [optional_variable_declarations];structure_tag 是结构体类型的名称。每个 member_definition 都是一个标准的变量声明。您可以在闭合花括号后立即声明结构体类型的变量,但更常见的做法是使用 struct structure_tag variable_name; 单独声明它们。
示例:定义一个 Point 结构体。
// 定义名为 'Point' 的结构体struct Point { double x; double y;};
// 定义一个 Book 结构体(使用 typedef 方便后续使用)typedef struct { char title[100]; char author[100]; int year_published; double price;} Book;访问结构体成员
Section titled “访问结构体成员”要访问结构体变量的成员,使用成员访问运算符,即点号(.)。
语法:structure_variable.member_name
#include <stdio.h>#include <string.h> // For strncpy
// Define the Point structurestruct Point { double x; double y;};
// Use the Book typedef from the previous exampletypedef struct { char title[100]; char author[100]; int year_published; double price;} Book;
int main(void) { // Declare structure variables struct Point p1, p2; Book book1;
// --- Initialize p1 --- p1.x = 10.5; p1.y = -3.7;
// --- Initialize p2 using designated initializers (C99+) --- // 使用指定初始化器初始化 p2 (C99+) p2 = (struct Point){ .x = 0.0, .y = 5.0 }; // Compound literal initialization
// --- Initialize book1 --- strncpy(book1.title, "The C Programming Language", sizeof(book1.title) - 1); book1.title[sizeof(book1.title) - 1] = '\0'; // Ensure null termination // 确保字符串以空字符结束 strncpy(book1.author, "Kernighan & Ritchie", sizeof(book1.author) - 1); book1.author[sizeof(book1.author) - 1] = '\0'; book1.year_published = 1978; book1.price = 25.99;
// --- Access and print members --- // 访问并打印成员 printf("Point p1: (%.2f, %.2f)\n", p1.x, p1.y); printf("Point p2: (%.2f, %.2f)\n", p2.x, p2.y);
printf("\nBook 1 Details:\n"); printf(" Title: %s\n", book1.title); printf(" Author: %s\n", book1.author); printf(" Year: %d\n", book1.year_published); printf(" Price: $%.2f\n", book1.price);
return 0;}输出:
Point p1: (10.50, -3.70)Point p2: (0.00, 5.00)
Book 1 Details: Title: The C Programming Language Author: Kernighan & Ritchie Year: 1978 Price: $25.99结构体作为函数参数
Section titled “结构体作为函数参数”结构体可以通过两种方式传递给函数:
- 按值传递(Pass by Value): 结构体的整个副本被传递给函数。函数内部的更改不会影响原始结构体。对于大型结构体来说,由于复制开销,这种方式可能效率较低。
- 按指针传递(Pass by Pointer): 结构体的地址被传递。函数通过指针直接操作原始结构体。对于大型结构体来说,这种方式更有效率,并且允许函数修改原始结构体。
示例:按值传递与按指针传递
#include <stdio.h>
// Define the Point structurestruct Point { double x; double y;};
// Function taking Point by value (makes a copy)// 函数按值传递 Point (创建副本)void print_point_value(struct Point p) { printf("Value: (%.2f, %.2f)\n", p.x, p.y); // p.x = 0; // Change here would NOT affect the original p1 // 在此处更改不会影响原始 p1}
// Function taking Point by pointer (accesses original)// 函数按指针传递 Point (访问原始结构体)void print_point_pointer(const struct Point *p_ptr) { // Use const if not modifying // 如果不修改,使用 const // Access members using the arrow operator (->) // 使用箭头运算符 (->) 访问成员 printf("Pointer: (%.2f, %.2f)\n", p_ptr->x, p_ptr->y); // p_ptr->x = 0; // Change here WOULD affect the original p1 (if not const) // 在此处更改会影响原始 p1 (如果不使用 const)}
// Function to modify Point via pointer// 通过指针修改 Point 的函数void move_point(struct Point *p_ptr, double dx, double dy) { p_ptr->x += dx; p_ptr->y += dy;}
int main(void) { struct Point p1 = { 3.0, 4.0 };
printf("Original p1: "); print_point_value(p1); // Pass by value // 按值传递
printf("Original p1 via pointer: "); print_point_pointer(&p1); // Pass address (pointer) // 传递地址 (指针)
move_point(&p1, 1.0, -2.0); // Modify original p1 // 修改原始 p1 printf("Moved p1: "); print_point_pointer(&p1); // Print modified p1 // 打印修改后的 p1
return 0;}输出:
Original p1: Value: (3.00, 4.00)Original p1 via pointer: Pointer: (3.00, 4.00)Moved p1: Pointer: (4.00, 2.00)指向结构体的指针
Section titled “指向结构体的指针”您可以声明持有结构体变量内存地址的指针。
struct Point *ptr_p1; // Declares a pointer to a Point structure// 声明一个指向 Point 结构体的指针使用地址运算符(&)分配结构体变量的地址。
struct Point p1 = {1.0, 2.0};ptr_p1 = &p1; // ptr_p1 now holds the address of p1// ptr_p1 现在持有 p1 的地址要通过指针访问结构体的成员,使用箭头运算符(->)。
ptr_p1->x = 10.0; // Equivalent to (*ptr_p1).x = 10.0// 等价于 (*ptr_p1).x = 10.0ptr_p1->y = 20.0; // Equivalent to (*ptr_p1).y = 20.0// 等价于 (*ptr_p1).y = 20.0
printf("Point via pointer: (%.2f, %.2f)\n", ptr_p1->x, ptr_p1->y);表达式 (*ptr_p1).x 首先解引用指针(*ptr_p1)以获取结构体本身,然后使用点运算符(.)访问成员。箭头运算符(->)是这种常见操作的便捷简写形式。
位域(Bit Fields)
Section titled “位域(Bit Fields)”位域允许您定义结构体成员,这些成员占据特定的位数,而不是完整的字节或字。这对于以下情况很有用:
- 节省内存(Memory Saving): 将多个布尔标志或小型整数值打包到单个整数成员中。
- 硬件接口(Hardware Interfacing): 使数据结构直接匹配硬件寄存器或特定二进制数据格式,其中字段不按字节对齐。
您通过指定成员类型(通常是 unsigned int 或 int)、成员名称、一个冒号 : 和位数来定义位域。
struct HardwareRegister { unsigned int enable : 1; // 1 bit for enable flag // 启用标志,占 1 位 unsigned int error_flag : 1; // 1 bit for error flag // 错误标志,占 1 位 unsigned int mode : 3; // 3 bits for mode (0-7) // 模式,占 3 位 (0-7) unsigned int reserved : 3; // 3 unused bits (padding) // 保留字段,占 3 位 (填充) // Total: 1+1+3+3 = 8 bits (likely fits in 1 byte or int) // 总计: 1+1+3+3 = 8 位 (可能存储在 1 字节或 int 中)};
int main(void) { struct HardwareRegister reg = {0}; // Initialize all bits to 0 // 初始化所有位为 0
reg.enable = 1; // Set the enable bit // 设置启用位为 1 reg.mode = 5; // Set mode to 5 (binary 101) // 设置模式为 5 (二进制 101)
if (reg.error_flag == 0) { printf("No error detected.\n"); } printf("Mode: %u\n", reg.mode); printf("Size of register struct: %zu bytes\n", sizeof(reg));
return 0;}关于位域的重要注意事项:
- 可移植性(Portability): 位域在内存中的确切布局(顺序、填充)是实现定义的(取决于编译器和架构)。依赖特定位域布局的代码可能不可移植。
- 可寻址性(Addressability): 您不能获取位域成员的地址(
&)。 - 性能(Performance): 访问位域可能比访问常规成员慢,因为需要额外的位掩码和位移操作。
- 类型(Type): 虽然
int和unsigned int是常见的类型,但根据实现的不同,可能允许使用其他整数类型。
当内存节省或硬件映射是主要考虑因素时,请谨慎使用位域,但要了解其对可移植性的影响。