C/C++中的结构体和联合体

c语言中为我们提供了几种基本数据类型,但是在实际编程中,有时候要表达复杂数据关系时,单单使用基本数据类型不能很好的表示,为了解决这种问题,可以自己构建一个结构体数据类型,定义的结构体数据类型与基本数据类型使用方式一致,这样就可以很好的满足对数据类型扩充的需求。

要表示一个学生信息,一般包括姓名、学号、年龄等。如果不使用结构体,要表示这些信息我们要定义下面这些字段:

char name[8] = "kobe";
int number = 24;
int age = 9;

这种方式虽然可以表示学生的信息,但是考虑到使用数据的地方,这种方式显然并不友好。比如现在有一个方法要打印学生信息,如果用这种定义方式,在方法定义时需要定义三个参数:

/**
 * 打印学生信息
 */
void print_student(char name[], int number, int age) {
    printf("Student : %s, %d, %d\n", name, number, age);
}

但是使用结构体,上面的定义和使用方式会变成下面这种形式:

#include <stdio.h>
#include <string.h>

/**
 * 学生结构体
 */
typedef struct student {
    unsigned short age;     // 年龄
    int number;             // 学号
    char name[20];          // 姓名
} student;

/**
 * 打印学生信息
 */
void print_student(struct student stu) {
    printf("Student : %s, %d, %d\n", stu.name, stu.number, stu.age);
}

int main() {
    struct student stu;
    strcpy(stu.name, "rose");
    stu.number = 1;
    stu.age = 12;

    print_student(stu);

    return 0;
}

通过使用结构体,表达的含义会变得简单并且更有逻辑性,而且在c语言中,结构体完全可以按照基本数据类型那样定义和使用。数组、指针这些使用场景完全适用。

1、结构体定义和初始化

定义结构体通过struct关键字,定义的格式为:

struct 结构体名 {
    字段信息1;
    字段信息2;
    ...
    字段信息n;
};

比如定义一个学生信息的结构体:

struct student {
    unsigned short age;     // 年龄
    int number;             // 学号
    char name[20];          // 姓名
};

在使用结构体时,同时需要带上 struct 结构体名 这两部分,为了简化使用,可以为结构体定义别名,定义格式调整为:

typedef struct 结构体名 {
    字段信息1;
    字段信息2;
    ...
    字段信息n;
} 结构体别名;

如:

typedef struct student {
    unsigned short age;     // 年龄
    int number;             // 学号
    char name[20];          // 姓名
} student;

在使用结构体类型时,就可以省略 struct 关键字,只通过结构体别名定义数据类型,就像使用基本数据类型一样:

// 不省略 struct 关键字,通过结构体名定义变量
struct student stu;
// 省略 struct 关键字,通过结构体别名定义变量
student stu;

结构体的初始化有几种方式:
1、先定义结构体变量,然后对结构体里面的字段逐个进行初始化赋值:

// 分字段初始化
struct student stu;
strcpy(stu.name, "Rose");
stu.number = 1;
stu.age = 12;
print_student(stu);

2、定义结构体变量时按照结构体中定义字段的顺序进行初始化:

// 顺序初始化
struct student stu = {11, 2, "Jordan" };
print_student(stu);

3、定义结构体变量时指定属性名进行初始化,属性名称不一定是按照在结构体中定义的顺序赋值:

// 指定属性名乱序初始化
struct student stu = { .name = "Kobe", .number = 2, .age = 12 };
print_student(stu);

2、结构体和数组、指针

定义的结构体跟c语言中提供的基本数据类型使用方式一致,它同样支持数组和指针这种使用方式:

// 结构体数组
student arr[] = {
        {13, 5, "Johnson" },
        {14, 6, "Bird" }
};
int len = sizeof(arr) / sizeof(student);
for(int i = 0; i < len; i++) {
    print_student(arr[i]);
}

指针也一样,只是取数据方式支持两种(.和->):

// 结构体指针
student* p = malloc(sizeof(student));
strcpy(p->name, "ONeal");
p->number = 7;
p->age = 12;
print_student(*p);

