自定义类型之结构体-爱代码爱编程
一、基础知识
1.结构体的声明
1.1结构类型的声明
关键字 自定义标识符
{
类型名1 成员名表1;
......
类型名n 成员名表n;
};
例:
struct student
{
long num;
char name[20];
float score;
};
1.2结构类型变量的声明
关键字 自定义标识符 结构变量
直接声明
struct student//系统只为已声明变量的结构类型分配存储空间
{
long num;
char name[20];
}stu1,stu2;//此结构变量占字节数:4+20=24;
间接声明
struct student
{
long num;
char name[20];
};
struct student stu1,stu2;//关键字 自定义标识符 结构变量
无类型名声明(匿名结构体)//只用一次时
struct //省去结构名,直接给出结构变量
{
long num;
char name[20];
}stu1,stu2;//无结构类型名,无法再次声明该结构类型的变量
😵注意:即使匿名结构体成员相同,在编译器看来也是不同类型的结构体!
struct
{
long num;
char name[20];
}x;
struct
{
long num;
char name[20];
}*p;
p=&x;
1.3类型重命名(较简便)
typedef struct Node
{
int data;
long num;
}Node,*pNode;//pNode-->struct Node*(结构体指针重命名)
int main()
{
Node n;
return 0;
}
2.结构体的自引用
👀引:数据结构:线性/树形/图
线性:顺序表 链表(数据域|指针域)
错误示例:
struct Node
{
int data;
struct Node n;
};
int main()
{
struct Node n;
return 0;
}
正确示例:
struct Node
{
int data;//数据
struct Node* next;//指针
};
!注意:错误自引用
typedef struct
{
int data;
Node* next;
}Node;
int main()
{
Node n;
return 0;
}
修正:
typedef struct Node
{
int data;
struct Node* next;
}Node;
int main()
{
Node n;
return 0;
}
3.结构变量的初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};
4.结构体传参(待补充)
二、😈结构体内存对齐
😇引例:
struct S1 //对齐数
{
char c1; //1
int i; //4
char c2; //1 //最大对齐数为4 4的倍数为12
}s1;
struct S2
{
char c1; //1
char c2; //1
int i; //4 //最大对齐数为4 4的倍数为8
}s2;
2.1结构体的对齐规则:
第一个成员在结构体变量偏移量为0的地址处。
其他成员变量在对齐数的整数倍的地址处。
对齐数 = 编译器默认对齐数 与 成员所占字节数 的较小值。
(VS中默认对齐数为8/Linux环境无默认对齐数,自身大小即为对齐数)
结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。(取最小值)
s1 | s2 | |
内存 | 内存 | 偏移量 |
c1 | c1 | 0 |
waste | c2 | 1 |
waste | waste | 2 |
waste | waste | 3 |
i | i | 4 |
i | i | 5 |
i | i | 6 |
i | i | 7 |
c2 | 8 | |
waste | 9 | |
waste | 10 | |
waste | 11 | |
12 |
2.1.1练习:
struct S3
{ //偏移量(所放位置)
double d; //0-7
char c; //8
int i; //12-15 //最大对齐数为8
}s3;//16
2.1.2拓展:offsetof-宏
用途:计算结构体成员相对于起始位置的偏移量
原型:
#include<stddef.h>
size_t offsetof(structName,member name);
struct S3
{ //偏移量(所放位置)
double d; //0-7
char c; //8
int i; //12-15 //最大对齐数为8
}s3;//16
printf("%u",offsetof(struct S3,d));//0 8 12
2.1.3嵌套结构体的内存对齐规则
嵌套的结构体对齐到自己最大对齐数的整数倍处,整体大小是所有最大对齐数(含嵌套结构体对齐数)的整数倍。
struct S3
{
double d;//8
char c;//1
int i;//4 //最大对齐数8
};
printf("%d\n", sizeof(struct S3));
struct S4
{
char c1;//1
struct S3 s3;//16
double d;//8 //所有对齐数最大为8
};
printf("%d\n", sizeof(struct S4));//32
偏移量 | 偏移量 | 偏移量 | 偏移量 | 偏移量 | |||||
c1 | 0 | waste | 7 | s3 | 14 | s3 | 21 | d | 28 |
waste | 1 | s3 | 8 | 15 | 22 | 29 | |||
waste | 2 | 9 | 16 | 23 | 30 | ||||
waste | 3 | 10 | 17 | d | 24 | 31 | |||
waste | 4 | 11 | 18 | 25 | 32 | ||||
waste | 5 | 12 | 19 | 26 | 33 | ||||
waste | 6 | 13 | 20 | 27 | 34 |
2.2内存对齐的存在原因
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
问。引例引用:比特鹏哥的图片讲解板书
😷:Sum:结构体的内存对齐是拿空间来换取时间的做法。
改进:设计结构体的时候,既要满足对齐,又要节省空间,让占用空间小的成员尽量集中在一起。
2.3默认对齐数的修改
对齐方式不合适时可更改默认对齐数
struct S 字节 默认对齐数 对齐数 偏移量
{
char c; //1 8 1 0
double d; //8 8 8 8-15
};//16
#pragma pack(4)//设置默认对齐数为4
struct S 字节 默认对齐数 对齐数 偏移量
{
char c; //1 4 1 0
double d; //8 4 4 4-11
};//12
#pragma pack()//取消,还原为默认
三、位段(位指二进制位)
3.1什么是位段
位段的声明和结构类似,不同之处:
1.位段的成员必须是 int、unsigned int 或signed int 。(char也可以)
位段的成员名后边有一个冒号和一个数字(数字意义:该成员占用比特位数)
例如:
struct A //A就是一个位段类型
{
int _a:2;
int _b:5;
int _c:10;
int _d:30;
};//47bit
printf("%d\n", sizeof(struct A));//8bite-->2*int
3.2位段的内存分配
1. 位段的成员可以是 int ,unsigned int ,signed int 或者 char (属于整形家族)类型
2. 位段的空间按照需要,以4个字节( int )或1个字节( char )方式来开辟。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植程序应避免使用位段。
struct S //剩余比特位
{ //_a是int-4byte-32bit
int _a:2; //30
int _b:5; //25
int _c:10; //10
int _d:30; //开辟一个新的4byte-不知_b是否占用原字节剩余的15bit
};
//16位机器-16bit
详细图解(图片来自比特课件)
3.3位段的跨平台问题
1. int 位段被当成有/无符号数不确定
2. 位段中最大位的数目不能确定。(16位机器16,32位机器32,写成27,在16位机器会出问题)
3. 位段中成员在内存中分配标准尚未定义(左-右||右-左)。
4. 含两个位段的结构,第一个位段剩余位无法容纳第二个位段成员,剩余的位不确定舍弃||利用。
Sum:位段可以节省空间,但是存在跨平台的问题