深入理解 C 语言中的结构体 —— 原理与实践

news2024/11/22 23:25:15

引言

在 C 语言中,结构体是一种非常强大的数据类型,用于组织不同类型的数据成员。通过结构体,我们可以创建复杂的数据结构,用于表示现实生活中的对象。本文将详细介绍 C 语言中结构体的基本概念、语法、使用方法以及一些高级主题,包括底层原理和具体应用。
在这里插入图片描述

结构体基础知识

定义结构体

结构体通过关键字 struct 来定义。结构体中可以包含不同类型的成员变量,这些成员可以是基本类型(如 int, float 等)也可以是其他结构体类型。

struct student {
    char name[50]; // 学生姓名
    int age;       // 年龄
    float gpa;     // 平均成绩点数
};
使用结构体

定义结构体后,可以通过结构体变量来使用它。

struct student s1;
strcpy(s1.name, "Alice");
s1.age = 20;
s1.gpa = 3.5;
访问结构体成员

可以通过.操作符来访问结构体成员。

printf("Name: %s, Age: %d, GPA: %.2f\n", s1.name, s1.age, s1.gpa);

结构体的高级用法

匿名结构体

匿名结构体没有名称,主要用于在定义变量的同时初始化结构体。

struct {
    char name[50]; // 学生姓名
    int age;       // 年龄
    float gpa;     // 平均成绩点数
} s2 = {"Bob", 22, 3.7};
结构体数组

可以创建结构体数组来存储多个结构体实例。

struct student students[3] = {
    {"Alice", 20, 3.5},
    {"Bob", 22, 3.7},
    {"Charlie", 21, 3.8}
};

for (int i = 0; i < 3; i++) {
    printf("Student %d: Name: %s, Age: %d, GPA: %.2f\n",
           i + 1, students[i].name, students[i].age, students[i].gpa);
}
结构体指针

可以定义指向结构体的指针,并通过指针来访问结构体成员。

struct student *ptr = &s1;
printf("Pointer to structure: Name: %s, Age: %d, GPA: %.2f\n",
       ptr->name, ptr->age, ptr->gpa);
结构体与函数

可以将结构体作为参数传递给函数,也可以返回结构体。

struct student get_student_info() {
    struct student s;
    strcpy(s.name, "David");
    s.age = 23;
    s.gpa = 3.6;
    return s;
}

int main() {
    struct student s3 = get_student_info();
    printf("Returned structure: Name: %s, Age: %d, GPA: %.2f\n",
           s3.name, s3.age, s3.gpa);
    return 0;
}

结构体的内存布局

字节对齐

为了提高性能,编译器会对结构体中的成员进行字节对齐。这意味着成员的地址通常是其类型大小的倍数。例如,int 类型在许多系统上是 4 字节,那么 int 成员的地址通常是 4 的倍数。

struct example {
    char c;      // 1 字节
    int i;       // 4 字节
    double d;    // 8 字节
};

在上面的例子中,char c 之后会有填充(padding),以便 int i 的地址是 4 的倍数。同样,int i 之后也会有填充,以便 double d 的地址是 8 的倍数。

结构体的大小

结构体的总大小等于所有成员的大小加上必要的填充。

struct example {
    char c;      // 1 字节
    int i;       // 4 字节
    double d;    // 8 字节
};

printf("Size of struct example: %zu\n", sizeof(struct example));
包含结构体的结构体

可以将一个结构体作为另一个结构体的成员。

struct person {
    char name[50]; // 姓名
    int age;       // 年龄
};

struct employee {
    struct person info; // 个人信息
    double salary;      // 工资
};

struct employee emp = {{"John Doe", 30}, 50000.0};

结构体与联合

联合

联合是一种特殊的数据类型,它允许多个成员共享同一段内存空间。这意味着联合的大小等于最大成员的大小。

union mixed {
    int i;        // 整数
    double d;     // 浮点数
    char str[20]; // 字符串
};

