C语言之结构体及位段

目录

结构体

结构体与数组

结构体成员类型

结构体的声明

结构体自引用 

结构体的定义和初始化

结构体成员的访问

结构体内存对齐

结构体的对齐规则

为什么要内存对齐

计算成员变量相对于结构体类型的偏移量

结构体传参

位段

举例: 

位段的内存分配

关于位段类型我的理解(VS编译器)

位段的跨平台问题

结构体

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

结构体与数组

  • 数组:一组相同类型元素的集合
  • 结构体:也是一些值得集合,但是值的类型可以不同

注意:结构体类型仅仅是一种数据类型,也可以存在结构体的数组 

结构体成员类型

结构体成员可以是标量,数组,指针,甚至是其他结构体。

结构体的声明

//1
struct Stu
{	//结构的成员变量
	char name[20];
	int	age;
	char id[20];
};
void main() {
	//s是局部变量
	struct Stu s1,s2;//创建结构体对象
}

//2
struct Stu
{	//结构的成员变量
	char name[20];
	int	age;
	char id[20];
}s1,s2;//s1,s2也是结构体变量,其是全局变量(创建结构体类型时顺带创建2个结构体全局变量)

//3
//匿名结构体类型
struct {
	char name[20];
	int price;
	char id[12];
}s;

结构体自引用 

定义:结构体要能够找到和他同类型的下一个结构体

结构体中本身可以包含另一个结构体变量

struct Book{
	struct Paper p;
	char name[20];
	int price;
	char id[12];
};
struct Paper {
	int size;
	char color[20];
};

但是结构体内不能有自身结构体变量,但是可以用指针指向一个本类型结构体来实现结构体自引用

//结构体自引用
struct Node
{
	int data;//数据域
	struct Node* next;//指针域
};

 关于typedef(定义类型)定义定义匿名结构体类型 

typedef struct
{
	int data;//数据域
	struct Node* next;//指针域
} Node;
void main() {
	Node n;
 }//编译后不可行

结构体的定义和初始化

struct People
{
	char c;
	short s;
	double d;
};
struct Stu
{	//结构的成员变量
	struct People p;
	char name[20];
	int	age;
	char id[20];
}s1 = { {'b',10,3.8},"lili",3,"8" },s2 = { {'j',20,3.1},"kate",302,"999" };//s1,s2也是结构体变量,其是全局变量,并对其全部初始化,其实也可不初始化
void main() {
	//s是局部变量
	struct Stu s = { {'c',20,3.14},"张三",30,"888"};//创建结构体对象并完成初始化
}


//2.匿名结构体类型初始化
struct {
	char name[20];
	int price;
	char id[12];
}s = { "git",7,"123" };

注意:

  • 结构体的声明和初始化也可以在函数里面进行
  • 结构体定义完就必须对其进行初始化
  • 结构体也可以通过操作符"."的形式来进行里面值的操作

结构体成员的访问

结构体变量访问成员结构体变量通过操作符"."来访问的,"."操作符接受2个操作数eg:

#include <stdio.h>
struct People
{
	char c;
	short s;
	double d;
};
struct Stu
{	//结构的成员变量
	struct People p;
	char name[20];
	int	age;
	char id[20];
}s1 = { {'b',10,3.8},"lili",3,"8" },s2 = { {'j',20,3.1},"kate",302,"999" };//s1,s2也是结构体变量,其是全局变量,并对其全部初始化,其实也可不初始化
void main() {
	//s是局部变量
	struct Stu s = { {'c',20,3.14},"张三",30,"888"};//创建结构体对象并完成初始化
	//.操作符
	printf("%s\n", s.name);
	printf("%c\n", s1.p.c);
	//->操作符
	struct Stu* ps = &s2;
	printf("%c\n", (*ps).p.c);//因为.的优先级比*高,所以应加()
	printf("%d\n", ps->p.s);
	printf("%s\n", ps->name);
}

结构体内存对齐

定义:结构体成员在内存中到底如何存储,有一个存放的规则,这就是结构体的内存对齐规则。

结构体的对齐规则

  1. 第一个成员在与结构体变量偏移量为0的地址处
  2. 其他的成员变量要对其到某个数字(对齐数:对齐数=编译器默认的一个对齐数与该成员大小的较小值,vs中默认值为8)的整数倍的地址处
  3. 结构体总大小为最大对齐数(每一个成员变量都有一个对齐数)的整数倍
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

理解:

  • 结构体的第一个成员放在结构体变量在内存中储存位置的0偏移地址处(选择0地址)
  • 从第二个成员往后的所有成员都放在一个对齐数(成员大小和编译器默认对齐数的较小值)的整数倍的地址处
  • 结构体的总大小是结构体所有成员对齐数中最大的那个对齐数的整数倍   
  • 嵌套结构体则内层结构体对齐到自己内层最大对齐数的整数倍处,外层结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