// 单独访问某个属性
printf("visit field : %d, %d\n", p->age, (*p).age);

完整示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
 * 学生结构体
 */
typedef struct student {
    unsigned short age;     // 年龄
    int number;             // 学号
    char name[20];          // 姓名
} student;

/**
 * 打印学生信息
 */
void print_student(struct student stu) {
    printf("Student : %s, %d, %d\n", stu.name, stu.number, stu.age);
}

int main() {
    // 分字段初始化
    struct student stu;
    strcpy(stu.name, "Rose");
    stu.number = 1;
    stu.age = 12;
    print_student(stu);

    // 顺序初始化
    struct student stu1 = {11, 2, "Jordan" };
    print_student(stu1);

    // 指定属性名乱序初始化
    struct student stu2 = { .name = "Kobe", .number = 3, .age = 12 };
    print_student(stu2);

    student stu3 = {14, 4, "James" };
    print_student(stu3);

    // 结构体数组
    student arr[] = {
            {13, 5, "Johnson" },
            {14, 6, "Bird" }
    };
    int len = sizeof(arr) / sizeof(student);
    for(int i = 0; i < len; i++) {
        print_student(arr[i]);
    }

    // 结构体指针
    student* p = malloc(sizeof(student));
    strcpy(p->name, "ONeal");
    p->number = 7;
    p->age = 12;
    print_student(*p);

    // 单独访问某个属性
    printf("visit field : %d, %d\n", p->age, (*p).age);

    free(p);

    return 0;
}

3、结构体内存对齐

在c语言中,所有数据类型都遵循内存对齐的原则,也就是:不同类型的数据只能存储在相对于起始地址偏移为该类型整数倍的内存地址上。表述起来很拗口,简单来说,就是存储数据的地址能被该类型所占内存大小整除。

比如下面这几个数据类型的内存地址输出如下:

#include <stdio.h>

int main() {
    // int占4字节
    int a = 10;
    int b = 20;
    printf("%p\n", &a);
    printf("%p\n", &b);

    // long long占8字节
    long long fa = 10L, fb = 20L;
    printf("%p\n", &fa);
    printf("%p\n", &fb);

    return 0;
}

其中的某一次输出:

00000072b55ff81c
00000072b55ff818
00000072b55ff810
00000072b55ff808

大家可以通过计算器做一下运算,这些内存地址肯定能整除变量的占用空间大小。
c语言内存对齐

结构体的内存对齐原则如下:

1、结构体第一个成员对齐到结构体变量起始位置偏移量为0的地址处
2、其他成员变量需要对齐到变量所占内存大小的整数倍位置
3、结构体总大小应为所有变量类型中最大数据类型的整数倍
4、在结构体的对应位置填充字节让整个结构体满足上面的规则

下面通过代码验证上面的规则:

#include <stdio.h>

struct memory_demo {
    char c1;
    char c2;
    int i;
    double d;
    long long l;
};

int main() {
    struct memory_demo md;

    printf("-------- struct size --------\n");
    printf("%llu\n", sizeof(struct memory_demo));
    printf("%llu\n", sizeof(md));

    printf("-------- struct address --------\n");
    printf("%p\n", &md);
    printf("%p\n", &(md.c1));
    printf("%p\n", &(md.c2));
    printf("%p\n", &(md.i));
    printf("%p\n", &(md.d));
    printf("%p\n", &(md.l));

    return 0;
}

结构体内存地址规则1

-------- struct size --------
24
24
-------- struct address --------
000000774f9ff800
000000774f9ff800
000000774f9ff801
000000774f9ff804
000000774f9ff808
000000774f9ff810

分析一下上面的内存使用:前两个属性是char类型,占用1个字节内存;第三个属性是int类型占用4个字节内存,所以它相对起始位置偏移4个字节;第四个属性是double类型占用8个字节内存,所以它应该相对起始位置偏移8的整数倍字节;第五个属性是long long类型也是占用8字节,所以它应该在起始位置偏移16个字节的位置。
调整结构体内字段的顺序,在观察一下内存占用:

#include <stdio.h>

struct memory_demo {
    char c1;
    char c2;
    double d;
    int i;
    long long l;
};

