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

自定义类型—结构体

目录

1 . 结构体类型的声明

1.1 结构的声明

1.2 结构体变量的创建与初始化

1.3  结构体的特殊声明

1.4 结构体的自引用

2. 结构体内存对齐

2.1 对齐规则

2.2 为什么存在内存对齐

2.3 修改默认对齐数

3. 结构体传参

4.结构体实现位段

4.1 位段的内存分配

4.3 位段的应用

4.4 位段的使用注意事项


1 . 结构体类型的声明

1.1 结构的声明

struct tag
{member-list;
}variable-list;

假如描述一个学生

struct Stu 
{int age;char name[20];char sex[5];char id[20];
}

1.2 结构体变量的创建与初始化

struct Stu
{char name[20];//名字int age;//年龄char sex[5];//性别char id[20];//学号
};
int main()
{//按照结构体成员的顺序初始化struct Stu s = { "张三", 20, "男", "20230818001" };printf("name: %s\n", s.name);printf("age : %d\n", s.age);printf("sex : %s\n", s.sex);printf("id : %s\n", s.id);//按照指定的顺序初始化struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex = "⼥" };printf("name: %s\n", s2.name);printf("age : %d\n", s2.age);printf("sex : %s\n", s2.sex);printf("id : %s\n", s2.id);return 0;
}

1.3  结构体的特殊声明

在声明结构体的时候,可以进行不完全声明(又称匿名结构体)

struct
{int a;char b;float c;
}x;struct
{int a;char b;float c;
}a[20], * p;

上述两个结构体在声明的时候省略了结构体标签

在某些情况下,如果只是想使用一次结构体,就可以使用匿名结构体

在此基础上,下面代码合法吗

p = &x;
编译器会把上面的两个声明当成完全不同的两个类型,所以是非法的。
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用⼀次。

1.4 结构体的自引用

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

看下列代码

struct Node
{int data;struct Node next;
}

 分析一下不难发现,其实是不行的,因为一个结构体中再包含一个同类型的结构体变量,这样结

构体变量的大小就会无穷大。

struct S
{int n;struct S* next;
};

但是如果时包含和自己相同类型的指针,是可行的,在x86或x64的环境下,指针的大小无非就是

4/8字节。

 在结构体自引用使用的过程中,夹杂了 typedef 对匿名结构体类型重命名,也容易引入问题,看看

下面的代码,可行吗
typedef struct
{int data;S* next;
}S;
是不行的,因为S是对前面的匿名结构体类型的重命名产生的,但是在匿名结构体内部提前使
用Node类型来创建成员变量,这是不行的。

2. 结构体内存对齐

看一段代码

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

如果只按成员的大小来看的话,该结构体应该只占用6个字节就够了

但程序运行起来后可以发现是12个字节。

这就涉及到结构体内存对齐。

2.1 对齐规则

1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的⼀个对齐数与该成员变量大小的较小值。
VS 中默认的值为 8
- Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小
3. 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对齐数,所有对齐数中最大的)的
整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构
体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
一共9个字节,按照规则3来说,为最大对齐数的整数倍,那就是3 * 4 = 12个字节
再来看几个例子
struct S2
{char c1;char c2;int i;
};

一共8个字节,按照规则3来说,为最大对齐数的整数倍,那就是2* 4 = 8个字节

struct S3
{double d;char c;int i;
};

一共15个字节,按照规则3来说,为最大对齐数的整数倍,那就是4* 4 = 16个字节

struct S4
{char c1;struct S3 s3;double d;
};

其中嵌套了s3,已经知道s3的大小是16个字节,其中最大对齐数是8,那么就从偏移量8开始占16个字节。

一共32个字节,按照规则3来说,为最大对齐数的整数倍,那就是4* 8 = 32个字节

2.2 为什么存在内存对齐

1. 平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器
需要作两次内存访问;而对齐的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字
节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对齐成8的倍数,
那么就可以用⼀个内存操作来读或者写值了。否则,我们可能需要执行两次内存访问,因为对象可
能被分放在两个8字节内存块中。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。
个人观点:主要原因还是第二个
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占⽤空间小的成员尽量集中在⼀起
如上例 S1,S2
struct S1
{char c1;int i;char c2;
}s;struct S2
{char c1;char c2;int i;
};

同样的成员类型,我们可以发现S1占12个字节,S2就只占8个字节了。

