自定义类型【c语言】

news2024/11/28 13:40:40

前言:

c语言提供了丰富的内置类型,但是在描述一些复杂对象的时候仍不能满足一定的功能,因此c语言为了支持我们能描述一些复杂对象给出了我们能自定义的一些类型,因此便有了自定义类型。

在之前我们已经初步对结构体进行相应的了解,今天我将带领大家进一步的学习结构体。

目录

  • 结构体
    • 1.1结构体类型的声明
      • 1.1.1 结构的基础知识
      • 1.1.2 结构的声明
      • 1.1.3 特殊的声明
    • 1.2结构的自引用
    • 1.3结构体变量的定义和初始化
    • 1.4结构体内存对齐
    • 1.5修改默认对齐数
    • 1.6 结构体传参
  • 位段
    • 2.1 什么是位段
    • 2.2 位段的跨平台问题
    • 2.3 位段的应用
  • 枚举
    • 3.1 枚举类型的定义
    • 3.2 枚举的优点
    • 3.3 枚举的使用
  • 联合
    • 4.1 联合类型的定义
    • 4.2 联合的特点
    • 4.3 联合大小的计算

结构体

1.1结构体类型的声明

1.1.1 结构的基础知识

定义:

结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量

1.1.2 结构的声明

我们以描述一个学生进行相应的举例:

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

1.1.3 特殊的声明

struct 
{
 char name[20];//名字
 int age;     //年龄
 char sex[5]; //性别
 char id[20]; //学号
}; //匿名结构体

上面的这个结构在声明的时候省略掉了结构体标签(tag),这个时候编译器便会出现警告,而上述这个结构体我们叫做匿名结构体。那么我们想要使用这个匿名结构体应该怎么办呢,我们可以通过以下的方式去进行相应的操作:

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

int main()
{
	return 0;
}

这时我们创建了sb1,但是这种情况下我们只能使用一次。

大家在看如下的代码是否正确:

struct 
{
	char name[20];//名字
	int age;     //年龄
	char sex[5]; //性别
	char id[20]; //学号
}sb1;

struct 
{
	char name[20];//名字
	int age;     //年龄
	char sex[5]; //性别
	char id[20]; //学号
}*ps;

int main()
{
	ps = &sb1;
	return 0;
}

结果如下所示:
在这里插入图片描述
从上我们得出一条结论:

即使上述结构体的成员变量相同,但是编译器会把上面的两个声明当成完全不同的两个类型。所以是非法的。

1.2结构的自引用

定义:结构体的自引用 ,就是在结构体内部,包含指向自身类型结构体的指针

错误方式:

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

这种声明是错误的,因为这种声明实际上是一个无限循环,成员next是一个结构体,next的内部还会有成员是结构体,依次下去,无线循环。在分配内存的时候,由于无限嵌套,也无法确定这个结构体的长度,所以这种方式是非法的。

正确方式如下(需要使用指针):

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

而当我们使用typedef时,这种方式存在一定的争议,有的说不推荐这样使用,有的则持反对态度,我们简单的介绍一下:
错误示范:

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

这里的目的是使用typedef为结构体创建一个别名Node。但是这里是错误的,因为类型名的作用域是从语句的结尾开始,而在结构体内部是不能使用的,因为还没定义。
正确示范:

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

1.3结构体变量的定义和初始化

有了结构体类型,那如何定义变量,这里我们进行相应的介绍有以下两种方法:
第一种:

struct stu
{
	char name[20];//名字
	int age;     //年龄
	char sex[5]; //性别
	char id[20]; //学号
}sb1; //声明类型的同时定义变量p1

第二种:

struct stu sb2; //定义结构体变量p2

初始化情况也有以下几种:
第一种:

//初始化:定义变量的同时赋初值。
struct stu sb1 = {"张三","15","男","12456"}; //局部变量

第二种:

