【C语言】自定义类型讲解

文章目录

  • 一、前言
  • 二、结构体
    • 2.1 概念
    • 2.2 定义
      • 2.2.1 通常情况下的定义
      • 2.2.2 匿名结构体
    • 2.3 结构体的自引用和嵌套
    • 2.4 结构体变量的定义与初始化
    • 2.5 结构体的内存对齐
    • 2.6 结构体传参
    • 2.7 结构体实现位段
  • 三、枚举
    • 3.1 概念
    • 3.2 定义
    • 3.3 枚举的优点
      • 3.3.1 提高代码的可读性
      • 3.3.2 防止非法值
      • 3.3.3 方便维护
    • 3.4 枚举的使用
      • 3.4.1 基础使用
      • 3.4.2 搭配switch使用
      • 3.4.3 使用typedef简化枚举类型
  • 四、联合体
    • 4.1 概念
    • 4.2 定义
    • 4.3 联合体的特点
    • 4.4 联合体的使用
      • 4.4.1 基础使用
      • 4.4.2 联合体的内存大小
    • 4.5 应用场景

一、前言

本文主要是讲解C语言中的自定义类型,包括:结构体、枚举、联合体。

二、结构体

2.1 概念

在C语言中,结构体是一种强大的数据组织工具,它允许我们将不同类型的数据组合在一起,形成一个逻辑上的整体。通过合理使用结构体,可以提高代码的可读性、可维护性和复用性。

2.2 定义

2.2.1 通常情况下的定义

struct Book
{char name[20];int price;char id[12];
}b1,b2;

2.2.2 匿名结构体

匿名结构体通常用于一些临时性的、不需要多次使用的数据结构。由于没有类型名称,匿名结构体变量只能在声明它的作用域内使用。

struct
{char name[20];int price;char id[12];
}s;

2.3 结构体的自引用和嵌套

结构体可以包含自身类型的成员,这种特性称为自引用。例如,我们可以定义一个链表节点结构体,其中包含一个指向相同结构体类型的指针:

struct Node
{int data;struct Node* next;
};

2.4 结构体变量的定义与初始化

struct Book b1 = {"C Programming", 50, "123456789012"};

这里,b1是一个Book类型的结构体变量,并且在定义时初始化了它的所有成员。如果只初始化部分成员,未初始化的成员将自动初始化为零(对于数值类型)或空字符串(对于字符数组)。

2.5 结构体的内存对齐

在C语言中,结构体的内存布局受到内存对齐规则的影响。内存对齐的目的是提高数据访问的效率。以下是结构体内存对齐的一些基本规则:

  1. 第一个成员的对齐:结构体的第一个成员放在结构体变量在内存中存储位置的0偏移处开始。
  2. 后续成员的对齐:从第二个成员往后的所有成员,都放在一个对齐数(成员的大小和默认对齐数的较小值)的整数倍的地址处。
  3. 结构体总大小的对齐:结构体的总大小是结构体的所有成员的对齐数中最大的那个对齐数的整数倍。
  4. 嵌套结构体的对齐:如果结构体中嵌套了其他结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

第二点规则的例子:

struct Example
{char a;      // 1字节short b;     // 2字节int c;       // 4字节char d;      // 1字节
};

此时,内存应该如下图对齐:

把成员调换一下

struct Example
{char a;      // 1字节int c;       // 4字节short b;     // 2字节char d;      // 1字节
};

如下图:

2.6 结构体传参

在函数调用中,结构体变量可以作为参数传递。由于结构体可能包含多个成员,直接传递结构体变量可能会导致较大的内存拷贝开销。因此,建议在传递结构体时使用指针。例如:

void printBook(struct Book* book)
{printf("Name: %s\n", book->name);printf("Price: %d\n", book->price);printf("ID: %s\n", book->id);
}struct Book b1 = {"C Programming", 50, "123456789012"};
printBook(&b1);

2.7 结构体实现位段

位段是一种特殊的结构体成员,它允许我们指定成员占用的位数。位段通常用于硬件编程或需要精确控制内存布局的场景。例如:

struct S
{char a : 3; // a占3个bitchar b : 4;char c : 5;char d : 4;
};struct S s;
s.a = 7; // 二进制为0111
s.b = 15; // 二进制为1111

在这个例子中,a、b、c和d是位段成员,分别占用3、4、5和4个位。位段的空间是按照需要以4个字节(int)或者一个字节(char)的方式来开辟的。

三、枚举

3.1 概念

在C语言中,枚举(enum)是一种用户自定义的整数类型,它允许为整数值赋予有意义的名称。枚举类型通常用于表示一组相关的常量,使代码更具可读性和易维护性。

