当前位置: 首页 > news >正文

C语言学习之结构体

     在C语言中,我们已经学了好几种类型的数据。比如整型int、char、short等,浮点型double、float等。但是这些都是基本数据类型,而这些数据类型应用在实际编程里显然是不够用的。比如我们没有办法用一旦数据类型来定义一个”人“的属性。因此这里我们需要一个框,将不同的数据类型放在一起,这就是结构体,接下来我们将深入学习结构体的知识。

结构体

结构体的创建与初始化

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

        语法结构:

struct name//名字自定义
{member-list;//一个或者多个成员变量
}variable-list//可以省略。是结构体变量列表

举例说明

struct person
{char name[20];int age;float height;
};
int main()
{struct person p1 = {"诗华",18,1.72};return 0;
}

结构体的特殊声明

        声明结构体的时候可以不完全声明。

        但是匿名结构体的声明其后必须创建变量

struct//匿名结构体类型
{char name[20];int age;float height;
}s1,s2;

        匿名结构体类型只能用一次。

        但是切记这种方法不行

拓展:数据结构简单科普

        链表是一种数据结构。数据结构是描述数据在内存当中的存储结构,对数据的操作就是对数据结构的操作。

        大体上数据结构分为以下几种

结构的自引用

        在结构体中包含一个类型为该结构本身的成员是否可以呢?

上图报错的原因:结构体内不能包含同类型的自己。 

如果想正确表达,需要这样

struct Node
{int data;struct Node *next;
};int main()
{printf("%zd\n",sizeof(struct Node));/*struct person p1 = {"诗华",18,1.72};*/return 0;
}

结果为: 

而在上图中,int data为数据域,struct Node * next为指针域。

        也可以简化为:

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

之后如果使用结构体定义变量可以直接写为

Node n;

但是这么些是绝对错误的

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

结构体内存对齐

        我们学会结构体的基本使用后,那么问题来了,结构体的大小是怎么计算的呢?我们举例分析。

#include<stdio.h>
struct s1
{char c1;//1char c2;//1int n;//4
};
struct s2
{char c1;//1int n;//4char c2;//1
};
struct s3
{double d1;//8int n;//1char c1;//4
};
struct s4
{double d1;//8struct s3 S;//16char c2;//1
};
int main()
{printf("%zd\n",sizeof(struct s1));printf("%zd\n", sizeof(struct s2));printf("%zd\n",sizeof(struct s3));printf("%zd\n",sizeof(struct s4));return 0;
}

结果为:

        

        为什么是这样呢?

        结构体成员在存储的时候有内存对齐的现象。

        这里介绍一个新概念:offsetof——一个宏,可以计算出一个结构体成员相较于结构体起始位置的偏移量。(其实之前博主的博客预处理那节讲解过:C语言学习之预处理指令-CSDN博客)

        内存对齐的规则

1.结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处。

2.其他成员变量要对齐到某个数字(对齐数)的整数被的地址处

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

VS中默认的值为8

Linux中gcc编译器没有默认对齐数,对齐数就是成员变量自身的大小

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

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

        

 

为什么会存在内存对齐?

1.不是所有的硬件平台都能访问任意地址的任意数据;某些硬件平台只能在某些地址处去某些特定类型的数据。

2.数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存访问仅仅需要一次访问。

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

那在设计结构体的时候,我们既要满足对齐,又要节省空间如何做到呢“

让占用空间小的成员尽量集中在一起。

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

修改默认对齐数

        #pragma这个预处理指令可以改变编译器默认对齐数量。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#pragma pack(1)//设置结构体默认对齐数为1
struct S 
{char c1;int n;char c2;
};
#pragma	pack()//恢复默认对齐数int main()
{printf("%d\n",sizeof(struct S));return 0;
}

结构体传参

         

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

        上面的print1和print 2哪个更好呢?答案是print2

原因:

函数传参的时候,参数是需要压栈的,会有时间和空间上系统的开销。

如果传递一个结构体对象时,结构体体积过大,参数压栈的系统开销过大,会导致性能下降。

结论:结构体传参的时候,要穿结构体的地址

结构体实现位段  

        位段

        位段的声明和结构必须要类似,有两个不同。

1.位段的成员必须是int,unsigned int或者signed int 。在C99中位段成员类型也可以是其他类型

