C语言中自定义类型讲解

news2024/11/25 9:32:25
  • 前言:C语言中拥有三种自定义类型,这三种自定义类型是怎么运用呢?在内存中又是怎么存储的呢?通过这篇文章我们来逐个讲解讲解。

三种类型分别是:

1.结构体 – 通俗的来讲就是可以把不同类型的变量放在一个集合中
2.枚举 – 可以理解成把所有事情全部列举出来
3.联合体 – 通俗的理解成,成员公用一块空间的结构体

自定义类型

    • 结构体
      • 结构体的声明:
      • 匿名结构体类型:
      • 结构体的自引用:
      • 结构体的定义与初始化:
      • 结构体在内存中的存储方式
      • 为什么存在内存对齐?
      • 结构体传参:
    • 位段
      • 位段的跨平台问题
    • 枚举
      • 枚举的优点
    • 联合(共用体)
      • 联合体在内存中的存储
      • 联合大小的计算

结构体

结构体的声明:

struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}; //分号不能丢

我们在声明结构体的时候,也可以选择匿名(通俗的讲就是这个结构体我们只会用一次)

匿名结构体类型:

//匿名结构体类型
struct
{
 int a;
 char b;
 float c;
}x;

在这之前我们学过,“循环”,“判断语句”,这些都是可以嵌套使用的,在结构体当中,我们是否也可以嵌套使用结构体呢?就是在结构体中在引用结构体。

结构体的自引用:

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

我们可以发现,我们通过结构体指针的方式,在结构体中引用结构体的方式,可以来实现结构体的自引用。

结构体的定义与初始化:

struct Point
{
 int x;
 int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};
struct Stu        //类型声明
{
 char name[15];//名字
 int age;      //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
 int data;
 struct Point p;
 struct Node* next; 
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

结构体在内存中的存储方式

要了解结构体的存储方式,首先我们来看一看它是否和我们平常想象的方式一样在内存中存储。
例题:

struct S1
{
	char c1;
	int i;
	char c2;
};

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

分析:

按照我们平常的逻辑,char c1占1个字节,int i 占4个字节,char c2占一个字节,总的加一起应供占6个字节,我们来看看运行的结果是不是六个字节呢?
运行结果:在这里插入图片描述
因此我们可以知道,实际上他并不是像我们想象的方式在内存中存储的,那是怎么存储的呢?

首先得掌握结构体的对齐规则:

  1. 第一个成员在与结构体变量偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
    VS中默认的值为8
  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
    体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

这段话又是什么意思呢?我们接着上面的代码继续分析
我们把下图比作内存条:第一个成员在与结构体变量偏移量为0的地址处(故名思意就是代码的起始处)。如图所示的C1就是所放的位置,其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值,因为int类型的的大小是4与VScode下的默认对齐数8相比较小,因此int i必须放在内存位置4个倍数的位置,如下图中所放的位置,同理C2也是如此,可是最后的结果如图加起来应该是9,为什么结果是12呢?我们看 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。因为此结构体中最大对齐数是4,因此我们的内存总大小是12,和我们刚刚所对的运行结果刚刚好一样,因此我们可以通过这个结构体的对齐规则来掌握结构体在内存中的存储。
在这里插入图片描述

为什么存在内存对齐?

平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

我们也可修改默认对齐数,来控制结构体中内存的存储。

#pragma pack(8)//设置默认对齐数为8
struct S1
{
 char c1;
 int i;
 char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

通过这个例子我们知道了通过 pragma来控制默认对齐数,那么我们来一道例题。

#include <stdio.h>
#pragma pack(8)
struct S1
{
 char c1;
 int i;
 char c2;
};
#pragma pack()
#pragma pack(1)//设置默认对齐数为1
struct S2
{
 char c1;
 int i;
 char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
    //输出的结果是什么?
    printf("%d\n", sizeof(struct S1));
    printf("%d\n", sizeof(struct S2));
    return 0;
}

如图所示:这里是引用
S1和上个例题相同就不再过多讲解,在S2中我们的默认对齐数是1可知C1放在起始处,后面可以根据依次连接的方式对齐。因此结果是12、6
在这里插入图片描述

因此我们可以知道:结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。

结构体传参:

我们直接看代码:

struct S
{
 int data[1000];
 int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
 printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
 printf("%d\n", ps->num);
}
int main()
{
 print1(s);  //传结构体
 print2(&s); //传地址
 return 0;
}

请问:上面的 print1 和 print2 函数哪个好些?
答案是:首选print2函数。
原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。在print1中是将整个结构体传过去,而print2中是传的地址,因此print2会大幅度提高性能

位段

