首页 > 其他分享 >【C语言】自定义类型

【C语言】自定义类型

时间:2024-06-20 19:30:55浏览次数:16  
标签:位段 struct 自定义 int C语言 char 类型 对齐 结构

目录

一、结构体:

1、结构体的声明:

2、结构体的自引用:

3、结构体变量的定义和初始化:

4、结构体内存对齐:

5、结构体传参:

6、位段:

二、枚举类型:

三、联合体:


一、结构体:

1、结构体的声明:

首先要了解什么是结构:

结构是一些值的集合,与数组不同的是结构的每一个成员变量可以使不同类型的变量。

其声明的时候用struct关键字加上名字,如下定义一个学生的结构体:

struct Student
{
	char name[20];
	int age;
	char sex[10];
	char id[20];
};

拓展:也可以声明为匿名结构体(就是没有名字的结构体),这种类型的结构体就没有办法使用了,只有在这种类型后面直接定义变量。

struct 
{
	char name[20];
	int age;
	char sex[10];
	char id[20];
}s1,s2;

如上,这就定义了一个匿名结构体,其定义了s1,s2这样的全局变量,就可以在函数中使用

2、结构体的自引用:

如果在一个结构体中嵌套一个结构体,那么并不是直接在结构体中直接嵌套一个结构体的,因为这样的话,这个结构体的大小就会计算不了(结构体嵌套一个结构体,嵌套一个结构体里面又会有一个结构体,就会计算不了)那么,就需要在一个结构体中,存放执行向下一个结构体的指针,这样的话大小就可以计算了。

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

3、结构体变量的定义和初始化:

1、可以在声明类型的同时定义变量:

struct Student
{
	char name[20];
	int age;
	char sex[10];
	char id[20];
}s1,s2;

2、可以在全局变量域中定义,并且在定义的同时赋初始值:

struct Node
{
    int x;
	int y;
}s1,s2;

struct Node s3 = {3,5};

3、若在结构体中引用另一个结构体并初始化:

struct Node1
{
	int a;
	int b;
};

struct Node2
{
	char x;
	struct Node1;
	float y;
};

struct Node2 node = { 'w', { 1,2 }, 3.14f };

4、结构体内存对齐:

结构体对齐规则: 1、第一个成员在与结构体变量偏移量为0的地址处。
2、其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。       对齐数:编译器默认的对齐数(VS上是8)和该成员大小的 较小值         可以用pragma pack(N)来修改默认对齐数,N是几,默认对齐数就修改为几,                 pragma pack()这串代码就是取消设置的默认对齐数,还原为默认 3、结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4、如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,       结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

那么为什么会有结构体对齐的规则呢?

在大多数资料都是这么说的:

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

例如:

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

5、结构体传参:

从代码入手:

struct S
{
	int data[1000];
	int num;
};

struct S s = { {1,2,3,4,5,6,7},13 };

void print1(struct S s)
{
	printf("%d\n", s.num);
}

void print2(struct S* s)
{
	printf("%d\n", s->num);
}
int main()
{
	print1(s);
	print2(&s);
	return 0;
}

在如上代码中,print1就是传结构体即可,print2就是传结构体地址的。

建议:

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

6、位段:

我们首先来看看位段是怎么定义的:

struct PPR
{
	int a : 2;
	int b : 6;
	int c : 13;
	int d : 31;
	char e : 7;
	unsigned int f : 2;
};

如上所示,

注意:

1、位段的成员必须是整型:int, unsigned int ,char等。

2、位段成员后面加上一个:和数字,来表示这个成员占有几个比特位(不能超过变量本身)

部分成员也可以不加冒号和数字。

理解:

位段时根据后面比特位的大小来给空间的,如:

struct PPR
{
	int a : 2;
	int b : 6;
	int c : 13;
	int d : 31;
	char x : 2;
	char e : 7;
};

对于上诉代码的位段理解:

首先第一个成员是int,就开辟32个比特位,接着把a,b,c可以都存放进去(2+6+13<32)

此时d的话放不进去,那么就在开辟32个比特位,将d存放进去。

在进行判断,发现只剩下一个比特位,x存放不进去,就在开辟char类型(8个比特位),然后将x放进去,在判断发现还剩下6个比特位,小于e,就在开辟8个比特位,将e存放进去。

以上就是位段的存放,但是位段有跨平台的问题:

1、int位段被当成有符号数还是无符号数是不确定的

2、位段中最大位的数目不能够确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题)

3、位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

4、当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

二、枚举类型:

枚举类型就是将有限的值一 一列举出来,比如:星期,月份等等。

1、定义:用关键字enum来定义,总体和结构体大差不差,

例如星期的定义:(各个枚举常量之间用逗号隔开,最后一个枚举常量后面不需要逗号)

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

2、理解:

这些枚举常量都是有值的,如果均没有赋值就从0开始向下一次增加1;

也可以随便赋值(整型),也是依次向下增加1。

3、优点:

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

(5)使用方便,一次可以定义多个常量

三、联合体:

联合体也叫共用体,里面的那些成员变量共用同一块空间,

例如:

union Un
{
	char x;
	int i;
};

