自定义类型:结构体

目录

前言:

一、结构体类型的声明

1.1、什么是结构体?

1.2、结构的声明:

1.23、结构体变量的创建和初始化

[1.3.1 结构体变量的创建](#1.3.1 结构体变量的创建)

[1.3.2 结构体变量的初始化](#1.3.2 结构体变量的初始化)

1.4、结构的特殊声明

二、结构体的自引用

2.1、概念

2.2、通过链表来理解自引用:

三、结构体内存对齐

3.1、对齐规则

四、结构体实现位段

[4.1 什么是位段](#4.1 什么是位段)

[4.2 位段的内存分配](#4.2 位段的内存分配)

[4.3 位段的跨平台问题](#4.3 位段的跨平台问题)

[4.4 位段使用的注意事项](#4.4 位段使用的注意事项)

[4.4.1 位数限制:](#4.4.1 位数限制:)

[4.4.2 取地址操作限制:](#4.4.2 取地址操作限制:)

[4.4.3 整型升级:](#4.4.3 整型升级:)

[4.4.4 赋值范围:](#4.4.4 赋值范围:)

[4.4.5 数组限制:](#4.4.5 数组限制:)

[4.4.6 跨平台问题:](#4.4.6 跨平台问题:)

结束语:


前言:

哈喽!好久未见,甚是想念!不知道大家国庆玩的怎么样?反正小编去了北京玩了两天就病倒了 ,只能在宾馆度过余下的假期。不过呢也算因祸得福,提前从北京回来,回家里休息了两天,但是仅仅回家两天都能吃胖一些,简直是不可思议!!本来去北京玩几天都瘦了,结果回家吃了两天体重就增加了,很难想象......

言归正传!废话不多说,小编今天将会更新结构体模块,也希望本篇文章能够给大家带来一些学习结构体的帮助!!!

一、结构体类型的声明

1.1、什么是结构体?

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

1.2、结构的声明:

struct name
{
   member-list;
}variable-list;
  • name指的是该结构的名称
  • member-list是指成员列表
  • variable-list是变量列表

我们来描述一个人的信息

arduino 复制代码
struct Stu {
    char name[20];
    int age;
    char id[12];
    float height;
};//这里的分号是编译器默认的,不可缺少的

结构名称就是Stu,也就是你要描述的对象;

成员列表是指描述对象的一些信息,比如说一个学生的姓名,年龄,学号,体重等信息

这样我们就可以认为struct定义了一个学生类型,括号里面的都是它的成员,可以有一个成员,也可以有多个成员,并且这些成员的类型是可以不相同的。

1.3、结构体变量的创建和初始化

1.3.1 结构体变量的创建

variable-list是变量列表,这里这个没有表现出来,解释一下

struct Stu是以一个结构体类型,类型是用来创建变量的,因此当我们有了结构体类型,就可以进行下面这个操作

//结构体类型
struct Stu {
    char name[20];
    int age;
    char id[12];
    float height;
};
int main()
{
    //创建3个结构体变量
    struct Stu s1;
    struct Stu s2;
    struct Stu s3;
    return 0;
}

我们不仅可以将变量创建在main函数中,也可以放在结构体括号后面,比如说:

struct Stu {
    char name[20];
    int age;
    char id[12];
    float height;
}s4,s5,s6;

这里的s4,s5,s6与前面的s1,s2,s3是一样的,唯一的区别就是s4,s5,s6是全局变量,s1,s2,s3是局部变量

1.3.2 结构体变量的初始化

struct Stu {
    char name[20];
    int age;
    char id[12];
    float height;
}s4,s5,s6;
int main()
{
    //创建3个结构体变量
    struct Stu s1;
    struct Stu s2;
    struct Stu s3;
    return 0;
}

接下来对s1进行初始化

第一种初始化(按照上面struct中成员顺序初始化):

 struct Stu s1 = {"lisi",20,"20246061",57.2};

第二种初始化(使用.操作符):

  struct Stu s1 = { .age = 20,.height = 78.2,.name = "lisi",.id = "2024123"};

第一种初始化需要根据成员的顺序进行一一对应的初始化,第二种初始化不需要按照顺序进行,只需要通过.操作符寻找到struct中的成员即可。

我们该如何打印呢?

 printf("%d", s1.age);

这样便可以打印s1中的成员,我们也可以同时打印几个成员

printf("%s %d", s1.name,s1.age);

补充:. 操作符的用法:结构体变量名.成员名

1.4、结构的特殊声明

结构体的特殊声明又称匿名结构体,顾名思义,匿名就是将结构体的名称隐藏起来,也就是结构体的不完全声明。

正常的结构体:

struct Stu {
    char name[20];
    int age;
    char id[12];
    float height;
}s;

匿名结构体:

struct     
{
    char name[20];
    int age;
    char id[12];
    float height;
}s;

匿名结构体怎么使用呢?其实和正常的结构体是一样的

但是注意:匿名结构体类型只能使用一次

int main()
{
    s.age = 19;
    printf("%d", s.age);
    return 0;
}

也就是说在你创建匿名结构体类型后,创建一个变量使用一次之后,该匿名结构体类型便不能够再被使用。

举个栗子:

对于有名字的结构体来说:

   struct Student {
       int age;
       char id[20];
   };
   struct Student s1, s2;
  • 这里定义了 Student 结构体类型,可以方便地创建多个该类型的变量s1和s2。

  • 但是对于匿名结构体,以下代码是错误的:

     struct {
         int age;
         char name[20];
     } s1;
     struct {
         int age;
         char name[20];
     } s2;
    
  • 虽然s1和s2的结构相同,但由于匿名结构体没有名称,编译器会认为它们是不同的、独立的匿名结构体类型,所以不能这样重复定义类似结构的变量。在实际应用中,如果需要创建多个相同结构的变量,应该使用有名字的结构体类型。

二、结构体的自引用

2.1、概念

在 C 语言中,结构体的自引用是指结构体内部包含一个指向自身类型的指针成员。这种结构允许构建动态数据结构,如链表、树等。(这里了解即可,后面会详细介绍这些知识)

2.2、通过链表来理解自引用:

火车大概都清楚把!

可以把结构体的自引用想象成一列火车。每个车厢就像是一个结构体。车厢里的人(对应结构体中的数据成员),还有一个连接装置(对应指向自身结构体类型的指针)。这个连接装置可以用来连接下一个车厢,这样就可以形成一长串的车厢,也就是链表。例如,我们有一个结构体代表车厢:

   struct train
   {
       int people; // 车厢里的人,给每个人一个编号
       struct Carriage *next; // 连接下一个车厢的装置
   };

这就像火车车厢依次链接一样,通过next指针,可以将多个train结构体连接起来,从车头开始,沿着next指针就能遍历整列火车,这也就是链表。

三、结构体内存对齐

关于结构体的基本使用,就介绍到这里,接下来将会介绍一个相对来说比较重要的知识点,结构体的内存对齐。你可能会问学习这个有什么用呢?

还记得结构体是什么吗?结构体是一种类型,它和整型,字符型都是一样的, 我们知道这些类型都是有大小的,整型的大小是4个字节,字符型的大小是1个字节,那么结构体类型的大小是多少呢?由于结构体的特殊性,因此,结构体类型的大小是需要计算的 ,那么该如何计算,就需要用到我们接下来要介绍的结构体内存对齐了。

3.1、对齐规则

**(1)**结构体的第一个成员的地址与结构体的起始地址相同,即偏移量为 0。

**(2)**其它成员变量要对齐到每个数字(对齐数)的整数倍的地址处

对齐数= 编译器默认的一个对齐数与该成员变量大小的较小值

  • vs中默认的值是8

  • Linux中gcc没有默认对齐数,对齐数就是成员自身的大小

**(3)**结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大 的)的整数倍。

**(4)**如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。

第二条理解:

从第二个成员开始,每个成员的存储地址必须是该成员类型大小(如果类型大小 小于等于 4 字节)或者是 4 字节(如果类型大小 大于 4 字节)的整数倍。例如,对于一个int类型(通常为 4 字节)的成员,它的存储地址必须是 4 的倍数;对于一个char类型(1 字节)的成员,它的存储地址只要是 1 的倍数即可。

第三条理解:

结构体的总大小必须是其内部最大成员类型大小(如果最大成员类型大小小于等于 4 字节)或者是 4 字节(如果最大成员类型大小大于4 字节)的整数倍。这可能会导致在结构体的末尾添加填充字节。

文字解释可能有些难以理解,接下来结合例题来理解内存对齐

先来看两组结构体对比:

struct s1
{
	char c1;
	char c2;
	int n;
};
struct s2
{
	char c1;
	int n;
	char c2;
};
int main()
{
	printf("%zd\n", sizeof(struct s1));
	printf("%zd\n", sizeof(struct s2));
	return 0;
}

s1与s2的区别只有成员列表的顺序不同,接下来来打印s1与s2的大小。

哎!这是为何呢?明明s1与s2中的成员是一模一样的,为什么大小不同呢?

是不是很神奇?这里便涉及到了结构体的内存对齐

结构体s1的大小

c1是第一个成员,c1的大小是1,直接填写到起始地址(0)处即可,c2的大小是1,与8比较后对齐数是1,1是1的倍数,因此c2填写到地址为1的地方,n的大小是4,与8比较后对齐数是4,而2,3不是4的倍数,因此跳过从4开始填写,向后占4个空间,这时总大小便是从0~7,占8个位置,根据第三条结构体总大小需要是成员中最大值的倍数,s1中成员最大值是4,8是4的倍数,因此,最终结构体s1的大小即为8

补充:这里有待完善,描述的不是很清晰,还请大家先将就看一下,我会尽快将这一块整理清楚,介绍给大家,还请见谅!!!

四、结构体实现位段

4.1 什么是位段

位段的声明和结构是类似的,但有两个不同:

  1. 位段的成员必须是int,unsigned int 或 signed int ,在c99中位段成员的类型也可以选择其它类型。
  2. 位段的成员名后边又一个冒号和一个数字。

比如说:

struct A
{
	int a : 2;
	int b : 5;
	int c : 10;
	int d : 30;
};

这里的冒号和数字是什么意思呢?

注意:位段这里的位指的是2进制位

这里的位段A所占内存的大小是多少?

如果A是结构体的话,该占多少内存呢?

struct A
{
	int a;
	int b;
	int c;
	int d;
};

a占4个字节,b占4个字节,c占4个字节,d占4个字节,按照前面介绍的对齐方式,刚好对齐下来,从0到15,总共占16个字节。

那么位段A占多少字节呢?我们来算一下

printf("%d\n", sizeof(struct A));

运行结果是8,也就是说位段A所占内存是8个字节。似乎比结构体所占的空间要小,那么这个8是怎么算出来的呢?

按理来说每个整型都占4个字节,四个整型怎么能够算出来8个字节的呢?

因此,只能说冒号后面的数字所指的便是bite位了,只有这样的话才有可能算出来8个字节位。

位段中:a的意思是只占2个bite位,:b 只占5个bite位,:c 只占10个bite位,:d只占30个bite

这里又有一个问题,如果按照bite来算的话,位段A总共只占了47个bite位,一个字节是8个bite,那么应该只需要6个字节就可以了呀!为什么最后需要8个bite呢?

这里就涉及到了位段的内存分配。

4.2 位段的内存分配

位段是如何给它里面的成员分配空间的呢?

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

主要看第二个,位段开辟空间是根据所需来开辟,并且一次可以开辟4个字节或者1个字节

我们来用一个例子展示一下如何开辟的空间。

struct A
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};
int main()
{
	struct A s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

首先,我们先看一下位段中的成员,abcd都是char类型的,因此我们一次开辟一个字节的空间,一个字节8个bite位。

先开辟一个字节的空间,此时呢我们有8个bite,拿出3个用来存放a,这时候又会出现一个问题,是从左边开始存放还是右边存放呢?这里C语言并没有明文规定,因此我们先假设从右边开始存放

在第一个字节中,我们存放了a和b, 由于开辟的第一个字节空间不够存放c 因此我们在开辟一个字节的空间。第一个字节中还剩下的空间只能浪费。为了存放d,我们需要在开辟一个字节

如果按照这样分析的话,该位段占3个字节,我们来运行一下程序

结果是3,也就是说前面的分析是没有问题的。

vs上位段的开辟方式:

  1. 如果是char类型,我们就一个字节一个字节的开辟
  2. 一个字节内部,从右向左使用,如果剩余的空间不够下一个成员使用的话,浪费掉,然后开辟新的空间存放。

但是注意:在vs上是这样开辟不代表所有的都是这样开辟的,因此位段是不能够跨平台的。

现在我们来算一下第一个例子中位段的大小是8个字节。

struct A
{
	int a : 2;
	int b : 5;
	int c : 10;
	int d : 30;
};

位段成员类型是整型,因此一下开辟4个字节的空间,也就是32个bite位,从右侧开始存放

所以最终该位段A占8个字节的空间。

4.3 位段的跨平台问题

之所以说位段是不跨平台的有以下几点:

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

总结以下:

和结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题。

4.4 位段使用的注意事项

4.4.1 位数限制

位段占的二进制位数不能超过该基本类型所能表示的最大位数。例如在常见的 32 位系统中,如果使用int类型定义位段,那么最多只能是 32 位;如果超出这个范围,编译器可能会报错或者产生不可预期的结果。

4.4.2 取地址操作限制

不能对位段进行取地址操作。因为位段的成员在内存中的位置可能不是按照字节对齐的,取地址操作可能会导致不可预期的结果,所以编译器不允许这样做。如果需要给位段成员赋值,可以先将值存入一个普通变量中,再将该变量的值赋给位段成员。

4.4.3 整型升级

若位段出现在表达式中,则会自动进行整型升级,自动转换为int型或者unsigned int型。这可能会影响表达式的计算结果,特别是在与其他类型的数据进行混合运算时,需要注意数据类型的转换和结果的准确性。

4.4.4 赋值范围

对位段赋值时,最好不要超过位段所能表示的最大范围。例如,一个定义为 2 位的位段,其取值范围是 0~3,如果赋给它一个大于 3 的值,可能会导致数据丢失或其他不可预期的结果。

4.4.5 数组限制

位段不能出现数组的形式。即不能定义位段数组,因为位段的存储方式和普通数组的存储方式不兼容,这样的定义是不合法的。

4.4.6 跨平台问题

不同的编译器和平台可能对位段的实现细节存在差异,如位段的存储顺序(从左到右或从右到左)、位段是否支持跨字节边界等。如果代码需要在不同的平台上运行,要特别注意位段的可移植性问题,尽量避免依赖特定平台的实现细节。


结束语:

本篇文章介绍了结构体相关知识,不够可能是小编还没有学到通透,有些地方介绍的比较模糊,还希望各位看官大人见谅!后续小编也希望能够写出越来越好的文章,也希望能够帮助大家整理C语言的相关知识点。最后!期待我们都能变得越来越好!成为自己想做的人!

还有一个重磅消息!小编即将更新Java系列,希望各位能够捧捧场,来看一看,敬请期待!!1

相关推荐
双手插兜-装高手24 分钟前
Linux - 线程基础
linux·c语言·笔记
huaqianzkh30 分钟前
学习C#中的Parallel类
windows·microsoft·c#
XiaoCCCcCCccCcccC1 小时前
Linux环境下的基础开发工具 -- 包管理器,vim,gcc/g++,make/makefile,git,gdb/cgdb
linux·c语言·gdb
最后一个bug2 小时前
如何理解Lua 使用虚拟堆栈
linux·c语言·开发语言·嵌入式硬件·lua
一子二木生三火3 小时前
IO流(C++)
c语言·开发语言·数据结构·c++·青少年编程
时光の尘4 小时前
C语言菜鸟入门·关键字·void的用法
c语言·开发语言·c++·算法·c#·c·关键字
小林熬夜学编程4 小时前
【Linux系统编程】第四十九弹---日志系统构建指南:从基础结构到时间处理与Log类实现
linux·运维·服务器·c语言·开发语言·c++
折枝寄北4 小时前
C指针之舞——指针探秘之旅(2)
c语言·开发语言
陌小呆^O^4 小时前
关于C/C++Windows下连接MYSQL操作
c语言·c++·windows