目录
结构体:
结构体类型的声明
结构体的自引用
结构体变量的定义和初始化
结构体内存对齐
结构体传参
结构体实现位段(位段的填充&可移植性)
枚举:
枚举类型的定义
枚举的优点
枚举的使用
联合:
联合类型的定义
联合的特点
联合大小的计算
结构体
结构体的声明:
结构体是一些值集合,这些值称为成员变量。结构体每个成员可以是不同类型的变量
数组是一组相同类型的元素的集合
结构体是一种复杂类型
复杂类型:比如书籍:书名,作者,版号,定价,出版社
比如学生:姓名,名字,性别,地址,学校,班级......
struct book {
char name[20];
int age;
char id[20];
}b,b4,b5,b6;
//结构体可以引用结构体
struct a {
struct book b;
int id;
};
//匿名结构体类型
//匿名结构体只能用这一次,即声明一次s
struct
{
char name[20];
int age;
char id[20];
}s;
//匿名结构体类型的指针
struct
{
char name[20];
int age;
char id[20];
}*ps;
int main()
{
ps = &s;
struct book b1;
struct book b2;
struct book b3;
return 0;
}
b1,b2,b3和b4,b5,b6类型是完全一样的
但是,b4,b5,b6是全局变量
结构体自引用:
数据结构:数据在内存中存储的结构
顺序存储结构(数组):1,2,3,4,5
链表:
单向链表也叫做单链表,是链表中最简单的一种形式,它的每个节点包含两个域,一个信息域(元素域)和一个链接域。这个链接指向链表中的下一个节点,而最后一个节点的链接域则指向一个空值。
节点:就是链表的数据元素
顺序和链表统称为线性数据结构
树形数据结构:二叉树
数据域+指针域(存放下一个节点的地址)
正确的自引用方式:不是包含同类型的结构体变量,而是包含同类型结构体的指针
struct node {
int data;
struct node* next;
};
结构体变量的定义和初始化
struct b {
int arr[10];
char ch[20];
};
struct c {
double d;
struct b b;
char c;
};
int main()
{
struct b n = { 0 };
struct b n1 = { 0 };
struct c c1 = { 3.14,{{1,2},"hello"},'w' };
printf("%lf\n%s\n%c\n", c1.d, c1.b.ch, c1.c);
return 0;
}
结构体内存对齐:
第一个问题:
一个结构体占多大内存空间呢?
struct s1 {
int i1; //4
char c1; //1
};
struct s2 {
char c1;
int i;
char c2;
};
struct s3 {
int i;
char c1;
char c2;
};
struct s4 {
int i; //偏移量0 = 0+1-1
char c2; //偏移量4 = 1*4+1 -1
double c1; //偏移量8 = 1*8 +1-1 (第9个地址,偏移量为8)
int c3; //偏移量16 = 2*8 +1 -1
int c4; //偏移量20 = 16+4+1-1
}; //总大小:偏移量+1 == 占用了多少个字节
//再+(最后一个成员变量的大小-1)因为这个成员变量的起始地址已经被计算进去了
struct s5 {
int i;
struct s4 s;
char c2;
};
int main()
{
struct s1 ss1 = { 0 };
struct s2 ss2 = { 0 };
struct s3 ss3 = { 0 };
struct s4 ss4 = { 1,0,0,1,1 };
struct s5 ss5 = { 0 };
printf("%d\n", sizeof(ss1));
printf("%d\n", sizeof(ss2));
printf("%d\n", sizeof(ss3));
printf("%d\n", sizeof(ss4));
printf("%d\n", sizeof(ss5));
return 0;
}
结构体对齐规则:
1,第一个成员在与结构体变量偏移量为0的地址处。
2,其他成员变量要对齐到(某个数字(对齐数)的整数倍地址偏移量-1)处。 //因为结构体是从0偏移量开始的,所以,到3偏移量,就已经是第四个地址了...所以要用对齐数倍数-1
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值
-- vs中默认为8
-- linux中没有默认对齐数的概念
3,结构体总大小为结构体所有成员中的最大对齐数(每一个变量成员都有一个对齐数)的整数倍。
4,如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
为什么存在内存对齐?
大部份资料是如是说的:
1,平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;
某些平台只能在某些地址处取特定类型的数据否则抛出硬件异常
2,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐地内存,处理器需要做两次内存访问;而对齐地内存仅访问一次
总体来说:结构体地内存对齐是拿空间来换取时间的做法
修改默认对齐参数
当你觉得对齐数不合适的时候,可以修改默认对齐数
把默认对齐数改为2;
#pragma pack(2)
struct s1 {
char c1;
int i;
char c2;
};
//offsetof宏可以计算结构体的成员变量距离起始地址的偏移量
int main()
{
printf("%d\n", sizeof(struct s1));
printf("%d\n", offsetof(struct s1,c1));
printf("%d\n", offsetof(struct s1,i));
printf("%d\n", offsetof(struct s1,c2));
return 0;
}
结构体传参:
struct s {
int a[10];
int num;
};
struct s s1 = { {1,2,3},1000 };
void print1(struct s s1)
{
printf("%d\n", s1.num);
}
void print2(struct s *s1)
{
printf("%d\n", s1->num);
}
int main()
{
print1(s1);
print2(&s1);
return 0;
}
结构体传参时,结构体占用的空间一般很大
值传方式,需要开辟一个空间来存储这个值,浪费很多空间
传址方式,节省空间,效率更高
函数传参时,参数是需要压栈的,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降
结论:结构体传参的时候,需要传结构体的地址
结构体位段:
什么是位段:
位段的声明和结构体是类似的,有了两个不同:
1,位段的成员必须是int,unsigned int或signed int
2,位段的成员名后边有一个冒号和一个数字
比如:
struct a {
int a : 1; //a成员占1个bit位
int b : 2; //b成员占2个bit位
int c : 3; //c成员占3个bit位
};
int main()
{
printf("%d\n", sizeof(struct a));
return 0;
}
性别:男,女,保密
位段一定程度上是在帮我们节省空间
位段的内存分配:
1,位段的成员可以是int,unsigned int,signed int或者是char(属于整型家族)类型
2,位段的空间上是按照需要4个字节(int)或者1个字节(char)的方式来开辟的
3,位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
int main()
{
int a = 0x12345678;
struct s1 {
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
struct s1 s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
return 0;
}
这个位段规则仅仅适应于vs
一个字节(整形)内部的地址,先使用低地址位的数据,再使用高地址为的数据,从右向左使用
当一块字节空间内剩余的空间不足以下一个空间使用,那么会跳到下一个字节
位段的跨平台问题:
1,int 位段被当成有符号数还是无符号数是不确定的。
2,位段中最大的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题)
3,位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4,当一个结构包含两个位段,第二个位段成员比较大,无法容纳第一个位段的剩余位时,是舍弃剩余的位还是利用,这是不确定的。
总结:跟结构相比比,位段可以达成同样的效果,但是可以很好地节省空间,但是有跨平台的问题存在。
枚举
枚举 - 顾名思义就是一一列举
星期,性别,月份都是有限的数据,都可以一一列举
定于:
enum day {
mon,
tue,
wed,
thu,
fri,
sat,
sun,
};
enum color {
red,
blue,
green,
};
enum color1 {
red1 = -1, //从第一个开始,默认+1;
blue1 ,
green1 = 8,
};
int main()
{
enum color c = blue;
//enum color c =1 ; //这样不可以,在c++编译器会报错
printf("%d\n",++c);
printf("%d\n",++c);
printf("%d\n", red);
printf("%d\n", blue);
printf("%d\n", green);
printf("%d\n", red1);
printf("%d\n", blue1);
printf("%d\n", green1);
return 0;
}
枚举和结构体区别:
枚举类型括号内是可能取值,是常量
结构体括号内是结构体成员,是变量
枚举的优点:
1,增加代码的可读性和可维护性
2,和#define定义的标识符比,枚举有类型检查,更加严谨
3,可以避免命名污染(封装)
4,便于调试
5,使用方便,一次定义多个常量
编译 -->链接-->exe-->调试
编译:预编译,编译,汇编
define在编译期间就运行了;所以不方便调试
枚举是一种自定义类型
枚举大小就是一个整形的大小
联合
联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列成员,特征是这些成员公用同一块空间(所以联合体也叫共用体)
这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)
union un {
char c;
int i;
};
int main()
{
union un u = {10}; //联合体初始化
printf("%d\n", sizeof(u));
printf("%p\n", &u);
printf("%p\n", &u.c);
printf("%p\n", &u.i);
return 0;
}
输出:
4
000000a7eaaff684
000000a7eaaff684
000000a7eaaff684
联合体也叫共用体
因为共用地址,所以在同一时间只能使用联合体中的一个成员
联合体和结构体的区别:
判断当前计算机的大小端存储
vs使用小端存储
小段:低位数据存储在低位地址,高位存高位地址
大段:低位数据存储在高位地址,高位存低位地址
做一个计算大小端的函数
int check_sys()
{
int a = 1;
if ((*(char*)&a) == 1)
{
return 1;
}
else
return 0;
}
使用联合体的实现方式
int check_sys()
{
union un {
char c;
int i;
}u;
u.i = 1;
return u.c;
//返回1,就是小端,返回0就是大端
}
int main()
{
//int a = 1;
//&a是低地址,那么低地址存低位(1)就是小端
//printf("%d\n", *((char*)&a));
int ret = check_sys();
if (ret == 1)
{
printf("小端");
}
else
printf("大端");
return 0;
}
联合体大小的计算
联合的大小至少是最大成员的大小
当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍
比如:
union un {
char c[5]; //5
int i; //4
};
union un1 {
char c1[5]; //5
char c2; //4
}u1;
int main()
{
union un u;
printf("%d\n", sizeof(u));
printf("%d\n", sizeof(u1));
return 0;
}
输出为:8
因为,联合体内部变量的最大大小为5;但是最大对齐数为4,所以应该是最大对齐数整数倍4*2 =8;
注意:联合体没有默认对齐数