struct stu
{
	char name[20];//名字
	int age;     //年龄
	char sex[5]; //性别
	char id[20]; //学号
}sb1{"张三","15","男","12456"}; //全局变量

还有结构体嵌套初始化,跟这是一个道理的,在这里就不做展示了。

1.4结构体内存对齐

我们已经掌握了结构体的基本使用了。紧接着我们将深入讨论一个问题:计算结构体的大小,这也是一个特别热门的考点: 结构体内存对齐
我们通过以下例子来进行了解:

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

struct S2
{
	char c1;
	char c2;
	int i;
};
int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

结果如下:
在这里插入图片描述
解答:
在这里我们为了方便看出结果,我们可以用到一个相关的函数来进行计算,这个函数叫做offsetof:

offsetof (type,member)
此具有函数形式的宏返回数据结构或联合类型类型中成员成员的偏移值(以字节为单位),如下图所示:
在这里插入图片描述
具体在内存中的分布如下图所示:
在这里插入图片描述

这里就涉及到了一个我们需要了解的内存对齐问题。
首先得掌握结构体的对齐规则:

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

为什么存在内存对齐?

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

总体来说:结构体的内存对齐是拿空间来换取时间的做法。
注意:

那在设计结构体的时候,我们让占用空间小的成员尽量集中在一起。

上面的s2我相信大家根据这个方法很容易就可以计算出答案。

1.5修改默认对齐数

相信大家都有这样的疑问,在vs下默认值为8,那么可以改变吗,结果当然是可以的。

#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;
}

结果如下:
在这里插入图片描述

结论:

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

1.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;
}

那么上述两种方案,哪种更值得采纳呢?
首选print2函数,原因如下:

函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

因此结构体传参的时候,要传结构体的地址。

位段

2.1 什么是位段

位段-其中的位其实是二进制位,位段的声明和结构是类似的,有两个不同:

1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字。

我们通过以下代码进行相应的理解:


struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
int main()
{
	printf("%d\n", sizeof(struct A));
	return 0;

}

结果如下:
在这里插入图片描述
解答:
在这里我们需要了解位段的分配原则,具体如下:

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

所以,开始时我们需要开辟四个字节的空间,但是代码内存的开辟中占用47个bit,所以我们还需要开辟4个字节的空间,所以综上所述我们需要开辟8个字节的空间,所以最后结果为8.

2.2 位段的跨平台问题

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

总结:
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在

2.3 位段的应用

网络中IP数据包的封装就可以表示:
在这里插入图片描述

枚举

枚举顾名思义就是一一列举,把可能的取值一一列举。

3.1 枚举类型的定义

我们已描述星期为准进行举例:

enum Day//星期
{
 Mon,
 Tues,
 Wed,
 Thur,
 Fri,
 Sat,
 Sun
};

以上定义的 enum Day 是枚举类型,{}中的内容是枚举类型的可能取值,也叫枚举常量
枚举的可能取值,每一个可能的取值是常量,不能修改
例如:

enum Color//颜色
{
 RED=1,
 GREEN=2,
 BLUE=4
};

3.2 枚举的优点

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

3.3 枚举的使用

enum Color//颜色
{
 RED=1,
 GREEN=2,
 BLUE=4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异

联合

4.1 联合类型的定义

联合也是一种特殊的自定义类型,这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)
例如:

 union Un
{
	char c;//1
	int i;//4
	double d;//8
};

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

我们可以看到结果如下:
在这里插入图片描述
从上我们可以看出相应的定义,联合体共占一份空间。

4.2 联合的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)

union Un
{
	char c;//1
	int i;//4
	double d;//8
};

int main()
{
	union Un un;
	printf("%p\n", &un);
	printf("%p\n", &(un.c));
	printf("%p\n", &(un.i));
	printf("%p\n", &(un.d));
	return 0;
}

我们可以得到结果如下:
在这里插入图片描述
我们可以发现我们所占的是同一地址,

4.3 联合大小的计算