#include <stdio.h>
struct People
{
	char c;
	short s;
	double d;
};
void main() {
	struct People p = { 0 };//不完全初始化——把第一个初始化成0,剩下的默认本就是0;
	printf("%d\n", sizeof(p));//16
	printf("%d\n", sizeof(struct People));//16
}
#include <stdio.h>
struct Hand
{
	int size;
	char q;
};//8
struct People
{
	struct Hand h;
	char c;
	short s;
	double d;
};
void main() {
	struct People p = { 0 };//不完全初始化——把第一个初始化成0,剩下的默认本就是0;
	printf("%d\n", sizeof(p));//24
	printf("%d\n", sizeof(struct People));//24
}
//计算Hand结构体4+1=5,5不是4的整数倍,所以Hand结构体大小为8
//计算People结构体找到最大对齐数4的整数倍,因为放在第一所以不用找了8+1——10+2——16+8=24;

为什么要内存对齐

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

在设计结构体时我们既要满足对齐,又要节省空间,那么我们应该

  1. 让占用小的成员尽量集中在一起
  2. 修改默认对齐数

 #pragma pack(num)//num为设置的默认对齐数

#include <stdio.h>
//修改默认对齐数为2
#pragma pack(2)
struct Hand
{
	char c;
	int size;
	char q;
};
#pragma pack()//取消设置的默认对齐,还原默认
void main() {
	printf("%d\n", sizeof(struct Hand));//默认对齐数8时——12,默认对齐数2时——8
}

计算成员变量相对于结构体类型的偏移量

offsetof函数,使用时需引入头文件#include <stddef.h>

#include <stdio.h>
#include <stddef.h>
struct Hand
{
	char c;
	int size;
	char q;
};
void main() {
	printf("%d\n", offsetof(struct Hand,c));//0
	printf("%d\n", offsetof(struct Hand,size));//4
	printf("%d\n", offsetof(struct Hand,q));//8
}

结构体传参

#include <stdio.h>
struct Stu
{	//结构的成员变量
	char name[20];
	int	age;
	char id[20];
};
//传值调用
void print1(struct Stu t) {
	printf("%s,%d,%s\n", t.name, t.age, t.id);
}
//传址调用
void print2(const struct Stu* t) {
	printf("%s,%d,%s\n", t->name, t->age, t->id);
}
void main() {
	//s是局部变量
	struct Stu s = {"张三",30,"888"};//创建结构体对象并完成初始化
	print1(s);//传值调用,浪费空间,不可以改变结构体变量
	print2(&s);//传址调用,省空间,可以改变结构体变量
}

注意:

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

结构体传参中const的作用:使原结构体只可以进行读取操作不可以进行更改操作,以防止误操作

位段

位段的声明和结构体的声明是类似的,有2个不同

  • 位段的成员必须是int,unsigned int,或signed int(也可以char等属于整形家族的)。
  • 位段的成员名后边有一个冒号和一个数字

举例: 

struct A
{
	int _a : 2;//这里的int也可以改成char等整形类型下面改不改都可以
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

位段的内存分配

#include <stdio.h>
struct A
{
	//因为是int类型所以先开辟4个字节——32个比特位
	int a : 2; //a成员占2个比特位
	int b : 5; //b成员占5个比特位
	int c : 10;//c成员占10个比特位
	//用掉了17bit剩下15bit不够后面的30bit(因此,15bit直接舍弃重新开辟空间使用)因为下面是int所以又开辟32bit
	int d : 30;//d成员占30个比特位
	//总共用64bit因此占用空间8字节
};
void main() {
	printf("%d\n", sizeof(struct A));//8
}

注意:位段的大小根据需求设置,但位段大小不能设置超过类型所占空间的大小。

关于位段类型我的理解(VS编译器)

#include <stdio.h>
struct A
{
	char a : 4; 
	char b : 5;
	char c : 7;
	char d : 6;	
};
void main() {
	printf("%d\n", sizeof(struct A));//4
	//通过结果得知总共开辟了4次空间,说明开辟空间用后余下的空间没有和新开辟的空间结合,而是直接浪费掉
}
#include <stdio.h>
struct A
{
	int a : 4; 
	char b : 5;
	char c : 2;
	char d : 6;
	
};
void main() {
	printf("%d\n", sizeof(struct A));//8
	//通过结果得知int用后余下的空间虽然可以满足char的所有成员,但是没用,说明转换类型时,前类型余下的空间直接浪费掉
}

结构体8大小的由来

分配4字节空间给与a成员变量(结构体偏移量为0的地址处),转换类型,分配1字节空间给予以下char变量b和c(必须注意对齐到对齐数的整数倍地址处,因为是char所以就不纠结了)因为不够给d直接浪费掉,再开辟1字节空间给d(必须注意对齐到对齐数的整数倍地址处,因为是char所以就不纠结了),由于结构体大小是所有成员对齐数中最大那个对齐数的整数倍,也就是int(4字节)的整数倍,最后得出8

位段:变量类型所占空间的合理规划

在VS编译器下一个字节内部的数据,先使用低比特位的地址,再使用高比特位的地址(在内存中分配从右往左使用)

位段的跨平台问题

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

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值