前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >自定义类型:结构体

自定义类型:结构体

作者头像
LonlyMay
发布2024-10-21 21:32:34
770
发布2024-10-21 21:32:34
举报
文章被收录于专栏:一位计算机小白的学习日记

前言:

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

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

一、结构体类型的声明

1.1、什么是结构体?

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

1.2、结构的声明:

代码语言:javascript
复制
struct name
{
   member-list;
}variable-list;
  • name指的是该结构的名称
  • member-list是指成员列表
  • variable-list是变量列表

我们来描述一个人的信息

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

结构名称就是Stu,也就是你要描述的对象; 成员列表是指描述对象的一些信息,比如说一个学生的姓名,年龄,学号,体重等信息

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

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

1.3.1 结构体变量的创建

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

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

代码语言:javascript
复制
//结构体类型
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函数中,也可以放在结构体括号后面,比如说:

代码语言:javascript
复制
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 结构体变量的初始化
代码语言:javascript
复制
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中成员顺序初始化):

代码语言:javascript
复制
 struct Stu s1 = {"lisi",20,"20246061",57.2};

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

代码语言:javascript
复制
  struct Stu s1 = { .age = 20,.height = 78.2,.name = "lisi",.id = "2024123"};

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

我们该如何打印呢?

代码语言:javascript
复制
 printf("%d", s1.age);

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

代码语言:javascript
复制
printf("%s %d", s1.name,s1.age);

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

1.4、结构的特殊声明

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

正常的结构体:

代码语言:javascript
复制
struct Stu {
    char name[20];
    int age;
    char id[12];
    float height;
}s;

匿名结构体:

代码语言:javascript
复制
struct     
{
    char name[20];
    int age;
    char id[12];
    float height;
}s;

匿名结构体怎么使用呢?其实和正常的结构体是一样的 但是注意:匿名结构体类型只能使用一次

代码语言:javascript
复制
int main()
{
    s.age = 19;
    printf("%d", s.age);
    return 0;
}

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

举个栗子:

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

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

二、结构体的自引用

2.1、概念

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

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

火车大概都清楚把!

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

代码语言:javascript
复制
   struct train
   {
       int people; // 车厢里的人,给每个人一个编号
       struct Carriage *next; // 连接下一个车厢的装置
   };

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

5392f2d842da4b159c42b16f45e0ab4f.png
5392f2d842da4b159c42b16f45e0ab4f.png

三、结构体内存对齐

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

还记得结构体是什么吗?结构体是一种类型,它和整型,字符型都是一样的,我们知道这些类型都是有大小的,整型的大小是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 字节)的整数倍。这可能会导致在结构体的末尾添加填充字节。

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

先来看两组结构体对比:

代码语言:javascript
复制
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的大小。

7d735793d50444abbffb55d6efe956e1.png
7d735793d50444abbffb55d6efe956e1.png

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

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

结构体s1的大小

6dd5375fef24457aaabc72fc2d02ffde.png
6dd5375fef24457aaabc72fc2d02ffde.png

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. 位段的成员名后边又一个冒号和一个数字。

比如说:

代码语言:javascript
复制
struct A
{
	int a : 2;
	int b : 5;
	int c : 10;
	int d : 30;
};

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

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

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

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

代码语言:javascript
复制
struct A
{
	int a;
	int b;
	int c;
	int d;
};

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

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

代码语言:javascript
复制
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个字节

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

代码语言:javascript
复制
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个字节。

代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-10-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言:
  • 一、结构体类型的声明
    • 1.1、什么是结构体?
      • 1.2、结构的声明:
        • 1.3、结构体变量的创建和初始化
          • 1.3.1 结构体变量的创建
          • 1.3.2 结构体变量的初始化
        • 1.4、结构的特殊声明
        • 二、结构体的自引用
          • 2.1、概念
            • 2.2、通过链表来理解自引用:
            • 三、结构体内存对齐
              • 3.1、对齐规则
              • 四、结构体实现位段
                • 4.1 什么是位段
                  • 4.2 位段的内存分配
                    • 4.3 位段的跨平台问题
                      • 4.4 位段使用的注意事项
                        • 4.4.1 位数限制:
                        • 4.4.2 取地址操作限制:
                        • 4.4.3 整型升级:
                        • 4.4.4 赋值范围:
                        • 4.4.5 数组限制:
                        • 4.4.6 跨平台问题:
                    • 结束语:
                    相关产品与服务
                    对象存储
                    对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档