04.自定义类型:结构体

news/2023/6/7 0:38:49

1 结构体的声明

1.1 结构的基础知识

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

1.2 结构的声明 

struct tag
{
member-list;//成员列表
}variable-list;//变量列表

EG:

描述一位学生: 

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

1.3 特殊的声明

在声明结构的时候,可以不完全的声明。
比如:

/*struct Stu
{char name[20];int age;
} s1,s2;*///全局变量,s1和s2是两个结构体变量typedef struct Stu//将复杂类型简单化
{char name[20];int age;
}Stu;//相当于将struct stu进行了重命名,主函数运用的时候直接用stu即可。struct
{char name[20];int age;
}s1;//匿名结构类型,直接创建名字struct
{int a;char c;double d;
}* p;//匿名结构类型的指针,创建pint main()
{struct Stu s3, s4;//局部变量Stu s5, s6;return 0;
}
//匿名结构体类型
struct
{int a;char b;float c;
}x;//x是全局变量,int main创建的是局部变量
struct
{int a;char b;float c;
}a[20], * p;

上面的两个结构在声明的时候省略掉了结构体标签(tag)。
那么问题来了?

//在上面代码的基础上,下面的代码合法吗?
p = &x;

警告:
原因:编译器会把上面的两个声明当成完全不同的两个类型。所以是非法的。

1.4 结构的自引用

//链表:

//数据结构->数据在内存中存储的结构

//顺序数据结构:顺序表

//链表(✳)

//放入同类型的下一个类型的指针(结构体自引用的方式) 

//二叉树 

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

//代码1
struct Node
{
int data;
struct Node next;
};
//可行否?
如果可以,那sizeof(struct Node)是多少?

正确的自引用方式:

//代码2
struct Node
{
int data;
struct Node* next;
};

用法:

typedef struct Node
{int data;struct Node* next;
}Node;int main()
{struct Node n1;struct Node n2;n1.next = &n2;return 0;
}

 注意:

/代码3
typedef struct
{
int data;
Node* next;
}Node;
//这样写代码,可行否?
//解决方案:
typedef struct Node
{
int data;
struct Node* next;
}Node;

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

有了结构体类型,那如何定义变量,其实很简单

struct Point
{int x;int y;
}p1 = {10, 20};struct Point p2 = {0,0};struct S
{int num;char ch;struct Point p;float d;
};int main()
{struct Point p3 = {1,2};struct S s = { 100, 'w', {2,5}, 3.14f};struct S s2 = {.d=1.2f, .p.x=3,.p.y=5, .ch = 'q', .num=200};printf("%d %c %d %d %f\n", s.num, s.ch, s.p.x, s.p.y, s.d);//乱序初始化printf("%d %c %d %d %f\n", s2.num, s2.ch, s2.p.x, s2.p.y, s2.d);return 0;
}

struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

1.6 结构体内存对齐

探讨:计算结构体的大小

热门考点:结构体内存对齐

#include <stdio.h>
#include <stddef.h>struct S1
{char c1;//int i;//char c2;//
};struct S2
{char c1;//1char c2;//1int i;//4
};//offsetofstruct S3
{double d;char c;int i;
};struct S4
{char c1;struct S3 s3;double d;
};int main()
{printf("%d\n", sizeof(struct S4));//printf("%d\n", sizeof(struct S3));//printf("%d\n", sizeof(struct S1));//12// 论证方法:宏offsetof//printf("%d\n", sizeof(struct S2));//8//printf("%d\n", offsetof(struct S1, c1));//printf("%d\n", offsetof(struct S1, i));//printf("%d\n", offsetof(struct S1, c2));return 0;
}

//S1和S2里面只是改变了顺序,为什么内存大小不一样 

1.结构体的第一个成员,对齐到结构体在内存中存放位置的0偏移处

2. 从第二个成员开始,每个成员都要对齐到(一个对齐数) 的整数倍处对齐数: 结构体成员自身大小和默认对齐数的较小值

VS :默认对齐数数8
Linux gcc:没有默认对齐数,对齐数就是结构体成员的自身大小
3.结构体的总大小,必须是所有成员的对文数中最大对齐数的整数倍

4.如果结构体中嵌套了结构体成员,要将嵌套的结构体成员对齐到自己的成员中最大对齐数的整数停处。结构体的总大小必须是最大对齐数的整数倍,这里的最大对齐数是: 包含嵌套结构体成员中的对齐数,的所有对齐数中的最大值。

struct S1
{char c1;//最大对齐数:1int i;//最大对齐数:4/8中最小的值,故为4char c2;//最大对齐数:1/4,故为4
};
//现在用了9,必须为最大对齐数4的倍数(又浪费了3个空间),故为12

struct S2
{char c1;//1char c2;//1int i;//4
};

s2用到的空间更小,是因为它将小类型的放在一起了 

