结构体,枚举,联合都是自定义类型,三种类型在内存中的存储大致类似但也有不同,下面我们来详细了解三种类型的定义、特点以及在内存中的存储方式和计算大小的方式,其中也含有位段的详解
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量
数组是一组相同类型元素的集合
数据结构:描述的是数据在内存中存储和组织结构
struct tag
{
member - list;
}variable - list;
也就是
struct 结构体名
{
成员列表
}变量名列表;
比如我们描述一个学生,定义一个学生结构体,里面包含学号、姓名、性别、年龄等
struct Stu
{
char num[20];//学号
char name[20];//名字
char sex[5];//性别
int age;//年龄
}; //分号不能丢
依旧是上面的结构体,想要在学生属性中多加一个出生日期
struct Date
{
int month;
int day;
int year;
};
struct Stu
{
char num[20];
char name[20];
char sex[5];
int age;
struct Date birthday;
};
深入思考是不是还可以自己引用自己
struct Node
{
int data;//存放数据-数据域
struct Node* n;//自引用
//存放下一个节点的地址-指针域
};
int main()
{
printf("%zd\n", sizeof(struct Node));
return 0;
}
我们会定义变量了,哪如何去用
struct Point
{
int x;
int y;
}p1 = { 1, 2 };//声明类型的同时定义变量p1
struct Point p3 = { 4,5 };//初始化:定义变量的同时赋初值。
struct Stu //类型声明
{
char name[15];
int age;
};
struct Node
{
int data;
struct Point p;
struct Node* next;//自引用
};
int main()
{
int a = 10;
int b = 3;
struct Point p2 = { a,b };
struct Stu s = { "zhangsan",20 };
struct Stu s2 = { .age = 18,.name = "如花" };
struct Node n = { 100,{20,21},NULL };//结构体嵌套初始化
printf("%s %d\n", s.name, s.age);
printf("%s %d\n", s2.name, s.age);
//printf("%d x = %d")
return 0;
}
想一下下面的代码运行结果是多少
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
struct S3
{
double d;
char c;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
printf("%d\n", sizeof(struct S3));
return 0;
}
结构体在内存中的存储遵循对齐规则
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
而结构体总大小为最大对齐数的整数倍,这里是int类型最大对齐数4,所以取4的整数倍,从0 ~ 8共9个字节,所以取12
结构体总大小为最大对齐数的整数倍,这里是int类型最大对齐数4,所以取4的整数倍,从0 ~ 7共8个字节,所以取8
在设计结构体的时候,我们既要满足对齐,又要节省空间,就可以让占用空间小的成员尽量集中在一起。
我们在结构体传参时,可以传值调用,也可以传址调用
struct S
{
int data[1000];
int num;
};
void print1(struct S t)
{
printf("%d %d %d %d\n", t.data[0], t.data[1], t.data[2], t.num);
}
void print2(const struct S* ps)
{
printf("%d %d %d %d\n", ps->data[0], ps->data[1], ps->data[2], ps->num);
}
int main()
{
struct S s = { {1,2,3},100 };
print1(s);//传值调用
print2(&s);//传址调用
return 0;
}
运行结果:
可以看到结果是相同的,但是我们平时传参时,首选print2函数形式传参。
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降
位段又称为位域,其定义借助于结构体,即以二进制位为单位定义结构体成员所占存储空间。
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是
int、unsigned int 或signed int 。
2.位段的成员名后边有一个冒号和一个数字。
struct A
{
int _a : 2;//—— _a占2个bit位的空间
int _b : 5;//—— _b占5个比特位
int _c : 10;
int _d : 30;
};
int main()
{
printf("%d\n", sizeof(struct A));
return 0;
}
上诉代码中,定义的时int类型,所以是以四个字节的方式来开辟的,所以先开辟了32个bit位
a占用2个bit位,剩余30个bit位 b占用5个bit位,剩余25个bit位
c占用10个bit位,此时还剩15个bit位,并不足以给d用,所以在这里重新开辟四个字节——也就是32个bit位给d用
但是要注意的是,并没有紧接着c后面来存储d,而是直接在存储在新开辟的32个bit位的内存中
- int 位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题。
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定的。
枚举顾名思义就是一一列举。
把可能的取值一一列举。
比如我们现实生活中:
一周的星期一到星期日是有限的7天,可以一一列举。
性别有:男、女、保密,也可以一一列举。
月份有12个月,也可以一一列举
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
enum Sex//性别
{
MALE,
FEMALE,
SECRET
};
enum Color//颜色
{
RED,
GREEN,
BLUE
};
这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值
enum Sex
{
//枚举的可能取值
MALE,//枚举常量
FEMALE,
SECRET
};
enum Color
{
RED,
GREEN,
BLUE
};
int main()
{
printf("%d\n", MALE);
printf("%d\n", FEMALE);
return 0;
}
思考下面的代码结果
enum ENUM_A
{
X1,
Y1,
Z1 = 255,
A1,
B1,
};
enum ENUM_A enumA = Y1;
enum ENUM_A enumB = B1;
int main()
{
printf("%d %d\n", enumA, enumB);
return 0;
}
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)
union Un
{
char c;
int i;
};
union Un
{
char c;
int i;
};
int main()
{
union Un un;
printf("%d\n", sizeof(un));
return 0;
}
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联
合至少得有能力保存最大的那个成员)
比如
union Un
{
int i;
char c;
};
union Un un;
int main()
{
// 下面输出的结果是一样的吗?
printf("%d\n", &(un.i));
printf("%d\n", &(un.c));
return 0;
}