联合的大小至少是最大成员的大小。
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
我们举个例子来进行了解:

union Un1
{
	char c[5];
	int i;
};
int main()
{
	printf("%d\n", sizeof(union Un1));
	return 0;
}

结果如下:
在这里插入图片描述
解答:

对于char来说,在内存中占用1个字节,而int在内存中占用4个字节,因此最大对齐数为4,而char有5个元素,因此就要对齐到最大对齐数的整数倍,所以为8

到此,我们有关自定义类型的介绍便到此介绍了,大家多多练习,掌握这部分内容还是很容易的!

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

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

相关文章

提升工作效率,领导都夸的开源数据可视化工具

echarts官网有很多好看的图例,怎么结合起来放到自己的项目中呢?比如这种酷酷的首页: 这种看起来美观又大方,自己要是能用上就好了。 其实这是可以的,echarts上有现成的图例和示例代码,咱们只要改改数据源就…

华为防火接与二层交换机对接配置VLAN上网设置

拓扑图 一、防火墙设置 1、G1/0/0接口设置IP&#xff0c;G1/0/1接口切换二层口设置VLAN&#xff0c;G1/0/0 桥接了本地无线网卡来模拟外网地址 <USG6000V1>sys [USG6000V1]sys FW1 [FW1]un in en# 设置外网IP [FW1]int g1/0/0 [FW1-GigabitEthernet1/0/0]ip addr 192.1…

ORB-SLAM2 --- LocalMapping::SearchInNeighbors函数

0.函数更新内容 仅对地图点进行融合。 1.函数作用 检查并融合当前关键帧与相邻帧&#xff08;两级相邻&#xff09;重复的地图点。 2.函数步骤 Step 1&#xff1a;获得当前关键帧在共视图中权重排名前nn的邻接关键帧 Step 2&#xff1a;存储一级相邻关键帧及其二级相邻关键帧 将…

Java变量的作用域:静态变量、全局变量和局部变量

变量的作用域规定了变量所能使用的范围&#xff0c;只有在作用域范围内变量才能被使用。根据变量声明地点的不同&#xff0c;变量的作用域也不同。根据作用域的不同&#xff0c;一般将变量分为不同的类型&#xff1a;成员变量和局部变量。下面对这几种变量进行详细说明。成员变…

代码随想录训练营第四十二天

1.背包问题 1.1 01背包 有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 1.1.1 用动态规划的方法解决------二维dp数组01背包 ①确定dp…

学一下这个60秒的男人

程序员求职简历&#xff0c;项目经验怎么写&#xff1f;免费修改简历、提供模板并内部推荐今天想跟大家聊一下这个“60秒”的男人。10月21日&#xff0c;罗辑思维发文&#xff1a;《罗胖60秒&#xff1a;10年期满&#xff0c;今日告别》。10年前&#xff0c;罗振宇开始干一件事…

智能防雷,智能防雷系统的应用研究方案

“智慧智能防雷”是近年来防雷界提出的一个全新的防雷理念&#xff0c;是防雷业发展的趋势。所谓“智慧智能防雷”&#xff0c;是将大数据分析、云存储、人工智能、移动互联网和物联网技术融入到传统防雷措施中&#xff0c;并通过软、硬件系统的集成&#xff0c;实现对特定的区…

企业微信收款后可以进行退款吗?如何操作?

很多企业使用企业微信运营&#xff0c;就是看中了企业微信对外收款的功能&#xff0c;它不仅简化了转账步骤&#xff0c;而且可以在必要时直接完成退款&#xff0c;操作简单方便。前言随着企业微信的普及度&#xff0c;越来越多的企业认识到企业微信运营功能的强大&#xff0c;…

带你了解2023新版本Internet Download Manager有哪些新功能优势