3.2 定义

默认是从0开始。

enum 枚举名 {枚举值1,枚举值2,枚举值3,...
};

例如,以下代码定义了一个表示一周七天的枚举类型Day,也可以自定义mon的值,这样它会根据最上面的值往下递增:

enum Day{Mon = 1,Tues,Wed,Thur,Fri,Sat,Sun
};

3.3 枚举的优点

3.3.1 提高代码的可读性

枚举类型通过为整数值赋予有意义的名称,使代码更易于理解和维护。例如,使用枚举类型Day时,代码可以写成:

enum Day today = Fri;

3.3.2 防止非法值

枚举类型限制了变量可以取的值,从而防止非法值的赋值。例如,today变量只能取Mon到Sun中的某个值,而不能是其他任意整数。这有助于减少潜在的错误。

3.3.3 方便维护

当需要添加新的枚举值时,只需在枚举定义中添加新的名称,而无需修改其他代码。例如,如果需要添加一个表示“节假日”的枚举值,只需在Day中添加:

enum Day{Mon = 1,Tues,Wed,Thur,Fri,Sat,Sun,Holiday
};

3.4 枚举的使用

3.4.1 基础使用

enum Day{Mon = 1,Tues,Wed,Thur,Fri,Sat,Sun
};int main()
{enum Day today = Mon;printf("today is %d\n", today);return 0;
}

3.4.2 搭配switch使用

enum Day{Mon = 1,Tues,Wed,Thur,Fri,Sat,Sun
};int main()
{/*模拟键盘输入 */enum Day today = Mon;switch (today) {case Mon:printf("Today is Monday\n");break;case Tues:printf("Today is Tuesday\n");break;case Wed:printf("Today is Wednesday\n");break;case Thur:printf("Today is Thursday\n");break;case Fri:printf("Today is Friday\n");break;case Sat:printf("Today is Saturday\n");break;case Sun:printf("Today is Sunday\n");break;default:printf("Unknown day\n");}return 0;
}

3.4.3 使用typedef简化枚举类型

typedef enum Day{Mon = 1,Tues,Wed,Thur,Fri,Sat,Sun
}Day_t;int main()
{/*模拟键盘输入 */Day_t today = Sat;switch (today) {case Mon:printf("Today is Monday\n");break;case Tues:printf("Today is Tuesday\n");break;case Wed:printf("Today is Wednesday\n");break;case Thur:printf("Today is Thursday\n");break;case Fri:printf("Today is Friday\n");break;case Sat:printf("Today is Saturday\n");break;case Sun:printf("Today is Sunday\n");break;default:printf("Unknown day\n");}return 0;
}

四、联合体

4.1 概念

在C语言中,联合体也叫共用体,联合体(Union)是一种特殊的数据结构,它允许在同一内存位置上存储不同类型的变量。换句话说,联合体中的所有成员都共享同一块内存空间,因此,联合体的大小是它最大成员所需的内存空间。联合体是一种有效的内存使用方式,尤其是在我们需要在不同时间点使用不同类型的数据时。

4.2 定义

在C语言中,联合体的定义使用关键字 union。与结构体不同,结构体中的每个成员都有自己的内存空间,而联合体中的所有成员共用同一块内存。其基本定义格式如下:

union Un {char c;int i;
};

在上述代码中,Un 是联合体的名字,c 和 i 是该联合体的成员。虽然它有两个成员,一个是字符型 char,另一个是整型 int,但是这两个成员是共享内存的。也就是说,它们存储的数据会覆盖彼此。

4.3 联合体的特点

  1. 共用内存:联合体的成员共享同一块内存,所有成员的起始地址相同。因此,每次只能存储一个成员的数据。修改一个成员的值会影响其他成员,因为它们共用同一块内存。
  2. 大小:联合体的大小由其最大成员的大小决定。即使联合体中有多个成员,它的大小将是最大的成员所占内存的大小。举个例子,在上述定义中,char 通常占 1 字节,int 通常占 4 字节,因此该联合体的大小通常是 4 字节(由 int 决定)。
  3. 内存优化:联合体提供了内存优化的功能,尤其适用于当某个变量需要存储不同类型的数据,但在同一时刻只需要存储其中一个数据时。例如,保存一个变量可能是整数,也可能是字符值,根据需要使用不同类型的数据时,使用联合体可以节省内存空间。

4.4 联合体的使用

4.4.1 基础使用

union Un
{char c;int i;
};int main()
{union Un u;u.c = 'A';u.i = 100;printf("c=%c i=%d\n", u.c, u.i);return 0;
}

此时,c=d i=100。

