目录
🚀结构体
🔥结构体类型的声明
🔥结构的自引用
🔥结构体变量的定义和初始化
🔥结构体内存对齐
🔥结构体传参
🔥结构体实现位段(位段的填充&可移植性)
🚀枚举
🔥枚举类型的定义
🔥枚举的优点
🔥枚举的使用
🚀联合(共用体)
🔥联合联合类型的定义
🔥联合的特点
🔥联合大小的计算
🚀结构体
🔥结构体类型的声明
结构是一些值的集合,这些值成为成员变量。结构的每个成员可以是不同类型的变量。
结构的声明:
struct Stu
{
char name[20];//姓名
int age;//年龄
char sex[5];//性别
char id[20];//学号
};//分号不能省略//struct——>结构体关键字;Stu——>结构体标签这里表示的是学生属性
特殊声明:
//匿名结构体类型
struct
{
int a;
char b;
}x;//匿名结构体类型只能使用一次
struct
{
int a;
char b;
}a[20],* p;//p指向的是这个结构体指针的地址
int main()
{
p = &x;
return 0;
} //编译器会把上面两个声明当成两个完全不同的类型
🔥结构的自引用
在结构体中包含一个类型为该结构本身的成员
struct Node
{
int data;
struct Node next;
};
这种类型的结构自引用是非法的,成员next中又会包含一个struct Node的结构,如此递归下去永无止境。在计算sizeof(struct Node)时无法求出。合法声明:
struct Node
{
int data;
struct Node* next;
};//也就是说线性结构中链表,每个节点包括了这个节点的数据和指向下一节点的地址的指针的信息。即数据域和指针域。
当使用typedef类型定义和自引用时要注意下面这种陷阱:
typedef struct
{
int data;
Node* next
}Node;//匿名结构体类型,typedef重定义一个名字Node
这种写法是非法的,因为类型名知道整个定义结束才遇到,在结构体内部的Node是未定义的
//解决方案:
typedef struct Node
{
int data;
struct Node* next
}Node;
🔥结构体变量的定义和初始化
struct Point
{
int x;
int y;
}s1; //声明类型的同时定义变量s1
struct Point s2;//定义结构体变量s2
//结构体嵌套初始化
struct Score
{
int n;
char ch
};
struct Stu
{
char name[20];
int age;
struct Score s
};
int main()
{
struct Stu s1 = { "zhangsan",20,{20,'q'} };
}
🔥结构体内存对齐
如何来计算结构体的大小
#include<stdio.h>
struct S1
{
char a;
int i;
char b;
};
struct S2
{
char a;
char b;
int i;
};
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
明明只是调换了一下位置,为什么所占字节大小会不同呢?
结构体的对齐规则:
1、第一个成员在与结构体变量偏移量为0的地址处
2、其他成员变量要对齐到某个数字(对其数)的整数倍的地址处。
对其数=编译器默认的一个对其数与该成员大小的较小值。
vs中默认的值是8
3、结构体总体大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4、如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
为什么会存在结构体内存?
1、平台原因:
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则会抛出硬件异常。
2、性能原因:
数据结构(尤其是栈)应尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存访问仅需要一次访问。
结构体的内存对齐是拿空间来换取时间的做法。
设计结构体的时候,我们既要满足对齐又要满足节省空间,尽量把小的类型集中在前面,从而减少空间的浪费
修改默认对其数
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char a;
int i;
char b;
};
#pragma pack()//取消设置的默认对齐数,还原为默认#pragma pack(1)//设置默认对齐数为1
struct S2
{
char a;
int i;
char b;
};
建议不要随意修改,当我们不去追求效率,而是追求空间浪费最少时可以考虑修改默认对齐数。
🔥结构体传参
#include<stdio.h>
struct S
{
int data[100];
int num;
};
void print1(struct S ss)
{
int i = 0;
for (i = 0; i < 2; i++)
{
printf("%d ", ss.data[i]);
}
printf("%d", ss.num);
}
void print2(struct S* ss)
{
int i = 0;
for (i = 0; i < 2; i++)
{
printf("%d ", ss->data[i]);
}
printf("%d", ss->num);
}
int main()
{
struct S s = { {1,2},100 };
print1(s);//传值调用
print2(&s);//传址调用
return 0;
}
在进行结构体传参时我们传地址是更好的,这是因为函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销。如果传递一个结构体对象的时候,结构以过大,参数压栈的系统开销比较大,会导致性能的下降。
🔥结构体实现位段(位段的填充&可移植性)
位段(位指的是比特位)的声明和结构是类似的,有两个不同:
1、位段的成员只能是整型:int、unsigned int,signed int或者char类型的
2、位段的成员后面有一个冒号和数字
#include<stdio.h>
struct A
{
int a : 2;
int b : 5;
int c : 10;
int d : 30;
};
位段的内存分配
1、位段成员可以是int 、unsigned int、signed int或者char类型
2、位段的空间上是按照需要以4(int)个字节或 1(char)个字节的方式来开辟的
3、位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应避免使用位段
//举个栗子:
#include<stdio.h>
struct S
{
//先开辟一个字节的空间,8个比特位
char a : 3;
//用了3个还剩5个比特位
char b : 4;
//用了4个还剩1个比特位
char c : 5;
//不够用了,再开辟一个字节,8个比特位,用了5个,剩下3个
char d : 4;
//又不够用了,再开辟一个字节,8个比特位用了4个,剩下4个
};
int main()
{
struct S s = { 0 };
printf("%d\n", sizeof(struct S));//3
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、当一个结构中包含两个位段,第二个位段的成员比较大,无法容纳容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的
总结:与结构相比,位段可以达到同样效果,但是可以很好的节省空间,但是有跨平台的问题存在。 当然位段在计算机网络中有其独有的作用,能节省不少空间浪费(数据越少,状态越好),从而达到网络环境较优的状态
🚀枚举
枚举顾名思义就是一一列举,比如:一年12个月可以一一列举、一周7天可以一一列举。
🔥枚举类型的定义
enum Day//星期
{
Mon,
Tues,
Wed
Thur,
Fri,
Sat,
Sun
};enum Sex//性别
{
MALE,
FEMALE,
SECRET
};//与结构体是非常相似的,但其内部是用逗号分隔开的,且内部只包含符号
{ }里面的内容是枚举类型的可能取值,也叫枚举常量。
这些可能取值都是有值的,默认从0开始,一次递增一,当然在定义的时候也可以赋初值。
enum Color//颜色
{
RED = 1,
GREEN=2,
BLUE=3
};
🔥枚举的优点
我们可以使用 #define 定义常量,为什么要用枚举呢?
1、增加代码的可读性和可维护性
2、和#define定义的标识符比较枚举有类型检查,更加严谨
3、防止命名污染(封装)
4、便于调试
5、使用方便,一次可以定义多个常量
🔥枚举的使用
enum Color//颜色
{
RED = 1,
GREEN=2,
BLUE=4
};
int main()
{
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异
clr = 5;
return 0;
}
🚀联合(共用体)
🔥联合联合类型的定义
联合也是一种特殊的自定义类型,这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以联合也叫共用体)
//举例:
union Un
{
int a;
char c;
};
int main()
{
union Un u;
printf("%d\n", sizeof(u));//4,说明共用了一份空间
return 0;
}
从这里我们就能看出来a与c共用一份空间
🔥联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合体至少得有能力保存最大的那个成员)
union Un
{
int a;
char c;
};
int main()
{
union Un u;
u.a = 0x11223344;
u.c = 0x55;
printf("%x\n", u.a);
return 0;
}
判断机器是大端存储还是小端存储(用联合的方法)
int check_sys()
{
union Un
{
int a;
char b;
}u;
u.a = 1;
return u.b;
}
int main()
{
int ret = check_sys();
if (ret == 1)
{
printf("小端\n");
}
else
printf("大端\n");
return 0;
}
🔥联合大小的计算
·联合的大小至少是最大成员的大小。
·当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。
union Un1
{
char c[5];
int i;
};//本来应该是5,最大对齐数为4,所以大小为4的倍数即8