  1. 位段的成员可以是 int (unsigned int) (signed int) 或者是 char (属于整形家族)类型。
  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
  4. 位段的声明和结构是类似的,但是位段的成员名后边有一个冒号和一个数字。

例如:

struct A 
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
}s1;

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

代码分析:

因为位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。

  • 声明类型是int类型的,所以一开始开辟了4个整型的空间即32bit
  • _a用掉了2bit,还剩下30bit
  • _b用掉了5bit,还剩下25bit
  • _c用掉了10bit,还剩下15bit
  • _d需要30bit,可是我们所剩下的空间只有15bit,又因为_d是int类型,因此在开辟一个整型的空间即32bit位将_d所需要的30bit位全部放进去,因此我们可以知道,一共开辟了8个字节的空间。
    运行结果:在这里插入图片描述

位段的跨平台问题

  1. int 位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
    器会出问题。
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

枚举

枚举顾名思义就是一一列举。
把可能的取值一一列举。
比如我们现实生活中:
一周的星期一到星期日是有限的7天,可以一一列举。

看代码:

enum Day//星期
{
 Mon,
 Tues,
 Wed,
 Thur,
 Fri,
 Sat,
 Sun
};
enum Sex//性别
{
 MALE,
 FEMALE,
 SECRET
}enum Color//颜色
{
 RED,
 GREEN,
 BLUE
};

当然这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。
我们来看一道题目带你懂枚举的值:

enum ENUM_A
{
	X1,
	Y1,
	Z1 = 255,
	A1,
	B1,
};
int main()
{
	enum ENUM_A enumA = Y1;
	enum ENUM_A enumB = B1;
	printf("%d %d\n", enumA, enumB);
	return 0;
}

代码分析:

我们刚刚讲过默认从0开始,一次递增1,当然在定义的时候也可以赋初值。那么这题的答案是多少?我们不难看出X1是从0开始故而Y1是1,然而Z1我们给它赋了初始值255,那B1是多少?是在255的基础上加2还是接着前面的0,1开始呢?看运行结果:
在这里插入图片描述
显然我们可以在知道,B1的值是在接着我们所赋的值来继续加1,因此我们也了解了枚举的作用。

枚举的优点

  1. 增加代码的可读性和可维护性
  2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
  3. 防止了命名污染(封装)
  4. 便于调试
  5. 使用方便,一次可以定义多个常量

联合(共用体)

定义:联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
口头的文字肯定不如代码的效果明显,所以我们直接上代码:

//联合类型的声明
union Un
{
 char c;
 int i;
};
//联合变量的定义
union Un un;

联合体在内存中的存储