1、特点:

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员) 但是当最大成员的大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。 例如:
union u1
{
	char arr[5];
	int i;
};

union u2
{
	short arr[7];
	int i;
};

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

如上所示:

u1中:char arr[5]这个最大对齐数为1成员大小为5

            int i这个最大对齐数为4成员大小为4,

综合来看:大小应该为5,又要是最大对齐数的整数倍,所以大小就是8.

u2中:short arr[7]这个最大对齐数为2成员大小为14

            int i这个最大对齐数为4成员大小为4,

综合来看:大小应该为14,又要是最大对齐数的整数倍,所以大小就是16./2.

2、应用:

判断计算机的大小端:

int main()
{
	union Un
	{
		int i;
		char a;
	}un;
	un.i = 1;
	if (un.a == 1)
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

标签:位段,struct,自定义,int,C语言,char,类型,对齐,结构
From: https://blog.csdn.net/2303_80828380/article/details/139832884

相关文章

  • [机器视觉]halcon应用实例 用户自定义多ROI模板匹配
    本示在前面几个halconROI示例的基础上继续扩展,更靠进实标情况。为了使ROI匹配更灵活,就要求可以让用户或工程根据实际使用情况自己去画ROI,想画几个是几个。数量不能在代码里写死。这次升级的主要是增加了一个while循环根据用户的鼠标按键来进行判断是否继续画,还是退出画ROI。效......
  • 探究C语言函数栈帧的创建和销毁
    引言在C语言程序中,每当一个函数被调用时,系统都会在栈上为该函数分配一块内存空间,这块内存空间就被称为栈帧。栈帧中包含了函数执行所需的所有信息,如局部变量、参数、返回地址等。栈帧的创建和销毁是函数调用的核心部分,它们确保了函数能够正确地执行和返回。本文将在VS2013环......
  • 数据处理技术-Hive的表与数据类型
    Hive数据模型Hive的数据模型主要由表构成,包括内部表,外部表,分区表和桶表。我也将从这四个方面介绍。在这之前先介绍另外一个概念:DDL,DataDefinitionLanguage数据定义语言,是SQL语言集中对数据库内部的对象结构进行创建,删除,修改等的操作语言。核心语法由CREATE,ALTER,DROP......
  • k8s探针类型及探针配置
    探针类型:存活探针(LivenessProbe):用于判断容器是否存活(running状态),如果LivenessProbe探针探测到容器不健康,则kubelet杀掉该容器,并根据容器的重启策略做相应的处理。如果一个容器不包含LivenessProbe探针,则kubelet认为该容器的LivenessProbe探针返回的值永远是“Success”。......
  • C语言基础入门 -1
    一.计算机中单位bit -比特位(最小单位);byte-字节kb-千字节mb-兆字节gb-千兆字节tb-太字节1字节=8比特位;其他单位之间换算为1024;二.数据类型与所占字节char//字符型   1字节short//短整型   2字节int  //整型   4字节long//长整型  ......
  • 单细胞测序最好的教程:(六)细胞类型注释|或许是全网最详细的注释教程
    作者按本教程将是本系列教程中最重要的一章,我们后续所有的单细胞分析,都要基于准确的细胞类型注释。本系列教程首发于“[单细胞最好的中文教程](single_cell_tutorialReadthedocs)”,未经授权许可,禁止转载。全文字数|预计阅读时间:4500|5min——Starlitnightly1.背景我们......
  • 单细胞测序最好的教程(九): 细胞类型自动注释|发表在Science的注释算法
    作者按本章节主要讲解了基于大模型的自动注释方法,包括CellTypist(发表在Science)和MetaTiME(发表在Naturecommunication),一个通用,一个泛癌专用。本教程首发于单细胞最好的中文教程,未经授权许可,禁止转载。全文字数|预计阅读时间:3000|3min——Starlitnightly(星夜)1.背景我......
  • 单细胞测序最好的教程(八): 细胞类型自动注释-1|基于marker的自动注释
    作者按本章节主要讲解了基于marker的自动注释方法,一般来说,我会先自动注释,再手动去确认marker,这是因为,对于一个陌生的组织,我对marker是不了解的,自动注释可以帮助我快速熟悉细胞类型。本教程首发于单细胞最好的中文教程,未经授权许可,禁止转载。全文字数|预计阅读时间:5000|5min......
  • 单细胞测序最好的教程(十):细胞类型注释迁移|万能的Transformer
    作者按本章节主要讲解了基于transformer的迁移注释方法TOSICA,该算法在迁移注释上达到了SOTA的水平,在注释这么卷的赛道愣是杀出了一条血路。本教程首发于单细胞最好的中文教程,未经授权许可,禁止转载。全文字数|预计阅读时间:3000|3min——Starlitnightly(星夜)1.背景迁移注释......
  • 快速自定义表单开发受欢迎的几个优势
    为了满足业务需求,低代码技术平台带着更理想的优势特点,广泛用于各中大型企业中,是助力企业实现提质增效、提升开发效率的有力武器。那么,您知道快速自定义表单开发的优势体现在哪里吗?为了帮助大家了解这些详情,接下来将会为大家介绍低代码技术平台及快速自定义表单开发的优势,希望能给......