union mixed m;
m.i = 42;
m.d = 3.14;
strcpy(m.str, "Hello, union!");

printf("Integer: %d\n", m.i);
printf("Double: %.2f\n", m.d);
printf("String: %s\n", m.str);
结构体与联合的混合使用

可以在结构体中使用联合来节省内存。

struct complex {
    char name[50]; // 姓名
    union {
        int age;      // 年龄
        double salary;// 工资
    } u;
};

struct complex c = {{"Jane Smith"}, .u = {35}};
printf("Name: %s, Age: %d\n", c.name, c.u.age);

结构体与枚举

枚举类型

枚举是一种特殊的类型,用于定义一组命名的常量。

enum day {
    MONDAY,   // 星期一
    TUESDAY,  // 星期二
    WEDNESDAY, // 星期三
    THURSDAY, // 星期四
    FRIDAY,   // 星期五
    SATURDAY, // 星期六
    SUNDAY    // 星期天
};

struct week {
    enum day today; // 今天
    enum day tomorrow; // 明天
};

struct week w = {{MONDAY}, {TUESDAY}};
printf("Today: %d, Tomorrow: %d\n", w.today, w.tomorrow);

在这里插入图片描述

结构体与位字段

位字段

位字段允许在结构体中定义具有特定位数的成员。这对于存储标志或控制位特别有用。

struct flags {
    unsigned int read_only : 1; // 只读标志
    unsigned int hidden : 1;    // 隐藏标志
    unsigned int executable : 1; // 可执行标志
    unsigned int reserved : 1;  // 保留位
    unsigned int unused : 28;   // 未使用的位
};

struct flags f = {1, 1, 1, 0, 0};
printf("Flags: read_only=%d, hidden=%d, executable=%d\n",
       f.read_only, f.hidden, f.executable);

结构体与动态分配

动态分配结构体

可以使用动态内存分配函数(如 malloccalloc)来分配结构体的内存。

struct student *dynamic_student = (struct student *)malloc(sizeof(struct student));

strcpy(dynamic_student->name, "Eva");
dynamic_student->age = 24;
dynamic_student->gpa = 3.9;

printf("Dynamic Student: Name: %s, Age: %d, GPA: %.2f\n",
       dynamic_student->name, dynamic_student->age, dynamic_student->gpa);

free(dynamic_student);
动态分配结构体数组

还可以动态分配结构体数组的内存。

struct student *students = (struct student *)malloc(3 * sizeof(struct student));

for (int i = 0; i < 3; i++) {
    students[i].age = i + 20;
    students[i].gpa = 3.0 + i * 0.5;
}

for (int i = 0; i < 3; i++) {
    printf("Student %d: Age: %d, GPA: %.2f\n", i + 1, students[i].age, students[i].gpa);
}

free(students);

结构体与文件操作

结构体的序列化与反序列化

可以使用标准 I/O 函数(如 fwritefread)来保存和恢复结构体的内容。

struct student s4 = {"Michael", 25, 3.8};

FILE *fp = fopen("students.bin", "wb");
if (fp == NULL) {
    perror("Error opening file");
    exit(EXIT_FAILURE);
}

fwrite(&s4, sizeof(struct student), 1, fp);
fclose(fp);

// 读取结构体
FILE *fp2 = fopen("students.bin", "rb");
if (fp2 == NULL) {
    perror("Error opening file");
    exit(EXIT_FAILURE);
}

struct student s5;
fread(&s5, sizeof(struct student), 1, fp2);
fclose(fp2);

printf("Loaded Student: Name: %s, Age: %d, GPA: %.2f\n",
       s5.name, s5.age, s5.gpa);

结构体与内存布局详解

字节对齐的细节

字节对齐是为了提高数据访问效率而设计的。编译器通常会根据目标平台的对齐要求来自动插入填充字节。

