关键字 自定义标识符
{
类型名1 成员名表1;
......
类型名n 成员名表n;
};
例:
struct student
{
long num;
char name[20];
float score;
};
关键字 自定义标识符 结构变量
直接声明
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;
typedef struct Node
{
int data;
long num;
}Node,*pNode;//pNode-->struct Node*(结构体指针重命名)
int main()
{
Node n;
return 0;
}
引:数据结构:线性/树形/图
线性:顺序表 链表(数据域|指针域)
错误示例:
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;
}
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};
引例:
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;
第一个成员在结构体变量偏移量为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 |
struct S3
{ //偏移量(所放位置)
double d; //0-7
char c; //8
int i; //12-15 //最大对齐数为8
}s3;//16
用途:计算结构体成员相对于起始位置的偏移量
原型:
#include
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
嵌套的结构体对齐到自己最大对齐数的整数倍处,整体大小是所有最大对齐数(含嵌套结构体对齐数)的整数倍。
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 |
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访
问。引例引用:比特鹏哥的图片讲解板书
:Sum:结构体的内存对齐是拿空间来换取时间的做法。
改进:设计结构体的时候,既要满足对齐,又要节省空间,让占用空间小的成员尽量集中在一起。
对齐方式不合适时可更改默认对齐数
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()//取消,还原为默认
位段的声明和结构类似,不同之处:
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
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
详细图解(图片来自比特课件)
1. int 位段被当成有/无符号数不确定
2. 位段中最大位的数目不能确定。(16位机器16,32位机器32,写成27,在16位机器会出问题)
3. 位段中成员在内存中分配标准尚未定义(左-右||右-左)。
4. 含两个位段的结构,第一个位段剩余位无法容纳第二个位段成员,剩余的位不确定舍弃||利用。
Sum:位段可以节省空间,但是存在跨平台的问题