自定义类型:结构体,枚举,联合。
本节重点
◼结构体(struct)
◻结构体类型声明
◻结构体的自引用
◻结构体变量的定义和初始化
◻结构体内存对齐
◻结构体传参
◻结构体实现位段(位段的填充&可移植性)
◼枚举(enum)
◻枚举的定义
◻枚举的优点
◻枚举的使用
◼联合(union)
◻联合的定义
◻联合的特点
◻联合大小的计算
1.结构体的基础知识
1.1结构体是一些值的集合,这些值称为成员变量,结构体的每个成员可以是不同类型的变量.
1.2结构体的声名
struct tag
{
memder-list;//1.成员列表
}variadle-list;//2.变量列表
举例说明如下对应
struct Stu
{//这相当于成员列表
char name[20];
int age;
double score;
}s1,s2,s3;//这相当于变量列表
struct Stu s1;//当然也可以声名全局的
int main()
{
struct Stu s2,s3;//这是声明局部的
}
1.3特殊声明
在声明结构体的时候,可以不完全的声明
比如:
//匿名结构体 意味只用一次
struct
{
int a;
char b;
float c;
}x;//必须加声明列表;
//注:匿名结构体的成员一样,在编译器看来也是不同类型的结构体类型
struct
{
int a;
char b;
float c;
}a[20],*p;
int main()
{
p=&x;//这是错误的虽成员一样,但在编译器看来也是不同类型的结构体类型
return 0;
}
上面两个结构体在声明的时候掉了结构体标签 (tag或Stu)
1.4结构体的自引用
typedef struct Node//typedef类型重命名把struct Node重命名为Node
{
int date ;//数据
struct Node* next;//指针
}Node;
int main()
{
struct Node n1;//找到相同数据的类型
Node n2;//重命明后n1和n2一样少了个struct Node
return 0;
}
1.5结构体类型和初始化
struct Stu
{
char name [20];//名字
float num;//成绩
char id[20];//评价
}s1={"王小明",66.6,"良"};//可以在这定义初始化
int main()
{
struct Stu n={"张三",77.8,"优良"};//也可以在这定义初始化
return 0;
}
1.6结构体类型对齐
规则如下
1.第一个成员在与结构体变量为0地址处。
2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数=编译器默认的一个对齐数(1.VS默认为8 2.Lin默认没有对齐数) 于该成员大小的较小值。
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4.如果嵌套了结构体的情况下,嵌套的结构对齐到自己的最大对齐数的整数倍数,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
struct S1
{
char ci;//(为1的类型大小)第一个成员放到结构体变量为0地址处·
int i;//(为4的类型大小)编译器默认大小与该成员大小的较小值为对齐数等于4
char c2;//(为1的类型 大小)编译器默认大小与该成员大小的较小值为对齐数等于1
//最后 求结构体最大对齐数与偏移量的整数倍对齐数为11
};
int main()
{
struct S1 s;
printf("%d\n",sizeof(struct S1));//字节大小为12
return 0;
}
1.7修改默认对齐数
用**#pragma**这个预处理命令可以修改我们的默认对齐数。
#include<stdio.h>
#pragma pack(4)//修该默认对齐数为4
struct s1
{
char c1;//1
int c2;//4
double c3;//4
};
int main()
{
printf("%d\n",sizeof(struct s1));//字节大小为16
return 0;
}
结论:
结构在对齐方式不合适的时候我们可以修改默认对齐数
1.8结构体传参
struct S
{
int dade[100];
int num;
};
struct S s={{1,2,3,4},1000};
//结构体传参
void print1(struct S s)
{
printf("%d\n",s.num);
}
//结构体地址传参
void print2(struct S* s)
{
printf("%d\n",s->num);
}
int main()
{
print1(s);//传结构体
print2(s);//传地址
return 0;
}
上面个的print1和print2那个函数好
答案是:print2好
原因:
函数传参的时候,参数都是压栈的,会有时间和空间上的系统开销。
如果传递一个结构对象的时候,结构体过大,参数压栈的系统开销过大,所以会导致性能下降。
结论:
结构体传参的时候,要传结构体的地址。
2.位段
2.1什么为位段
位段的声名和结构是类似的,有两个不同:
1.位段的成员必须为int unsigned int 或sigend int.
2.位段的成员名后面有一个冒号和数字。
比如:
struct A
{
int _a:2;//_a这个成员只占2个bit位
int _b:5;//_b这个成员只占5个bit位
int _c:10;
int _d:30;
};//用了8字节
int main()
{
printf("%d\n",sizeof(struct A));
return 0;
}
A就是一个位段类型
2.2位段的内存分配
1.位段的成员可以是int unsigned int singed int 或char(属于整形家族)类型。
2.位段的空间上是按照需要以4字节(int) 或1个字节(char)的方式来开辟的。
3.位段涉及很多不确定因素,位段是不跨平台的,注意可移植的程序应该避免使用位段。
//一个例子
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;
}
具体如下图
2.3位段的跨平台问题
1.int 位段当成有符号类型还是无符号类型是不确定的。
2.位段中最大的位数不能确定。(16位机器最大为16,32位机器最大为32,写成27,在16位机器中会出问题。)
3.位段中的成员在内存中从左到右还是从右向左分配尚未定义。
4.当一个结构包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位时,是舍弃剩余位还是利用,这是不确定的。
总结:
跟结构相比,位段可以达到相同的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
2.4位段的应用
3.枚举
枚举就是可以一一列举出来
如一周的星期一到星期日
还有男女
还有颜色
3.1枚举的定义
enum Sex
{
MALE,//男
FEMALE,//女
SECRET
};
int main()
{
printf("%d\n",MALE);//枚举是有确定值的为0
printf("%d\n",FEMALE);//1
printf("%d\n",SECRET);//2
enum Sex s=MALE;//用枚举的可能取值定义好可以直接用
enum Sex s2=FEMALE;
return 0;
}
3.2枚举的优点
我们可以用#define定义常量,为什么还要用枚举?
1.增加代码的可读性和可维护性
2.和#define定义的标识符比较枚举有检查类型,更加严谨。
3.防止了命名污染(封装)
4.便于调试。
5.使用方便,一次可以定义多个常量
4.联合(共用体)****
4.1联合类型的定义
联合也是一种特殊的自定义类型
这种类型定义的便量也包含一系列的成员,特征是这些成员公用一块空间,(所以也叫共用体)
比如:
union Un
{
char c;
int i;
};
int main()
{
//联合变量的定义
union Un un;
//计算两个变量的大小
printf("%d\n", sizeof(un));
printf("%p\n", &un);
printf("%p\n", &(un.c));
printf("%p\n", &(un.i));
}
下图我们发现为4个字节且地址一样
所示他们公用了同一个内存且同一时间只用一个(所以也叫共用体)
**
4.2枚举的应用
int num()
{
union Un
{
char c;
int i;
}u;
u.i=1;
return u.c;
}
int main()
{
//判断大小端
/*int i=1;
if(1==*(char*)&i)
{
printf("小端");
}else
printf("大端");*/
if(1==num())
{
printf("小端");
}else
printf("大端");
}
4.3联合体大小的计算
union Un
{
char arr[5];对齐数1
int i;对齐数4
};
int main()
{
printf("%d\n", sizeof(un));//8
}
int i;
}u;
u.i=1;
return u.c;
}
int main()
{
//判断大小端
/int i=1;
if(1==(char*)&i)
{
printf(“小端”);
}else
printf("大端");*/
if(1==num())
{
printf("小端");
}else
printf("大端");
}
##### 4.3**联合体大小的计算**
```c
union Un
{
char arr[5];对齐数1
int i;对齐数4
};
int main()
{
printf("%d\n", sizeof(un));//8
}