2.位段的成员名之后有一个冒号和一个数字

#include<stdio.h>
//结构体
struct A
{int a;int b;int c;int d;
};
//位段
struct B
{int _a:2;int _b : 3;int _c : 10;int _d : 5;
};
int main()
{printf("%zd\n", sizeof(struct A));printf("%zd\n", sizeof(struct B));return 0;
}

结构体和位段的大小分别为: 

        

位段的空间是如何开辟的呢?

位段的内存分配

1.位段的成员可以是:int,unsigned int或者signed int或者char类型

2.位段的空间上按照需要以4个字节和1个字节的方式开辟

3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植程序的程序避免使用。

//位段
struct B
{int _a:2;int _b : 3;int _c : 10;int _d : 30;
};
int main()
{/*printf("%zd\n", sizeof(struct A));*/printf("%zd\n", sizeof(struct B));return 0;
}

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

2.位段最大位的数目不确定(16位机器最大16,32位机器最大32.写成27的话16 位机器会出问题)

3.位段成员在内存中从左向右分配还是右向左分配是不确定的

4.当一个结构体包含两个位段的时候,第二个位段成员比较大,无法荣达第一个剩余的位时是舍弃剩余位置还是保留不确定

总结:与结构相比,位段可以达到同样的效果,更加节省空间不过会有跨平台的问题。

位段的应用

在网络协议的IP数据报中,用位段可以更节省空间的达到想要的效果,对网络畅通很有帮助。

 位段使用的注意事项

        位段的几个成员共同用一个字节,这样有i协成员起始位置并不是某个字节起始位置,那么这些位置只是没有地址的。内存中每个字节分配一个地址,一个字节内部的比特位是没有地址的。

        所以不能对位段的成员使用&*操作符,这样就不能直接使用scanf直接给位段的成员输入值。只能先输入放在一个变量里然后赋值给位段的成员

struct B
{int _a:2;int _b : 5;int _c : 10;int _d : 30;
};
int main()
{struct B s = {0};scanf("%d",&sa.b);//这是错误的//正确示范int b = 0;scanf("%d",b);sa._b = b;return 0;
}

感谢看到这里的读者朋友,求一个赞谢谢。

相关文章:

  • 《代码整洁之道》第9章 单元测试 - 笔记
  • 《代码整洁之道》第5章 格式 - 笔记
  • MRI学习笔记-conjunction analysis
  • docker(3) -- 图形界面
  • 驱动开发硬核特训 · Day 22(下篇): # 深入理解 Power-domain 框架:概念、功能与完整代码剖析
  • 《操作系统真象还原》第十章(1)——输入输出系统
  • 加密算法 AES、RSA、MD5、SM2 的对比分析与案例(AI)
  • 「Docker已死?」:基于Wasm容器的新型交付体系如何颠覆十二因素应用宣言
  • 2025.4.21-2025.4.26学习周报
  • 泰迪杯实战案例超深度解析:基于YOLOv5的农田害虫图像识别系统设计
  • 「Mac畅玩AIGC与多模态04」开发篇01 - 创建第一个 LLM 对话应用
  • 迷你世界UGC3.0脚本Wiki组件事件管理
  • 显存在哪里看 分享查看及优化方法
  • 分布式一致性算法起源思考与应用
  • 从“世界工厂”到“智造之都”:双运放如何改写东莞产业基因?
  • 云原生--核心组件-容器篇-5-Docker核心之-容器
  • 大模型、知识图谱和强化学习三者的结合,可以形成哪些研究方向?
  • 给视频自动打字幕:从Humanoid-X、UH-1到首个人形VLA Humanoid-VLA:迈向整合第一人称视角的通用人形控制
  • 蓝桥杯 1. 确定字符串是否包含唯一字符
  • Suna开源框架分析
  • 香港警务处高级助理处长叶云龙升任警务处副处长(行动)
  • 酒店就“保洁员调包住客港币”致歉,称希望尽早达成解决方案
  • 铁路上海站五一假期预计发送446万人次,同比增长8.4%
  • “世纪火种”嘉年华启动,69家单位加入阅读“朋友圈”
  • 游戏论|迟来的忍者与武士:从《刺客信条:影》论多元话语的争议
  • 五矿地产:今年要确保债务“不爆雷”、交付“不烂尾”