作为一款体积只有10M的下载软件&#xff0c;IDM却常年霸占着各软件评测榜的前列。它的界面简洁清爽&#xff0c;使用过程中无弹窗、无广告&#xff0c;小小的体积竟能将下载速度提升5倍&#xff01;该软件一进入中国市场&#xff0c;便受到了广大用户的追捧&#xff0c;被大家亲…

2023年留学基金委(CSC)联合培养博士研究生项目解读及建议

近日&#xff0c;国家留学基金委&#xff08;CSC&#xff09;公布了2023年国家建设高水平大学公派研究生项目&#xff0c;该项目分为两部分&#xff0c;1.申请攻读博士学位研究生&#xff1b;2.申请联合培养博士研究生。本文知识人网小编仅就联合培养博士研究生部分进行解读&am…

【生信】R语言进行id转换的方法(附可直接使用代码)

本文我都默认已经下载好了表达矩阵exp了哦 代码都是直接给出来了&#xff0c;需要修改的地方我进行了标记 一般只要修改一下都能直接用了 方法一&#xff1a;下载平台数据以得到对应信息 然后进入官网https://www.ncbi.nlm.nih.gov/geo/query/acc.cgi&#xff0c;在这里我以G…

【数据结构】4.4 数组

4.4.1 数组的定义 数组&#xff1a; 按照一定格式排列起来的&#xff0c;具有相同类型的数据元素的集合。 一维数组&#xff1a; 若线性表中的数据元素为非结构的简单元素&#xff0c;则称为一维数组。逻辑结构&#xff1a;线性结构&#xff0c;固定长度的线性表。声明格式…

如何学习微服务架构?(项目学习)

哪些项目适合使用微服务架构&#xff1f;对于一般的公司来说&#xff0c;微服务的实践有着很大的技术挑战&#xff0c;所以并不是所有的公司都适合将整体架构拆分成微服务架构。一般来说&#xff0c;微服务架构更适合于未来具有一定扩展复杂度、具有大量增量用户期望的应用&…

最新综述:基于语言模型提示学习的推理

©PaperWeekly 原创 作者 | OE-Heart引言推理能力是人类智能的核心能力之一。随着预训练技术的不断发展&#xff0c;大模型辅之以提示学习&#xff08;如 Chain-of-Thought Prompting [1]&#xff09;涌现出一系列的惊人的推理能力&#xff0c;引起了学术界、工业界学者的…

动态规划——数位dp

数位dp 文章目录数位dp概述题目特征基本原理计数技巧模板例题度的数量思路代码数字游戏思路代码不要62思路代码概述 数位是指把一个数字按照个、十、百、千等等一位一位地拆开&#xff0c;关注它每一位上的数字。如果拆的是十进制数&#xff0c;那么每一位数字都是 0~9&#xf…

unity 前向渲染 渲染阴影原理

下面情况默认是 前向渲染路径&#xff0c;场景中平行光开启了阴影方式原理备注ShadowMap把相机放到光源的位置&#xff0c;那么场景中该光源的阴影区域就是那些相机看不到的位置得到的是&#xff1a;场景中距离光源最近的表面位置&#xff08;深度信息&#xff09;unity中专门的…

一个基于SpringBoot+vue的学生信息管理系统详细设计

一个基于SpringBootvue的学生信息管理系统详细设计 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码…

【docker08】本地镜像发布到阿里云

本地镜像发布到阿里云流程 1.流程 2.镜像的生成方法 基于当前容器创建一个新的镜像&#xff0c;新功能增强命令&#xff1a; docker commit [OPTIONS] 容器ID [REPOSITORY[:TAG]] 3.将本地镜像推送到阿里云 3.1本地镜像素材原型 3.2阿里云开发者平台 进入阿里云找到控制台进…

Word控件Spire.Doc 【Table】教程(2):如何设置Word表格列宽

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

CV中一些常见的特征点

Harris、SIFT、SURF、ORB特征点总结本篇博客介绍一些常见的特征点。Brief描述子&#xff1a;编辑切换为居中添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09;编辑切换为居中添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09;编辑切换为居…