目录
结构体
结构体与数组
结构体成员类型
结构体的声明
结构体自引用
结构体的定义和初始化
结构体成员的访问
结构体内存对齐
结构体的对齐规则
为什么要内存对齐
计算成员变量相对于结构体类型的偏移量
结构体传参
位段
举例:
位段的内存分配
关于位段类型我的理解(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
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);
}
定义:结构体成员在内存中到底如何存储,有一个存放的规则,这就是结构体的内存对齐规则。
理解:
#include
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
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;
总结:结构体内存对齐是拿空间换时间的做法
在设计结构体时我们既要满足对齐,又要节省空间,那么我们应该
#pragma pack(num)//num为设置的默认对齐数
#include
//修改默认对齐数为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
#include
#include
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
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个不同
struct A
{
int _a : 2;//这里的int也可以改成char等整形类型下面改不改都可以
int _b : 5;
int _c : 10;
int _d : 30;
};
#include
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
}
注意:位段的大小根据需求设置,但位段大小不能设置超过类型所占空间的大小。
#include
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
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编译器下一个字节内部的数据,先使用低比特位的地址,再使用高比特位的地址(在内存中分配从右往左使用)
总结:和结构相比,位段可达到同样的效果,但是可以很好的节省空间,但是有跨平台问题的存在。