我们在设计时,为了使得空间占用小,需要将小类型的放在一起。

//去较小值为对齐数 

struct S3
{double d;//占用8个字节 char c;//对齐到整数倍的对齐数(8和char字节的最小值)int i;//4,8最小为4,倍数为12,12,13,14,15
};

占用8个字节 

存在内存对齐的原因:

1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。

//例如:
struct S1
{char c1;int i;char c2;
};
struct S2
{char c1;char c2;int i;
};

S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别。

 1.7 修改默认对齐数

//VS环境下默认对齐数是8

之前我们见过了 #pragma 这个预处理指令,这里我们再次使用,可以改变我们的默认对齐数。

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为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就相当于没有对齐了 

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

1.8 结构体传参

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优于print 1

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

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

2. 位段

结构体讲完就得讲讲结构体实现位段的能力。

2.1 什么是位段

位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字

EG:

struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};//节省空间
//作用于结构体类似

 32:5

2 (30)

5 (25)

10 (15)//有很多的不确定因素

32:

30

A就是一个位段类型。
那位段A的大小是多少?
printf("%d\n", sizeof(struct A));

2.2 位段的内存分配

%2.7.02:33:15

//位段是为了节省空间的,不存在对其

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

//一个例子
struct S
{char a : 3;char b : 4;char c : 5;char d : 4;
};
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
//空间是如何开辟的?


2.3 位段的跨平台问题

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

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


 

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

如若内容造成侵权/违法违规/事实不符,请联系郑州代理记账网进行投诉反馈,一经查实,立即删除!

相关文章

[oeasy]python0054_三引号_原样显示字符串_triple_quoted

三引号 回忆上次内容 \ 首先是转义字符 可以 和别的字符 构成转义序列 \a是 ␇ (bell),\b 退回一格\t 水平制表符\v、\f LineFeed\\ 输出 \\" 输出 "\ 输出 \xhh 通过 16 进制数值转义\nnn 通过 8 进制数值转义\ 还是 续行字符 放在 行尾可以让 下一行和本行 连成一…

精彩文章汇总

福利&#xff1a;GitHubIDEANginx框架Spring Boot数据结构与算法Linux面经职场杂谈JavaJavaEELuceneShiro设计模式Git前端博客搭建小程序微服务分布式网络原理实战SpringSpring MVCMyBatisMaven数据库Dubbo理解神兵利器

2018年文章汇总

Android ANR 实例分析Linux kernel计算某段代码运行时间Linux Kernel 发展和内核特点C/C函数指针与指针函数(二)老王带你理解算法复杂度O(1),O(N),O(N^2)Android NDK Tombstone/Crash 分析堆和栈的区别&#xff08;转过无数次的文章&#xff09;C语言scanf-周末杂想C语言-scanf…

使用 Python 抓取知乎美图(文末含福利)知乎钓鱼贴汇总

福利 当我们爬虫写好&#xff0c;入库&#xff0c;并成功展示出来&#xff0c;不知不觉就实现了一个小程序项目&#xff1a;宅宅生活收藏夹 微信搜索小程序&#xff1a;宅宅生活收藏夹。欢迎大家使用。 之前写到宅宅生活收藏夹的部署方法&#xff0c;见 使用Flask&#xf…

福利 | 2018各大技术大会资料汇总(可下载)

年底了&#xff0c;送一波福利。 这些都是本人收集的2018各大技术大会资料汇总。 以下资料全部都来自于互联网&#xff0c;请勿用作商业用途。只是希望大家都能转给身边有需要的人~ SACC2018 &#xff08;关注公众号&#xff1a;coder-zwz 回复&#xff1a;SACC&#xff09; D…

纯福利 | 前端新人面试题汇总-基础篇

近来&#xff0c;由于我的公众号粉丝越来越多&#xff0c;当然留言和各种问题也越来越多&#xff0c;虽说近来一段 因为产品接近收尾上线阶段&#xff0c;确实略忙&#xff0c;我有时候甚至回到我温暖的家 都将近深夜11点&#xff08;我也不知道为何这么拼&#xff0c;后面我会…

【福利】小程序开发资源干货汇总

随着小程序开发的日益深广&#xff0c;越来越多的业务需求把小程序提上日程&#xff0c;小程序的学习和开发逐渐成为前端开发者必备的技能和核心竞争力&#xff0c;不管是在工作中开发项目&#xff0c;还是储备知识&#xff0c;小程序资源干货总是备受瞩目。 官方指南 官方工具…

王大师送福利啦!2021年面试题目汇总

本人详解 作者&#xff1a;王文峰&#xff0c;参加过 CSDN 2020年度博客之星&#xff0c;《Java王大师王天师》作者 公众号&#xff1a;山峯草堂&#xff0c;非技术多篇文章&#xff0c;专注于天道酬勤的 Java 开发问题、中国国学、传统文化和代码爱好者的程序人生&#xff0c;…