struct example {
    char c;          // 1 字节
    short int s;     // 2 字节
    int i;           // 4 字节
    double d;        // 8 字节
};

printf("Size of struct example: %zu\n", sizeof(struct example));

在上述例子中,char c 后面可能会有一个填充字节,使 short int s 的地址成为 2 的倍数。接着,int i 之前可能会有两个填充字节,使其地址成为 4 的倍数。最后,double d 之前可能会有四个填充字节,使其地址成为 8 的倍数。因此,最终的结构体大小可能比成员总和大。

字节对齐的影响

字节对齐会影响结构体的内存使用效率。如果不需要严格的对齐要求,可以使用 #pragma pack 指令来控制对齐行为。

#pragma pack(push, 1)
struct packed_example {
    char c;          // 1 字节
    short int s;     // 2 字节
    int i;           // 4 字节
    double d;        // 8 字节
};
#pragma pack(pop)

printf("Size of struct packed_example: %zu\n", sizeof(struct packed_example));

在上面的例子中,使用了 #pragma pack 指令来禁用对齐填充,这样结构体的大小就等于成员的总和。

字节对齐的平台差异

不同平台上的对齐规则可能会有所不同。例如,在某些 32 位系统上,double 类型可能只需要 4 字节的对齐,而在 64 位系统上则需要 8 字节的对齐。编写跨平台代码时需要考虑这一点。


结构体与内存访问优化

数据访问模式

当结构体中的成员被频繁访问时,合理的布局可以减少缓存缺失,从而提高性能。

struct student {
    char name[50]; // 姓名
    int age;       // 年龄
    float gpa;     // 平均成绩点数
};

struct student students[100000]; // 存储大量学生信息

// 假设我们要查找所有年龄大于某个值的学生
int threshold = 25;
int count = 0;

for (int i = 0; i < 100000; i++) {
    if (students[i].age > threshold) {
        count++;
    }
}

printf("Number of students older than %d: %d\n", threshold, count);
缓存友好布局

在上述例子中,如果访问模式是连续的,那么可以通过将最常访问的成员放在前面来优化缓存使用。

struct student {
    int age;       // 年龄
    char name[50]; // 姓名
    float gpa;     // 平均成绩点数
};

struct student students[100000]; // 存储大量学生信息

int threshold = 25;
int count = 0;

for (int i = 0; i < 100000; i++) {
    if (students[i].age > threshold) {
        count++;
    }
}

printf("Number of students older than %d: %d\n", threshold, count);

结构体与内存对齐优化

对齐与性能

某些处理器架构对未对齐的内存访问有较高的性能开销。例如,如果在一个 64 位平台上访问一个未对齐的 64 位整数,处理器可能会需要两次内存访问才能读取完整数据。

struct misaligned {
    int i;         // 4 字节
    double d;      // 8 字节
};

struct aligned {
    double d;      // 8 字节
    int i;         // 4 字节
};

struct misaligned m;
struct aligned a;

// 假设我们要读取结构体中的双精度浮点数
double value_m = m.d;
double value_a = a.d;

// 在某些平台上,读取 `value_m` 可能会导致未对齐访问
对齐优化工具

现代编译器提供了多种工具和技术来帮助优化对齐。例如,GCC 提供了 __attribute__((aligned)) 属性来显式指定对齐要求。

struct aligned {
    double d __attribute__((aligned(8))); // 8 字节对齐
    int i;
};

struct aligned a;
double value_a = a.d;

在这里插入图片描述

结构体与内存管理

动态结构体的生命周期

动态分配的结构体需要手动管理其生命周期。当不再需要结构体时,应该释放其占用的内存。

struct student *dynamic_student = (struct student *)malloc(sizeof(struct student));

strcpy(dynamic_student->name, "Eva");
dynamic_student->age = 24;
dynamic_student->gpa = 3.9;

// 使用完后释放内存
free(dynamic_student);
结构体数组的生命周期