分析一下代码执行过程,首先u.c = ‘A’;此时联合体的内存内容是0x41。然后,u.i = 100;假设此时是小端模式则内存的内容是0x64。联合体是共享内存的所以u.c的内容已经被覆盖掉,char类型是一个字节,所以此时这个字节里面的内容用十进制表示就是100用ascii值表示就是d。

4.4.2 联合体的内存大小

#include <stdio.h>union Un {char c;int i;
};int main()
{union Un u;printf("Size of union Un: %zu bytes\n", sizeof(u));return 0;
}

根据上述代码,输出的联合体大小通常为 4 字节(这取决于系统架构)。这是因为 int 类型通常占 4 字节,而 char 占 1 字节。由于联合体共享内存,所以联合体的大小是最大成员(int 类型)所占的字节数。

4.5 应用场景

  1. 节省内存:当我们需要存储多种数据类型,但在某一时刻只需要其中之一时,使用联合体可以有效节省内存。例如,在编写操作系统或硬件驱动时,联合体常用于存储可能是不同类型的寄存器值。
  2. 类型转换:在一些需要类型转换的场合,联合体常被用作一种简便的方式来查看和修改不同数据类型的内存表示。例如,可以用联合体来将一个 float 类型的值解释为 int 类型的位表示,反之亦然。