int main() {
    struct memory_demo md;

    printf("-------- struct size --------\n");
    printf("%llu\n", sizeof(struct memory_demo));
    printf("%llu\n", sizeof(md));

    printf("-------- struct address --------\n");
    printf("%p\n", &md);
    printf("%p\n", &(md.c1));
    printf("%p\n", &(md.c2));
    printf("%p\n", &(md.d));
    printf("%p\n", &(md.i));
    printf("%p\n", &(md.l));

    return 0;
}

结构体内存规则2

-------- struct size --------
32
32
-------- struct address --------
000000e8349ffd40
000000e8349ffd40
000000e8349ffd41
000000e8349ffd48
000000e8349ffd50
000000e8349ffd58

分析一下上面的内存使用:前两个属性是char类型,占用1个字节内存;第三个属性是double类型占用8个字节内存,所以它相对起始位置偏移8个字节;第四个属性是int类型占用4个字节内存,所以它应该相对起始位置偏移4的整数倍字节,第三个属性在起始位置偏移8字节,同时占用8字节,所以第四个属性相对起始位置偏移16个字节;第五个属性是long long类型也是占用8字节,所以它应该在起始位置偏移24个字节的位置。
再次调整结构体属性字段顺序:

#include <stdio.h>

struct memory_demo {
    double d;
    long long l;
    int i;
    char c1;
    char c2;
};

int main() {
    struct memory_demo md;

    printf("-------- struct size --------\n");
    printf("%llu\n", sizeof(struct memory_demo));
    printf("%llu\n", sizeof(md));

    printf("-------- struct address --------\n");
    printf("%p\n", &md);
    printf("%p\n", &(md.d));
    printf("%p\n", &(md.l));
    printf("%p\n", &(md.i));
    printf("%p\n", &(md.c1));
    printf("%p\n", &(md.c2));

    return 0;
}

结构体内存规则3

-------- struct size --------
24
24
-------- struct address --------
000000477f1ff660
000000477f1ff660
000000477f1ff668
000000477f1ff670
000000477f1ff674
000000477f1ff675

分析一下上面的内存使用:第一个属性是double类型,占用8个字节内存;第二个属性是long long类型占用8个字节内存,所以它相对起始位置偏移8个字节;第三个属性是int类型占用4个字节内存,所以它应该相对起始位置偏移16个字节;最后两个属性是char类型占用1字节,所以它们应该在起始位置偏移20、21个字节的位置。由于整个结构体最大数据类型占用8字节,在结构体最后补2个字节,确保整个结构体是8的整数倍。
通过上面的几个示例分析,大家对结构体的内存结构会有很好的理解,在以后设计结构体内属性顺序时,尽量编排占用内存小的属性靠前,这样会让整个结构体占用内存更小。

4、联合体

联合体与结构体比较类似,只不过在联合体中多个属性字段共用一段内存空间,联合体占用内存大小为最大数据类型的大小。如果同时给多个属性字段赋值,那么后面的值会覆盖前面的值,所以,在联合体中只能使用其中的一个数据类型。
联合体占用内存大小:

#include <stdio.h>

union memory_demo {
    char ch;
    int i;
    double d;
};

int main() {
    printf("%d\n", sizeof(union memory_demo));
 	
    return 0;
}

联合体占用内存
联合体内所有属性在内存中占用的地址相同:

#include <stdio.h>

union memory_demo {
    char c;
    int i;
    double d;
};

int main() {
    union memory_demo md;

    printf("-------- union size --------\n");
    printf("%llu\n", sizeof(union memory_demo));
    printf("%llu\n", sizeof(md));

    printf("-------- union address --------\n");
    printf("%p\n", &md);
    printf("%p\n", &(md.c));
    printf("%p\n", &(md.i));
    printf("%p\n", &(md.d));

    return 0;
}

联合体内存地址
由于所有属性的内存地址相同,同时给多个属性赋值时,会导致数据错乱:

#include <stdio.h>

union memory_demo {
    char c;
    int i;
    double d;
};