动态分配的结构体数组也需要手动管理内存。

struct student *students = (struct student *)malloc(3 * sizeof(struct student));

for (int i = 0; i < 3; i++) {
    students[i].age = i + 20;
    students[i].gpa = 3.0 + i * 0.5;
}

// 使用完后释放内存
free(students);
结构体与内存泄漏

如果不正确管理结构体的生命周期,可能会导致内存泄漏。务必确保释放所有动态分配的内存。

struct student *students = (struct student *)malloc(3 * sizeof(struct student));

// 忘记释放内存
// free(students);

结构体与复杂数据结构

结构体与链表

结构体可以用来实现链表这样的复杂数据结构。

struct node {
    int data; // 数据
    struct node *next; // 下一个节点
};

struct node *head = NULL;

void append(int data) {
    struct node *new_node = (struct node *)malloc(sizeof(struct node));
    new_node->data = data;
    new_node->next = NULL;

    if (head == NULL) {
        head = new_node;
    } else {
        struct node *current = head;
        while (current->next != NULL) {
            current = current->next;
        }
        current->next = new_node;
    }
}

void print_list() {
    struct node *current = head;
    while (current != NULL) {
        printf("%d ", current->data);
        current = current->next;
    }
    printf("\n");
}

int main() {
    append(1);
    append(2);
    append(3);
    print_list();
    return 0;
}
结构体与树

结构体也可以用来实现树形结构。

struct tree_node {
    int data; // 数据
    struct tree_node *left; // 左子树
    struct tree_node *right; // 右子树
};

struct tree_node *root = NULL;

struct tree_node *create_node(int data) {
    struct tree_node *new_node = (struct tree_node *)malloc(sizeof(struct tree_node));
    new_node->data = data;
    new_node->left = NULL;
    new_node->right = NULL;
    return new_node;
}

void insert(int data) {
    if (root == NULL) {
        root = create_node(data);
    } else {
        struct tree_node *current = root;
        struct tree_node *parent = NULL;

        while (current != NULL) {
            parent = current;
            if (data < current->data) {
                current = current->left;
            } else {
                current = current->right;
            }
        }

        if (data < parent->data) {
            parent->left = create_node(data);
        } else {
            parent->right = create_node(data);
        }
    }
}

void print_tree_inorder(struct tree_node *node) {
    if (node != NULL) {
        print_tree_inorder(node->left);
        printf("%d ", node->data);
        print_tree_inorder(node->right);
    }
}

int main() {
    insert(5);
    insert(3);
    insert(7);
    insert(1);
    insert(4);
    insert(6);
    insert(8);

    print_tree_inorder(root);
    return 0;
}

总结

本文详细介绍了 C 语言中的结构体,包括基本用法、高级用法、内存布局、联合、枚举、位字段以及动态分配等内容。通过学习这些知识,您可以更加灵活地使用结构体来构建复杂的数据结构。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2137386.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++中的二叉搜索树

一概念&#xff1a; 静图展示&#xff1a; 动图展示&#xff1a; ①左子树不为空&#xff0c;则左子树节点值小于根节点值。 ②右子树不为空&#xff0c;则右子树节点值大于根节点值。 ③左右子树均为二叉搜索树。 ④对于它可以插入相等的也可以插入不相等的,这里如果插入的…

MATLAB系列02:MATLAB基础

MATLAB系列02&#xff1a;MATLAB基础 2. MATLAB基础2.1 变量和数组2.2 MATLAB变量的初始化2.2.1 用赋值语句初始化变量2.2.2 用捷径表达式赋值2.2.3 使用内置函数来初始化2.2.4 使用关键字input来初始化 2.3 多维数组2.3.1 创建多维数组2.3.2 多维数组在内存中的存储2.3.3 用单…

深入理解FastAPI中的root_path:提升API部署灵活性的关键配置