2.3 修改默认对齐数

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

#include <stdio.h>
#pragma pack(1)//设置默认对⻬数为1
struct S
{char c1;int i;char c2;
};
#pragma pack()//取消设置的对⻬数,还原为默认
int main()
{printf("%d\n", sizeof(struct S));return 0;
}

3. 结构体传参

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更好,因为print1在传参时是传值调用,这个值有多大就得开辟多大的空间,仅是一个data数

组就要了4000个字节的空间。

而print2在传参的时候是传址调用,传地址过去,大小无非就是4 / 8个字节,效率更高。

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

4.结构体实现位段

4.1 位段的定义

位(二进制位)

位段的声明和结构是类似的,有两个不同:
1. 位段的成员必须是 int unsigned int signed int ,在C99中位段成员的类型也可以选择其他类
型。
2. 位段的成员名后边有⼀个冒号和⼀个数字。
struct A
{int _a : 2;int _b : 5;int _c : 10;int _d : 30;
};struct B
{int _a;int _b ;int _c ;int _d ;
};
int main()
{printf("%zd\n", sizeof(struct A));printf("%zd\n", sizeof(struct B));return 0;
}
A就是⼀个位段类型。
后面跟的数字是代表分配多少比特位
我们来看看效果
可见 ,位段是专门用来节省内存空间的。
但是 ,如果只按照分配的比特位来看,2+5+10+30 = 47 ,应该只分配6个字节就够了,为什么是8
个。这就涉及到了位段的内存分配

4.1 位段的内存分配

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;
};int main()
{struct S s = { 0 };s.a = 10; s.b = 12;s.c = 3;s.d = 4;printf("%zd\n", sizeof(s));return 0;
}

4.2 位段的跨平台问题
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会
出问题。)
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当⼀个结构包含两个位段,第⼆个位段成员比较大,无法容纳于第⼀个位段剩余的位时,是舍弃
剩余的位还是利用,这是不确定的。
结论:跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存
在。

4.3 位段的应用

下图是网络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要几个bit位就能描述,这

里使用位段,能够实现想要的效果,也节省了空间,这样网络传输的数据报大小也会较小⼀些,对

网络的畅通是有帮助的。

4.4 位段的使用注意事项

位段的几个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些
位置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。
所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输⼊值,只能是先输
入放在⼀个变量中,然后赋值给位段的成员。
http://www.lryc.cn/news/335648.html

相关文章:

  • 【JavaWeb】Jsp基本教程
  • 外包干了25天,技术退步明显.......
  • C++(14): STL条件变量std::condition_variable
  • Harmony与Android项目结构对比
  • langchain 学习笔记-FunctionCalling三种方式
  • CNAS软件测试公司有什么好处?如何选择靠谱的软件测试公司?
  • Cohere推出全新升级版RAG大型AI模型:支持中文,搭载1040亿参数,现开源其权重!
  • 搭建前后端的链接(java)
  • Java多路查找树(含面试大厂题和源码)
  • day6 | 哈希表 part-2 | 454 四数相加II 、383. 赎金信、15. 三数之和、18. 四数之和
  • Redis常见数据类型(2)
  • SparkBug解决:Type mismatch; found : org.apache.spark.sql.Column required: Double
  • MQ之————如何保证消息的可靠性
  • TrollInstallerX官方一键安装巨魔商店
  • 生成随机图片验证码
  • 【0280】《数据库系统概论》阅读总结(附xmind思维导图)
  • 数据结构(二)----线性表(顺序表,链表)
  • 为什么你选择成为一名程序员?
  • 【Android】系统启动流程分析 —— SystemServer 处理过程
  • Web前端—属性描述符
  • SpringBoot及其特性
  • 「JavaEE」初识进程
  • 计算机视觉——图像特征提取D2D先描述后检测特征提取算法原理
  • The “from“ argument must be of type string. Received undefined——vue报错记录
  • 汽车4S行业的信息化特点与BI建设挑战
  • JSX 和 HTML 之间的区别
  • AI日报:GPT-4-Turbo正式版自带读图能力;Gemini1.5Pro开放API;SD3将于4月中旬发布;抖音宫崎骏AI特效爆火
  • IDEA 宝贝插件
  • [C语言][数据结构][链表] 单链表的从零实现!
  • oracle rac打补丁后sqlplus / as sysdba ora-12537