int main() {
    printf("-------- union value --------\n");
    union memory_demo md;
    md.c = 'A';
    md.i = 22;
    md.d = 33.3;

    printf("%c\n", md.c);
    printf("%d\n", md.i);
    printf("%lf\n", md.d);

    return 0;
}

联合体数据错乱

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MIT 6.S081 Lab1: Xv6 and Unix utilities翻译

Lab1: Xv6 and Unix utilities 文章目录 Lab1: Xv6 and Unix utilities实验任务启动xv6(难度&#xff1a;Easy)sleep(难度&#xff1a;Easy)pingpong&#xff08;难度&#xff1a;Easy&#xff09;Primes(素数&#xff0c;难度&#xff1a;Moderate/Hard)find&#xff08;难度&…

MySQL 如何用C语言连接

✨✨✨励志成为超级技术宅 ✨✨✨ 本文主要讲解在Linux服务器上&#xff0c;如何使用c语言MySQL库的接口来对MySQL数据库进行操作&#xff0c;如果没有服务器安装MySQL&#xff0c;也可以先学学看怎么用c语言mysql库的接口&#xff0c;还是比较容易的了。(●☌◡☌●)。那么开…

雨晨 24H2 Windows 11 IoT ltsc 2024 IE 极简版 26100.2222

文件: 雨晨 24H2 Windows 11 IoT ltsc 2024 IE 极简版 26100.2222 install.wim 大小: 1737837354 字节 修改时间: 2024年11月12日, 星期二, 12:41:56 MD5: 3511B5416EA18DD4AD2D75F133C49E25 SHA1: 817E4DF1F58AA5A584E5D9263F282C1D20533969 CRC32: EB1C1B7B 简述&#xff1a…

丹摩征文活动|FLUX.1+ComfyUI的详细部署以及实验总结

公主请阅 1. FLUX.1的简介2. 部署过程创建资源ComfyUI的部署操作部署FLUX.1 如何使用&#xff1f;实验总结&#xff1a;环境搭建与工具安装实验步骤实验结果分析总结 1. FLUX.1的简介 FLUX.1 是由黑森林实验室开发的图像生成工具&#xff0c;分为三个版本&#xff1a; FLUX-1-…

【电力系统】永磁同步电机调速系统带有扰动观测器

【电力系统】永磁同步电机调速系统带有扰动观测器( DOB)的最优滑模控制、改进补偿滑模控制、传统滑模、PID控制研究 摘要 本文研究了永磁同步电机&#xff08;PMSM&#xff09;调速系统中的不同控制策略&#xff0c;包括最优滑模控制、改进补偿滑模控制、传统滑模控制以及PID控…

手动实现h5移动端点击全屏按钮横屏展示图片,左右滑动切换,处理页面会随着手指滑动问题

页面提供全屏按钮,全屏展示的容器 <div class"container"><button click"openSwiper">点击全屏查看</button><!-- 大图 --><divclass"full"v-if"showSwiper"touchstart"handleTouchStart"touch…

RHEL 网络配置(Linux网络服务器 09)

0 引入 对于Linux系统的网络管理员来说&#xff0c;掌握Linux服务器的网络配置是至关重要的&#xff0c;同时管理远程主机也是网络管理员必须掌握的。这些是后续网络服务配置的基础。 本文&#xff0c;我们讲解如何使用nmtui命令配置网络参数&#xff0c;以及通过nmtui命令查…

【Qt】Macbook M1下载安装

文章目录 一、下载Xcode命令行工具二、在Cion中配置编译器三、安装Qt四、配置qmake环境五、创建Qt项目 博主已经下载了Clion&#xff0c;所以本文是将qt配置到Clion上 本博客所写的教程有一定的问题&#xff0c;因为我在官网下载后发现有一些所需的包是没有的&#xff0c;不知道…

UE5 样条线组件(未完待续)

按点生成模型 按距离生成 spline mesh 可缩放spline mesh

Linux(CentOS)项目总结(前后端分离)

项目情况&#xff1a; 前端开发&#xff1a;vue3 vite ts VSCode后端开发&#xff1a;JDK17 Spring Boot 3 Mybatis Maven IDEA数据库&#xff1a;MySQL8.4.3 SQLyog代码管理&#xff1a;Git虚拟环境&#xff1a;VMware远程登录&#xff1a;FinalShell服务器操作系统&…

【C++】 C++游戏设计---五子棋小游戏

1. 游戏介绍 一个简单的 C 五子棋小游戏 1.1 游戏规则&#xff1a; 双人轮流输入下入点坐标横竖撇捺先成五子连线者胜同一坐标点不允许重复输入 1.2 初始化与游戏界面 初始化界面 X 输入坐标后 O 输入坐标后 X 先达到胜出条件 2. 源代码 #include <iostream> #i…

A算法详解(go实现)

A*算法详解&#xff08;go实现&#xff09; 推荐一位大佬的文章&#xff0c;建议可以去看看https://hogwartsrico.github.io/2016/03/11/AStarPathFinding/index.html 下面贴出来文章中用于举例的网站&#xff1a; https://anvaka.github.io/ngraph.path.demo/#?graphamste…

丹摩征文活动 | 0基础带你上手经典目标检测模型 Faster-Rcnn

文章目录 &#x1f34b;1 引言&#x1f34b;2 平台优势&#x1f34b;3 丹摩平台服务器配置教程&#x1f34b;4 实操案例&#xff08; Faster-rcnn 项目&#xff09;&#x1f34b;4.1 文件处理&#x1f34b;4.2 环境配置&#x1f34b;4.3 训练模型&#x1f34b;4.4 数据保存并导…

Java poi 模板导出Word 带图片

Java poi 模板导出Word 带图片 重点&#xff01;&#xff01;&#xff01; 官方文档&#xff1a;https://deepoove.com/poi-tl/#_maven 最终效果 模板 其实内容都在官方文档里写的非常明白了 我这里只是抛砖引玉。 Maven依赖 <poi.version>4.1.2</poi.version>…

LabVIEW车辆侧翻预警系统

在工业和实验室环境中&#xff0c;搬运车辆、叉车和特种作业车辆经常在负载和高速转弯过程中发生侧翻事故&#xff0c;导致设备损坏和人员伤害。为提高工作环境的安全性&#xff0c;开发了一种基于LabVIEW的工业车辆侧翻预警系统&#xff0c;能够实时监测车辆状态并发出预警&am…

Unity3D UI 双击和长按

Unity3D 实现 UI 元素双击和长按功能。 UI 双击和长按 上一篇文章实现了拖拽接口&#xff0c;这篇文章来实现 UI 的双击和长按。 双击 创建脚本 UIDoubleClick.cs&#xff0c;创建一个 Image&#xff0c;并把脚本挂载到它身上。 在脚本中&#xff0c;继承 IPointerClickHa…

计算机网络——SDN

分布式控制路由 集中式控制路由

深入浅出rust内存对齐

在 Rust 中&#xff0c;内存对齐是一个重要的概念&#xff0c;它涉及到数据在内存中的存储方式&#xff0c;以及如何优化内存访问的效率。往往一门语言的内存布局以及对齐方式决定了一门语言的性能&#xff0c;因此学会并深入理解rust中内存布局会让我们写出高性能的rust代码&a…

ARM64环境使用docker-compose进行ElasticSearch8集群部署

环境规划 主机IP系统ES版本CPU架构用户名密码192.168.174.18Ubuntu 22.04.4 LTSelasticsearch:8.10.4ARM64elasticllodyi4TMmZD192.168.174.218Ubuntu 22.04.4 LTSelasticsearch:8.10.4ARM64192.168.174.112Ubuntu 22.04.4 LTSelasticsearch:8.10.4ARM64 概念&#xff1a; no…

28.医院管理系统(基于springboot和vue)

目录 1.系统的受众说明 2. 相关技术和开发环境 2.1 相关技术 2.1.1 Java语言 2.1.2 HTML、CSS、JavaScript 2.1.3 Redis 2.1.4 MySQL 2.1.5 SSM框架 2.1.6 Vue.js 2.1.7 SpringBoot 2.2 开发环境 3. 系统分析 3.1 可行性分析 3.1.1 经济可行性 3.1.2 技术…