在Web开发领域&#xff0c;FastAPI因其高性能、易于使用和类型提示功能而备受开发者喜爱。然而&#xff0c;当涉及到在生产环境中部署FastAPI应用程序时&#xff0c;我们常常需要面对一些挑战&#xff0c;比如如何正确处理代理服务器添加的路径前缀。这时&#xff0c;root_path…

关于java同步调用多个接口并返回数据

在现代软件开发中&#xff0c;应用程序经常需要与多个远程API接口进行交互以获取数据。Java作为一种流行的编程语言&#xff0c;提供了多种方式来实现这一需求。本文将探讨如何在Java中同步调用多个API接口&#xff0c;并有效地处理和返回数据。 同步调用的必要性 在某些场景下…

vue table id一样的列合并

合并场景&#xff1a;如果id一样&#xff0c;则主表列合并&#xff0c;子表列不做合并&#xff0c;可实现单行、多行合并&#xff0c;亲测&#xff01;&#xff01;&#xff01; 展示效果如图示&#xff1a; 组件代码&#xff1a; // table组件 :span-method"objectSpa…

网络安全 DVWA通关指南 DVWA SQL Injection (Blind SQL盲注)

DVWA SQL Injection (Blind) 文章目录 DVWA SQL Injection (Blind)Low布尔盲注时间盲注sqlmap MediumHighImpossible 参考文献 WEB 安全靶场通关指南 Low 0、分析网页源代码 <?phpif( isset( $_GET[ Submit ] ) ) {// Get input$id $_GET[ id ];// Check database$geti…

基于spring boot的车辆故障综合服务平台设计与实现----附源码 73314

摘 要 近年来&#xff0c;随着社会科技的不断发展&#xff0c;人们的生活方方面面进入了信息化时代。计算机的普及&#xff0c;使得我们的生活更加丰富多彩。本论文基于Spring Boot框架&#xff0c;设计并实现了一个车辆故障综合服务平台&#xff0c;旨在提供便捷、高效的汽车…

c++类模板为什么不能编译到动态库中来使用

在使用c的时候&#xff0c;我们习惯于将类的定义声明在头文件中&#xff0c;即.h文件&#xff1b;将类函数的实现定义在源文件中&#xff0c;即.cpp文件。如果我们要提供的是一个动态库&#xff0c;那么这种方式更常用&#xff0c;使用动态库的时候&#xff0c;包含头文件&…

如何注册Liberty大学并获取Perplexity Pro

俗称白嫖 Perplexity Pro 会员 如何注册Liberty大学并获取Perplexity Pro 1. 访问官网 首先&#xff0c;进入Liberty大学官网 https://www.liberty.edu&#xff0c;点击“Apply”按钮。 2. 选择课程 选择“Online”课程&#xff0c;选择“Certificate”&#xff0c;然后随便…

深入理解Docke工作原理:UnionFS文件系统详解

在容器技术的世界中&#xff0c;文件系统的设计和实现是其关键组成部分&#xff0c;影响着镜像的构建效率、容器的启动速度以及资源的利用率。**UnionFS&#xff08;联合文件系统&#xff09;**作为Docker的核心文件系统技术&#xff0c;通过其独特的分层结构和写时复制&#x…

5 - ZYNQ SDK学习记录(2)

文章目录 1 Vivado工程基本设计2 Vivado工程位置不变2.1 修改设计1 - 增加PS侧QSPI外设2.2 修改设计2 - 增加PL侧AXI GPIO外设2.3 总结 3 Vivado工程位置变动3.1 先修改BD后打开SDK3.2 先打开SDK后修改BD3.3 总结 1 Vivado工程基本设计 Step 1&#xff1a; Vivado版本Vivado …

【观影聊数学】聊聊电影《孤注一掷》中的数学逻辑

反电诈题材影片《孤注一掷》取材于真实案例&#xff0c;揭秘了境外电信网络诈骗黑色产业链的骇人内幕。境外诈骗集团往往以高薪招聘为诱饵&#xff0c;吸引有发财梦的人去境外淘金&#xff0c;一旦人们走出国门&#xff0c;跳入犯罪分子设下的陷阱里&#xff0c;等待他们的将是…

【python爬虫】之scrapy框架介绍

一.什么是Scrapy&#xff1f; Scrapy是一个为了爬取网站数据&#xff0c;提取结构性数据而编写的应用框架&#xff0c;非常出名&#xff0c;非常强悍。所谓的框架就是一个已经被集成了各种功能&#xff08;高性能异步下载&#xff0c;队列&#xff0c;分布式&#xff0c;解析&a…

SpringBoot开发——使用@Slf4j注解实现日志输出

文章目录 1、Lombok简介2、SLF4J简介3、实现步骤3.1 创建SpringBoot项目3.2 添加依赖3.3 使用 Slf4j 注解3.4 输出日志信息 4、结论 在现代Java开发中&#xff0c;日志记录是至关重要的。它不仅帮助开发者调试代码&#xff0c;还便于监控系统运行状态和性能。 Lombok 和 SLF4J …

了解水凝胶纤维制造?自润滑纺丝来帮忙!高韧性纤维用途广!

大家好&#xff0c;今天我们来了解一篇水凝胶纤维文章——《Continuous Spinning of High‐Tough Hydrogel Fibers for Flexible Electronics by Using Regional Heterogeneous Polymerization》发表于《Advanced Science》。在柔性电子领域&#xff0c;水凝胶纤维因其独特的性…

检查一个复数C的实部a和虚部b是否都是有限数值即a和b都不是无限数值、空值cmath.isfinite(x)

【小白从小学Python、C、Java】 【考研初试复试毕业设计】 【Python基础AI数据分析】 检查一个复数C的实部a和虚部b 是否都是有限数值 即a和b都不是无限数值、空值 cmath.isfinite(x) [太阳]选择题 根据给定的Python代码&#xff0c;哪个选项是错误的&#xff1f; import cma…

适合小客厅使用的投影仪推荐:2024年当贝X5S小户型客厅的最佳选择

我们在买投影前都会先看看家里的环境、预算以及自己的需求去选择适合自己的家的那款&#xff1b;正好最近有朋友向我资讯&#xff1a;我家客厅面积不大&#xff0c;有没有适合的家用投影仪推荐啊&#xff1f;对于这种家庭使用环境不大的小客厅我们该如何挑选投影仪&#xff1f;…

人员抽烟AI检测算法在智慧安防领域的创新应用,助力监控智能化

随着人工智能技术的飞速发展&#xff0c;计算机视觉和深度学习算法在各个领域的应用日益广泛。其中&#xff0c;人员抽烟AI检测算法以其高效、精准的特点&#xff0c;成为公共场所、工厂、学校等场景中的得力助手。本文将介绍TSINGSEE青犀AI智能分析网关V4人员抽烟检测算法的基…

JavaScript高级——执行上下文栈

1、在全局代码执行前&#xff0c;JS引擎就会创建一个栈来存储管理所有的执行上下文对象 2、在全局执行上下文&#xff08;window&#xff09;确定后&#xff0c;将其添加到栈中&#xff08;压栈&#xff09; 3、在函数执行上下文创建后&#xff0c;将其添加到栈中&#xff08…

AI 驱动腾讯游戏智能 NPC,开启新纪元

AI 驱动腾讯游戏智能 NPC&#xff0c;开启新纪元 前言AI 驱动腾讯智能 NPC 前言 曾经&#xff0c;游戏 NPC 往往只是按照预设脚本进行简单互动&#xff0c;缺乏深度和灵活性。然而&#xff0c;如今在 AI 的赋能下&#xff0c;NPC 开始展现出前所未有的智能与活力。它们能够进行…