一、结构体类型的声明
1、结构体的声明
结构体的声明:
struct tag
{member-list
};
2、结构体变量的创建和初始化
创建一个简单的学生信息结构体
struct student
{char name[20];int age;int score;
};
结构体默认初始化的顺序是结构体中创建变量的顺序。
初始化结构体:
struct student
{char name[20];int age;int score;
}s1, s2 = {"李华",18,88}; //第一种初始化struct student s3 = { "小明",20,100 }; //第二种初始化int main()
{struct student s4 = { "张三",15,70 }; //第三种初始化struct student s5 = { .score = 99,.name = "小红",.age = 22 }; //第四种初始化return 0;
}
3、特殊声明
匿名结构体
struct
{int a;char b;float c;
}x,*p;
匿名结构体只允许使用一次,p=&a是非法请求
4、结构体的自引用
结构体是可以进行自引用的
错误的自引用:
struct stu
{int a;struct stu next;
};
正确的自引用:
struct stu
{int a;struct stu* next;
};
5、typedef重命名结构体
typedef struct Node
{int data;struct Node* next;
}Node; //结构体命名为Node
二、结构体内存对⻬
1、对齐规则
掌握结构体的对齐规则:
- 结构体中第一个变量首对齐地址是偏移量为0的地址处。
- 其他成员的对齐变量是类型的整数倍数的地址处。vs默认对齐量为8,linux中gcc没用对齐数,对齐数就是自身大小
- 结构体的总大小为最大对齐数
- 对于嵌套结构体,嵌套结构中对齐量是结构体中最大的一个变量整数倍数地址处,而结束时是嵌套结构体的整数倍数
如计算int 对齐数 4 8 4 int类型的大小对比默认值大小,谁小选谁是对齐数
//练习1
struct S1
{char c1;int i;char c2;
};
printf("%d\n", sizeof(struct S1));
//练习2
struct S2
{char c1;char c2;int i;
};
printf("%d\n", sizeof(struct S2));
//练习3
struct S3
{double d;char c;int i;
};
printf("%d\n", sizeof(struct S3));
//练习4-结构体嵌套问题
struct S4
{char c1;struct S3 s3;double d;
};
printf("%d\n", sizeof(struct S4));
在以上例题中,我们发现结构体中开辟的空间有许多空间被浪费了,所以在创建类型时,需要合理的排序
在例题1与例题2中,创建的类型是一样的,就因为类型创建的顺序不一样,导致占用内存不一样。
尽量使变量小的类型排在前面
2、为什么存在内存对齐
1、平台移植问题:不是所有的硬件平台都能访问任意地址上的任意数据
2、性能问题:如果没用地址对齐,处理器需要作两次的内存访问,而对齐的内存只需要访问一次
总结来说:拿空间换时间
3、修改默认对齐数
通过使用#pragma这个预处理指令,可以改变编译器的默认对齐数
#pragma pack(4)//设置对齐数变为4
struct stu
{char a;int b;char d;double c;
};
#pragma pack()//取消设置的对齐数,还原默认
int main()
{printf("%zd\n", sizeof(struct stu));
}
三、结构体传参
struct stu
{char name[10];int age;
};struct stu s1 = { "xiaoming",28 };void print1(struct stu s1)
{printf("%d,%s\n", s1.age, s1.name);
}void print2(struct stu* s1)
{printf("%d,%s\n", s1->age, s1->name);
}
int main()
{print1(s1); //传值调用print2(&s1); //传址调用return 0;
}
输出:
结构体可以使用传值也可以使用传址,优先选用传址调用,这样不需要开辟新的空间,可以大大提高代码的运行效率。
四、结构体实现位段
1、什么是位段
位端可以限制结构体中创建变量占用内存的大小
- 使用位段时,成名必须是unsigend int,int,sigent int,在c99中其他成员可以使用位段
- 位段的创建在成员名后面加上一个冒号跟一个数字。
比如:
struct stu
{int _a : 4;int _b : 2;int _c : 1;int _d : 30;
};
2、位段的内存分配
- 位段的成员可以是int,unsigent int ,signet int,char类型
- 位段上的空间是以四个字节跟一个字节进行开辟空间的
- 位段无法跨平台使用
struct stu
{int _a : 4;int _b : 10;int _c : 1;int _d : 20;
};int main()
{printf("%zd\n", sizeof(struct stu));return;
}
输出:
如果没用位段,那么4个int类型的字节大小位12
位段是如何开辟空间的,如下:
3、位段的跨平台问题
1、int位段是被当成有符号位还是无符号位是不确定的
2、位段中最大数是不确定的(有16位机器,如果设置段位超过16比特,那么就会出问题,32位机器同意也是)
3、内存是从左到右分配,还是从右到左分配是不确定的(vs是从右到左分配)
4、当一个结构体包含两个位段时,第二个位段成员比较大,无法容纳第一个尾端剩余空间的时候,是否会舍弃剩余空间,这是不确定的。
总结:位段可以很好的节约空间,但存在跨平台问题
4、位段的应⽤
下图是⽹络协议中,IP数据报的格式,我们可以看到其中很多的属性只需要⼏个bit位就能描述,这⾥使⽤位段,能够实现想要的效果,也节省了空间,这样⽹络传输的数据报⼤⼩也会较⼩⼀些,对⽹络的畅通是有帮助的。
5、位段使⽤的注意事项
无法获取比特位的地址。
无法直接使用scanf来输入位段,但是可以赋值位段
struct stu
{int _a : 4;int _b : 10;int _c : 1;int _d : 20;
};int main()
{struct stu s = { 0 };int n = 0;scanf("%d", &n);s._a = n;return;
}