#include <stdio.h>union Converter {float f;int i;
};int main()
{union Converter c;// 将 float 类型的值存储在联合体中c.f = 3.14f;// 打印 float 类型的值printf("Float value: %f\n", c.f);// 打印相应的 int 类型位表示printf("Int representation (as bits): %d\n", c.i);// 将 int 类型值赋给联合体的 i 成员c.i = 1078523331;  // 通过 int 位表示修改 float 值// 打印修改后的 float 值printf("Modified float value: %f\n", c.f);return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

deep generative model stanford lecture note3 --- latent variable

1 Introduction 自回归模型随着gpt的出现取得很大的成功&#xff0c;还是有很多工程上的问题并不是很适合使用自回归模型&#xff1a; 1&#xff09;自回归需要的算力太大&#xff0c;满足不了实时性要求&#xff1a;例如在自动驾驶的轨迹预测任务中&#xff0c;如果要用纯自回…

变形金刚多元宇宙

涉及的公司&#xff1a; 日本Takara公司 Diaclone可变形机器人玩具 Microman可变形机器人玩具 孩之宝 孩之宝与Takara签订协议后&#xff0c;孩之宝开始使用Takara的专利进行研发。 漫威 为了推广玩具&#xff0c;1984年5月&#xff0c;孩之宝玩具与漫威《变形金刚》漫画试探…

Day33【AI思考】-分层递进式结构 对数学数系的 终极系统分类

文章目录 **分层递进式结构** 对数学数系的 **终极系统分类**总览**一、数系演化树&#xff08;纵向维度&#xff09;**数系扩展逻辑树**数系扩展逻辑** **二、代数结构对照表&#xff08;横向维度&#xff09;**数系扩展的数学意义 **三、几何对应图谱&#xff08;空间维度&am…

蓝桥杯python基础算法(2-1)——排序

目录 一、排序 二、例题 P3225——宝藏排序Ⅰ 三、各种排序比较 四、例题 P3226——宝藏排序Ⅱ 一、排序 &#xff08;一&#xff09;冒泡排序 基本思想&#xff1a;比较相邻的元素&#xff0c;如果顺序错误就把它们交换过来。 &#xff08;二&#xff09;选择排序 基本思想…

对象的实例化、内存布局与访问定位

一、创建对象的方式 二、创建对象的步骤: 一、判断对象对应的类是否加载、链接、初始化: 虚拟机遇到一条new指令&#xff0c;首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用&#xff0c;并且检查这个符号引用代表的类是否已经被加载、解析和初始化…

OSCP - Proving Grounds - Roquefort

主要知识点 githook 注入Linux path覆盖 具体步骤 依旧是nmap扫描开始&#xff0c;3000端口不是很熟悉&#xff0c;先看一下 Nmap scan report for 192.168.54.67 Host is up (0.00083s latency). Not shown: 65530 filtered tcp ports (no-response) PORT STATE SERV…

Python + Tkinter + pyttsx3实现的桌面版英语学习工具

Python Tkinter pyttsx3实现的桌面版英语学习工具 在多行文本框输入英文句子&#xff0c;双击其中的英文单词&#xff0c;给出英文读音和中文含义和音标。 本程序查询本地词典数据。通过菜单栏"文件"->"打开词典编辑器"进入编辑界面。 词典数据存储…

实验六 项目二 简易信号发生器的设计与实现 (HEU)

声明&#xff1a;代码部分使用了AI工具 实验六 综合考核 Quartus 18.0 FPGA 5CSXFC6D6F31C6N 1. 实验项目 要求利用硬件描述语言Verilog&#xff08;或VHDL&#xff09;、图形描述方式、IP核&#xff0c;结合数字系统设计方法&#xff0c;在Quartus开发环境下&#xff…

17.3.4 颜色矩阵

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 17.3.4.1 矩阵基本概念 矩阵&#xff08;Matrix&#xff09;是一个按照长方阵列排列的复数或实数集合&#xff0c;类似于数组。 由…

音视频入门基础:RTP专题(8)——使用Wireshark分析RTP

一、引言 通过Wireshark可以抓取RTP数据包&#xff0c;该软件可以从Wireshark Go Deep 下载。 二、通过Wireshark抓取RTP数据包 首先通过FFmpeg将一个媒体文件转推RTP&#xff0c;生成RTP流&#xff1a; ffmpeg -re -stream_loop -1 -i input.mp4 -vcodec copy -an -f rtp …

【leetcode100】路径总和Ⅲ

1、题目描述 给定一个二叉树的根节点 root &#xff0c;和一个整数 targetSum &#xff0c;求该二叉树里节点值之和等于 targetSum 的 路径 的数目。 路径 不需要从根节点开始&#xff0c;也不需要在叶子节点结束&#xff0c;但是路径方向必须是向下的&#xff08;只能从父节点…

解锁数据结构密码:层次树与自引用树的设计艺术与API实践

1. 引言&#xff1a;为什么选择层次树和自引用树&#xff1f; 数据结构是编程中的基石之一&#xff0c;尤其是在处理复杂关系和层次化数据时&#xff0c;树形结构常常是最佳选择。层次树&#xff08;Hierarchical Tree&#xff09;和自引用树&#xff08;Self-referencing Tree…

python-leetcode-二叉树的层序遍历

102. 二叉树的层序遍历 - 力扣&#xff08;LeetCode&#xff09; # Definition for a binary tree node. # class TreeNode: # def __init__(self, val0, leftNone, rightNone): # self.val val # self.left left # self.right right from coll…

c++可变参数详解

目录 引言 库的基本功能 va_start 宏: va_arg 宏 va_end 宏 va_copy 宏 使用 处理可变参数代码 C11可变参数模板 基本概念 sizeof... 运算符 包扩展 引言 在C编程中&#xff0c;处理不确定数量的参数是一个常见的需求。为了支持这种需求&#xff0c;C标准库提供了 &…

w191教师工作量管理系统的设计与实现

&#x1f64a;作者简介&#xff1a;多年一线开发工作经验&#xff0c;原创团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339;赠送计算机毕业设计600个选题excel文…

Vuex状态管理

1、Vuex 是什么&#xff1f; Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 库。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 简单理解 Vuex可以帮我们管理全局的属性&#xff0c;并且是是响应式的&…

DBASE DBF数据库文件解析

基于Java实现DBase DBF文件的解析和显示 JDK19编译运行&#xff0c;实现了数据库字段和数据解析显示。 首先解析数据库文件头代码 byte bytes[] Files.readAllBytes(Paths.get(file));BinaryBufferArray bis new BinaryBufferArray(bytes);DBF dbf new DBF();dbf.VersionN…

亚博microros小车-原生ubuntu支持系列:20 ROS Robot APP建图

依赖工程 新建工程laserscan_to_point_publisher src/laserscan_to_point_publisher/laserscan_to_point_publisher/目录下新建文件laserscan_to_point_publish.py #!/usr/bin/env python3import rclpy from rclpy.node import Node from geometry_msgs.msg import PoseStam…

冷启动+强化学习:DeepSeek-R1 的原理详解——无需监督数据的推理能力进化之路

本文基于 DeepSeek 官方论文进行分析,论文地址为:https://github.com/deepseek-ai/DeepSeek-R1/blob/main/DeepSeek_R1.pdf 有不足之处欢迎评论区交流 原文翻译 在阅读和理解一篇复杂的技术论文时,逐字翻译是一个重要的步骤。它不仅能帮助我们准确把握作者的原意,还能为后续…

优选算法的灵动之章:双指针专题(一)

个人主页&#xff1a;手握风云 专栏&#xff1a;算法 一、双指针算法思想 双指针算法主要用于处理数组、链表等线性数据结构中的问题。它通过设置两个指针&#xff0c;在数据结构上进行遍历和操作&#xff0c;从而实现高效解决问题。 二、算法题精讲 2.1. 查找总价格为目标值…