定义及声明
结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
比如,描述一个学生:
struct Student//结构体名
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}(创建变量名);
特殊声明
在声明结构的时候,可以不完全的声明。
匿名结构体类型:
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
以上两结构在声明时省略了结构体标签
编译器会把上⾯的两个声明当成完全不同的两个类型,所以p与&x不是同一类型
匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使⽤⼀次。
结构的⾃引⽤ :
struct Node
{
int data;
struct Node next;
};
这不是合法指令,这样的结构体变量的⼤ ⼩就会⽆穷的⼤,是不合理的。
正确的写法:
struct Node
{
int data;
struct Node* next;
};
在结构体⾃引⽤使⽤的过程中,夹杂了typedef对匿名结构体类型重命名,也容易引⼊问题:
typedef struct
{
int data;
Node* next;
}Node;
这是不⾏的,因为Node是对前⾯的匿名结构体类型的重命名产⽣的,但是在匿名结构体内部提前使 ⽤Node类型来创建成员变量,这是不⾏的。
解决⽅案如下:定义结构体不要使⽤匿名结构体了
typedef struct Node
{
int data;
struct Node* next;
}Node;
结构体变量的创建和初始化
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
struct Point p3 = {x, y};//初始化:定义变量的同时赋初值。
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化
初始化格式也可以是这样:
struct Stu
{
char name[15];
int age;
};
struct Stu s = {.age=20, .name="zhangsan"};
结构成员访问操作符有两个⼀个是 . ,⼀个是 ->
结构体变量.成员变量名
结构体指针—>成员变量名
结构体内存的对齐
1. 结构体的第⼀个成员对⻬到相对结构体变量起始位置偏移量为0的地址处
2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。 对⻬数 = 编译器默认的⼀个对⻬数 与 该成员变量⼤⼩的较⼩值。 - VS中默认的值为8 - Linux中没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
3. 结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构 体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
比如:
struct S1
{
char c1;
int i;
char c2;
};
c1(字符类型)从0开始占了一字节,i(整形类型)从4的倍数4开始占四字节,c2(字符类型)从8开始占一字节。总计大小为最大对齐数4的倍数8字节。
所以想节省空间就要改变成员创建的顺序。
修改默认对齐数
#pragma 这个预处理指令,可以改变编译器的默认对⻬数。
结构体在对⻬⽅式不合适的时候,我们可以⾃⼰更改默认对⻬数。
#pragma pack(1)//设置默认对⻬数为1
#pragma pack()//取消设置的默认对⻬数,还原为默认
结构体传参
因为函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下 降。
所以结构体传参的时候,要传结构体的地址。
位段
1.位段的成员可以是 int unsigned int signed int 或者是 char 等类型
2.位段的空间上是按照需要以4个字节( int )或者1个字节( char )的⽅式来开辟的。
3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段。
冒号后面跟的是给予成员的比特位数,这样大大节省了空间。
ps:位段的⼏个成员共有同⼀个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位 置处是没有地址的。内存中每个字节分配⼀个地址,⼀个字节内部的bit位是没有地址的。 所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输⼊ 放在⼀个变量中,然后赋值给位段的成员。