一、结构体
1、结构体
struct Stu
{
int age;
char name[20];
}stu1,stu2;
2、匿名结构体
struct
{
char color[10];
int wight;
}num1,num2;
3、结构体的自引用
struct Node
{
int val;
struct Node* next;
}
typedef struct Node
{
int val;
struct Node* next;
}Node;
Node node1;
node1.val = 12;
node1.next = NULL;
4、结构体内存对齐
struct S1
{
char c1; //1
int i; //4
char c2; //1
};
//sizeof(struct Stu) == 12字节
#include<stdio.h>
#include<stddef.h>
struct S2
{
char c1; //1
char c2; //1
int i; //4
};
//sizeof(struct Stu) == 8字节
int main()
{
printf("%d\n",offsetof(struct S2,c1));//0
printf("%d\n",offsetof(struct S2,i));//4
printf("%d\n",offsetof(struct S2,c2));//8
}
对齐规则:
1、第一个成员在结构体变量为0的地址处
2、其他成员变量对齐某个数字(对齐数)的整数倍地址处。
对齐数 = min (编译器默认对齐数,成员变量大小);
vs 编译器默认值为8,其他编译器没有默认对齐数,所以其他编译器会的对齐数就为成员变量本身大小。
3、结构体总体大小等于成员变量最大对齐数(含嵌套结构体的对齐数)的整数倍。
4、如果是嵌套结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
s1的结构体大小为12,s2的结构体大小为8;
结构体S1和S2中元素类型一样,数量一样,只是元素顺序不一样,导致在进行内存对齐时,浪费了很大的空间,因此在定义结构体时,应尽量将内存小的元素放在一起,依此来避免浪费内存空间
宏:offsetof
#include <stddef.h>
offsetof(tupe,member);
编辑器里面只有VS又默认对齐数8,其他编辑器没有默认对齐数
5、为什么需要内存对齐
(1)、平台原因(移植原因);
不是所有平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则会抛出硬件异常。
(2)、性能原因
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问没有内存对齐的内存,处理器需要做两次内存访问,而访问内存对齐的数据,只需要一次访问就能够拿到完整的数据。
内存对齐虽然会浪费一些内存资源,但是会提升内存的读写速度,典型的用空间换时间。
6、修改编辑器的默认对齐数
struct T
{
char c;
double d;
}
#pragma pack(4) //将编译器默认对齐数修改为4
struct S
{
char c;
double d;
}
#pragma pack() //将编译器默认对齐数恢复为默认值8
//通过以上操作,结构体S的默认对齐数为4,而结构体T的默认对齐是8
int main()
{
return 0;
}
7、结构体传参
结构体传参时,尽量传递结构体地址,减少内存开销和时间消耗
struct T
{
int arr[100];
int num;
}
//传参结构体元素
void print1(struct T t)
{
int i = 0;
for(i = 0;i < 3;i++){
printf("%d ",t.arr[i]);
}
printf("%d",t.num);
}
//传参结构体地址
void print2(struct T* pt)
{
int i = 0;
for(i = 0;i < 3;i++){
printf("%d ",pt->arr[i]);
}
printf("%d",pt->num);
}
结构体传参:
如果传入的是结构体对象,那么形参在接收结构体对象时,就会预先开辟一份空间去保存该参数
而传结构体对象地址时,只需要一个地址的空间,就可以接受参数。
8、位段
8.1、什么是位段
位段是通过结构体实现的;
①、位段的成员必须是整形家族的成员,int,char,unsigned int,signed int。
②、位段的成员名后边有一个冒号和一个数字,
//结构体实现位段的格式
struct T
{
int a:2; //表示给a字段分配2个bit位
int b:5; //表示给b字段分配5个bit位
int c:15: //表示给c字段分配15个bit位
int c:30; //表示给d字段分配30个bit位
}
a,b,c,d是四个整形数据,一个整形数据4个字节,四个整形数据,占用8个字节,当设置位段后,a,b,c,d共占用8个字节(与平台和编译器有关),大大节省了空间。
8.2、位段的内存分配
①、位段的成员必须是整形家族,unsigned int,signed int,int,char
②、位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的
③、位段涉及很多不确定的因素,位段是不跨平台的,注重可移植的程序应避免试用位段。
举例:
//结构体实现位段的格式
struct T
{
char a:2; //表示给a字段分配2个bit位
char b:3; //表示给b字段分配3个bit位
char c:5: //表示给c字段分配5个bit位
char d:4; //表示给d字段分配4个bit位
}
int main()
{
struct T t = {0};
t.a = 1;
t.b = 8;
t.c = 7;
t.d = 8;
8.3、位段跨平台问题
①、int字段被当成有符号数还是无符号数是不确定的
②、位段中最大位的数目不能确定,(16位机器最大位16位,32位机器最大32,写成30在16位机器会出现问题)
③、位段中的成员在内存中从左到右分配还是从右到左分配标准尚未定义。
④、当一个结构体包含两个位段,第二个位段成员比较大,无法容纳第一个剩余的位时,时舍弃剩余的位还是利用,这是不确定的。
8.4、位段的应用
网络报文利用位段设置报文信息
二、枚举
enum Day
{
Mon, //修改起始默认值为10的定义:Mon = 10;
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
}
int main()
{
enum Day day = Fri;
return 0;
}
枚举常量的默认取值是0开始;如果想修改起始值,只需要给第一个元素赋值即可;
枚举的优点:
①、增加代码可读性和可维护性
②、和#define定义的标识符比较有类型检查,更加严谨
#define没有类型,enum Day是定义了一种类型;枚举更加严谨
③、防止命名污染
④、便于调试
⑤、试用方便,一次可定义多个常量
三、联合(共用体)
1、联合是一种特殊的自定义类型。
这种类型定义的变量包含一系列的成员,特征是这些成员共用同一块空间(所以联合也叫共用体)
联合的定义
union Un
{
int i;
char c;
};
struct T
{
int i;
char c;
};
int main()
{
union Un u;
struct T t;
printf("联合:%d \n", sizeof(u));
printf("结构体:%d \n", sizeof(t));
printf("******联合****\n");
printf("%p \n", &u);
printf("%p \n", &(u.i));
printf("%p \n", &(u.c));
printf("******结构体*****\n");
printf("%p \n", &t);
printf("%p \n", &(t.i));
printf("%p \n", &(t.c));
return 0;
}
联合的所有属性公用一块空间;联合体空间大小至少是最大成员空间的大小;
2、联合计算
union Un
{
char arr[5];
int i;
};
int main()
{
union Un u;
printf("%d",sizeof(u));
return 0;
}
输出结果8,原因是,char类型5个字节,int i ,4个字节,但是联合在最后计算联合体大小时,需要对齐,所以大小为8;
3、柔性数组
柔性数组:结构中的最后一个元素允许是位置大小的数组,这个叫做柔性数组成员
struct T
{
int a;
int arr[];//柔性数组成员
}
①、柔性数组的特点
·柔性数组不能定义为结构体的第一个成员
·柔性数组不参与计算结构体的大小计算
·包含柔性数组的结构体要用malloc()函数进行内存动态分配;并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小;
strcut T
{
int i;
int arr[];
}
int main()
{
//柔性数组的初始化,需要给柔性数组额外开辟空间
strcut T* t = (struct T*)malloc(sizeof(struct T) + 40);
//通过realloc对结构体扩容
struct T* pt = (struct T*) realloc(t,sizeof(struct T) + 80);
if(pt != NULL)
{
t = pt;
}
//释放
free(t);
t = NULL;
return 0;
}
②、柔性数组的好处
·方便内存释放
柔性数组可以在创建结构体对象时,通过动态内存分配一次性为柔性数组分配空间,且可以一次性释放;如果在结构体使用不适用荣休给你数组,而去使用指针达到扩容的目的,则还需单独对柔性数组进行动态内存分配,且最后还需要对内存进行多次释放,容易导致内存泄漏。
·有利于访问速度
柔性数组通过动态内存可以分配到连续内存空间,方便访问,且内存连续就不会存在过多的内存碎片;多次动态分配内存就会出现内存碎片,在访问速率上也有影响。
通过动态内存分配的空间,在堆上存放;