  • 联合的大小至少是最大成员的大小。
  • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

例题:

union Un
{
	int i;
	char c;
};
union Un un;

int main()
{
	// 下面输出的结果是一样的吗?
	printf("%d\n", &(un.i));
	printf("%d\n", &(un.c));
	return 0;
}

前面我们说过,特征是这些成员公用同一块空间(所以联合也叫共用体)。
因此我们可以知道题目是公用一块空间。
在这里插入图片描述

联合大小的计算

例题:

union Un1
{
	char c[5];
	int i;
};
union Un2
{
	short c[7];
	int i;
};

int main()
{
	//下面输出的结果是什么?
	printf("%d\n", sizeof(union Un1));
	printf("%d\n", sizeof(union Un2));
	return 0;
}

代码分析:

Un1的分析:占8个字节在这里插入图片描述
Un2的分析:占16个字节在这里插入图片描述
好了,今天的讲解就到这里了,如果有讲的不好的地方希望各位多多指出。

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

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

相关文章

计算机图像处理-直方图均衡化

直方图均衡化 直方图均衡化是图像灰度变换中有一个非常有用的方法。图像的直方图是对图像对比度效果上的一种处理&#xff0c;旨在使得图像整体效果均匀&#xff0c;黑与白之间的各个像素级之间的点分布更均匀一点。通过这种方法&#xff0c;亮度可以更好地在直方图上分布。 …

将外包jar包导入到本地Maven仓库中

文章目录 1.问题描述2.方法如下 1.问题描述 有时候我们需要引入阿里云或者mvnRespository网上没有对于的jar。需要下载别人的jar的包&#xff0c;然后放到自己的项目的libs目录下。这样很不方便。因此需要把外包的jar,导入到本地maven仓库中。这样再pom.xml文件中直接按三要素…

项目进展(四)-双电机均可驱动,配置模拟SPI,调平仪功能初步实现!

一、前言 截止到今天&#xff0c;该项目也算实现基本功能了&#xff0c;后续继续更新有关32位ADC芯片相关的内容&#xff0c;今天对驱动芯片做一个总结&#xff0c;也对模拟SPI做一点总结吧 二、模拟SPI 由于模拟SPI还是得有四种模式(CPOL和CPHA组合为四种)&#xff0c;下面…

虚拟DOM详解

面试题&#xff1a;请你阐述一下对vue虚拟dom的理解 什么是虚拟dom&#xff1f; 虚拟dom本质上就是一个普通的JS对象&#xff0c;用于描述视图的界面结构 在vue中&#xff0c;每个组件都有一个render函数&#xff0c;每个render函数都会返回一个虚拟dom树&#xff0c;这也就意味…

基于视频技术与AI检测算法的体育场馆远程视频智能化监控方案

一、方案背景 近年来&#xff0c;随着居民体育运动意识的增强&#xff0c;体育场馆成为居民体育锻炼的重要场所。但使用场馆内的器材时&#xff0c;可能发生受伤意外&#xff0c;甚至牵扯责任赔偿纠纷问题。同时&#xff0c;物品丢失、人力巡逻成本问题突出&#xff0c;体育场…

系统集成|第十九章(笔记)

目录 第十九章 风险管理19.1 风险管理的概述及相关概念19.2 主要过程19.2.1 规划风险管理19.2.2 识别风险19.2.3 实施定性风险分析19.2.4 实施定量风险分析19.2.5 规划风险应对19.2.6 控制风险 上篇&#xff1a;第十八章、安全管理 下篇&#xff1a;第二十章、收尾管理 第十九…

什么是密码管理,密码管理的重要性

密码管理是通过遵守一套可持续的做法&#xff0c;在从创建到关闭的整个生命周期中保护和管理密码的过程。这是在存储特权的密码管理器的帮助下实现的 使用内置加密保管库的凭据。 随着 IT 环境的扩展&#xff0c;密码激增&#xff0c;并且随着需要保护的密码越来越多&#xff…

SpringMVC 学习(八)整合SSM

10. 整合 SSM (1) 新建数据库 CREATE DATABASE SSM;USE SSM;DROP TABLE IF EXISTS BOOKS;CREATE TABLE BOOKS (BOOK_ID INT(10) NOT NULL AUTO_INCREMENT COMMENT 书ID,BOOK_NAME VARCHAR(100) NOT NULL COMMENT 书名,BOOK_COUNTS INT(11) NOT NULL COMMENT 数量,DETAIL VARCH…

《计算机视觉中的多视图几何》笔记(12)

12 Structure Computation 本章讲述如何在已知基本矩阵 F F F和两幅图像中若干对对应点 x ↔ x ′ x \leftrightarrow x x↔x′的情况下计算三维空间点 X X X的位置。 文章目录 12 Structure Computation12.1 Problem statement12.2 Linear triangulation methods12.3 Geomet…

NLP的不同研究领域和最新发展的概述

一、介绍 作为理解、生成和处理自然语言文本的有效方法&#xff0c;自然语言处理 &#xff08;NLP&#xff09; 的研究近年来迅速普及并被广泛采用。鉴于NLP的快速发展&#xff0c;获得该领域的概述和维护它是困难的。这篇博文旨在提供NLP不同研究领域的结构化概述&#xff0c;…

淘宝商品详情接口数据采集用于上货,无货源选品上货,采集淘宝天猫商品详情数据

淘宝商品详情接口数据采集可用于上货。先通过关键字搜索接口&#xff0c;抓取到批量的商品ID&#xff0c;再将商品ID传入商品详情数据采集接口的请求参数中&#xff0c;从而达到批量抓取商品详情数据的功能。 接口名称&#xff1a;item_get&#xff0c;获取商品详情数据&#…

读高性能MySQL(第4版)笔记17_复制(下)

1. 复制切换 1.1. 复制是高可用性的基础 1.1.1. 总是保留一份持续更新的副本数据&#xff0c;会让灾难恢复更简单 1.2. “切换副本”&#xff08;promoting a replica&#xff09;和“故障切换”&#xff08;failing over&#xff09;是同义词 1.2.1. 意味着源服务器不再接…

JDBC【DBUtils】

一、 DBUtils工具类&#x1f353; (一)、DBUtils简介&#x1f95d; 使用JDBC我们发现冗余的代码太多了,为了简化开发 我们选择使用 DbUtils Commons DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库&#xff0c;使用它能够简化JDBC应用程序的开发&#xff0c…

Ubuntu 20.04二进制部署Nightingale v6.1.0和Prometheus

sudo lsb_release -r可以看到操作系统版本是20.04&#xff0c;sudo uname -r可以看到内核版本是5.5.19。 sudo apt-get update进行更新镜像源。 完成之后&#xff0c;如下图&#xff1a; sudo apt-get upgrade -y更新软件。 选择NO&#xff0c;按下Enter。 完成如下&…

C# Onnx Yolov8 Detect 手势识别

效果 Lable five four one three two 项目 代码 using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing;…

软件设计师考试

知识点速记 数据库 三范式和BC范式之间的关系 并发操作带来的问题是数据的不一致性&#xff0c;主要有三类&#xff1a; 丢失更新:两个事务T1和T2读入同一数据并修改&#xff0c;T2提交的结果破坏了T1提交的结果&#xff0c;导致T1的修改被丢失 不可重复读&#xff0c;不可重…

【小笔记】fasttext文本分类问题分析

【学而不思则罔&#xff0c;思维不学则怠】 2023.9.28 关于fasttext的原理及实战文章很多&#xff0c;我也尝试在自己的任务中进行使用&#xff0c;是一个典型的短文本分类任务&#xff0c;对知识图谱抽取的实体进行校验&#xff0c;判断实体类别是否正确&#xff0c;我构建了…

windows 下 vs code 格式化代码(clang-format)

vscode 的格式化代码能力来源于插件&#xff08;有不止一种插件提供格式化功能&#xff09;&#xff0c;而非 vscode 本身 1、安装插件 2、windows 下载 LLVM-17.0.1-win64.exe &#xff08;exe 结尾的安装包&#xff09; Releases llvm/llvm-project GitHub 可以直接把这…

python - random模块随机数常用方法

文章目录 前言python - random模块随机数常用方法1. 返回1-10之间的随机数&#xff0c;不包括102. 返回1-10的随机数&#xff0c;包括103. 随机选取0到100之间的偶数4. 返回一个随机浮点数5. 返回一个给定数据集合中的随机字符6. 从多个字符中选取特定数量的字符7. 生成随机字符…

巧用@Conditional注解根据配置文件注入不同的bean对象

项目中使用了mq&#xff0c;kafka两种消息队列进行发送数据&#xff0c;为了避免硬编码&#xff0c;在项目中通过不同的配置文件自动识别具体消息队列策略。这里整理两种实施方案&#xff0c;仅供参考&#xff01; 方案一&#xff1a;创建一个工具类&#xff0c;然后根据配置文…