结构体
结构体基本知识
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
结构体的声明
struct tag
{
member-list;
}variable-list;
例子描述学生
struct Stu
{
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;
上面两个结构体都省略了标签tag
//虽然两个结构体内容一样但是
p = &x;
//会报出警告
结构体自引用
在结构体包含结构体本身
//代码1
struct Node
{
int data;
struct Node next;
};
//不可行的
//如果可以,那sizeof(struct Node)是多少?不可, 该结构体内存位置
正确的方法
struct Node
{
int data;
struct Node* next;
};
结构体重命名:
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};//结构体嵌套初始化
结构体内存对齐
结构体大小计算
热门考点:结构体内存对齐
首先对齐规则:
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
例题:
struct S3{
double d; char c; int i;};//16printf("%d\n", sizeof(struct S3));//16
分析:
- 首先偏移量为零时放置double占据8个字节
- 然后根据对齐数规定按从默认中抽取较小数char为1内存位置是1的整数倍.
- int类型4所以对齐数为4内存位置也是4的整数倍找到12的位置开始放置int类型
- 结构体整体大小是最大对齐数的整数倍对于这道题是8所以大小为16.
(PS: 第一个元素大小也算进最大对齐数行列如上图再加个char类型时就到了8的三倍及24)
为什么存在内存对齐
1.平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总的来说
结构体的内存对齐是拿空间来换取时间的做法。
修改默认对齐值
使用#pragma
这个预处理指令
#pragma pack(4)
即可将默认值改为4
#pragma pack()
即可还原为默认
位段
位段是通过结构体实现
(吐槽:位段是很扣的以比特位作为单位)
什么是位段
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字。
如
struct A{
int _a:2; int _b:5; int _c:10; int _d:30;};//8
int 是开辟空间的字节数4
:后的数字时变量所占比特位的多少
当第一个开辟空间够用是下一个不会开辟第二个空间
比如本题就只开辟了两个int的空间占8个字节. 32个比特位
位段的内存分配
- 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
- 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
例子
(因为位段不确定性我们演示VS2019的版本):
//一个例子struct S{ char a:3; char b:4; char c:5; char d:4;};int main(){ struct S s = {0}; s.a = 10; s.b = 12; s.c = 3; s.d = 4; return 0;}
位段的跨平台问题
- int 位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题。
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定的
位段可以很好的节约空间但是跨平台能力极差.我们使用位段时我们要先观察自己平台位段的规则.
位段的应用
对每个信息进行到比特的控制大大增加了空间的利用率
枚举
枚举的作用和#define类似都是将字母变成常量但是枚举相较于#define更有优点
- 增加代码的可读性和可维护性
- 和#define定义的标识符比较枚举有类型检查,更加严谨。
- 防止了命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量
枚举的使用
enum Color//颜色{ RED=1, GREEN=2, BLUE=4};int main(){ enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。 clr = 5; return 0;}
此代码在C编译器可以正常使用
但是在cpp编译器时会报错
当我们不对枚举类型内元素初始化时会自动以递增的形势进行赋值从0开始
下面对枚举不同赋值情况进行展示:
所有都没赋值:
则RED=0 GREEN =1 BLUE =2
对RED赋值
如
RED= 1 时
RED=1 GREEN =2 …
只对GREEN赋值:
如GREEN = 2;
RED =0 GREEN =2 BLUE = 3
我们枚举内元素均为常量不可赋值改变.
联合(共用体)
联合类型的定义
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
可以观察到他们的地址相同
union Un{
char c; int i;};//联合变量的定义int main(){ union Un un; //计算连个变量的大小 printf("%d\n", sizeof(un));//4 return 0;}
联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
联合体大小的计算
- 联合的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
比如
union Un1{
char c[5]; int i;};//大小为8union Un2{ short c[7]; int i;};//大小为16
第一个因为char a[5]是char类型他的对齐数为1 int类型的对齐数为4
原本他们共用同一份空间大小为5
但联合体大小要对齐到最大对齐数的整数倍及4的整数倍8.
第二个同理
short类型的对齐数为2 int类型对齐数为4
原本公用一块内存大小为14(7*2)
但要对齐到最大对齐数的整数倍及4的整数倍还要大于14所以为16
结尾
本章知识多为定义后面会以通讯录为练习.