自定义类型详解(结构体+枚举+联合)
结构体
结构体类型的声明
struct tag
{
member-list
}variable-list;
struct Book
{
char name[20];
char author[20];
}s1;
struct Book s2;
int main()
{
struct Book s3;
}
特殊声明-匿名结构体
struct //匿名结构体类型
{
int a;
char c;
double d;
}s1,s2;
///声明的时候创建对象struct
{
int a;
char c;
double d;
}* ps
int main()
{
ps=&s1;
///waring:编译器认为不兼容
}
typedef struct
{
int data;
Node* next;
}Node;
///error
typedef struct Node
{
int data;
struct Node* next;
}Node;
///把这个结构体struct Node --->Node
///typedef xxx Node;
//中间的正常结构体命名就是xxx
结构的自引用
数据结构:描述了数据在内存中存储的结构
自己能够找到自己类型的数据
struct Node
{
int num;
struct Node* next;
}
结构体变量的定义和初始化
- 初始化用{}
struct P{
int x,y;
}p3={5,6},p4={7,8};
struct Point p2={1,2};
struct S
{
double d;
struct Point p;
char name[20];
int data[20];
};
int main()
{
struct Point p1={3,4};
struct S s={3.14,{1,5},"zhangsan",{1,2,3}};
printf("%lf\n",s.d);
printf("%d %d\n",s.p.x,s.p.x);
printf("%s\n",s.name);
}
结构体的内存对齐(计算结构体的大小)
热门考点
//练习1
struct S1
{
char c1;
int a;
char c2;
};
struct S2
{
char c1;
char c2;
int a;
};
int main()
{
struct S1 s={'x',100,'y'};
printf("%d",sizeof(struct S1));
printf("%d",sizoef(struct S2));
return 0;
}
- 结构体内存对齐的规则
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
- 每次对齐数 = 编译器默认的一个对齐数 与 该变量大小的较小值。VS中默认的值为8.GCC中没有规定
- 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数(内部结构体的对齐数))的整数倍。
- 结构体对齐的意义
- 平台原因(移植原因)
- 不是所有的硬件平台都能访问任意地址上的数据,某些硬件平台只能在地址处访问特定类型的数据(比如4的倍数),不然会抛出硬件异常
- 性能原因
- 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
- 比如一次只能访问4个字节,char+int一次就访问不完,还要接着读取下面的int的一个字节
- 平台原因(移植原因)
- 使用的时候我们尽可能满足内存对齐,原则是让占用空间小的成员尽量集中在一起 packi
-
struct S1 { char c1; int i; char c2; struct S2 { char c1; char c2; int i; };
-
#pragma pack(1) struct S1 { char c1; //1 1 1 int i; //4 1 1 char c2; //1 1 1 }; #pragma pack() int main() { struct S1 s; printf("%d\n",sizeof(s)); //6 }
-
写一个宏,计算结构体中某变量相对于首地址的偏移。并给出说明。
考察:offset宏的实现
#include
struct S1{
char c1;
int i;
char c2;
};
int main(){
printf("%d\n",offsetof(struct S1,c1));
printf("%d\n",offsetof(struct S1,i)) ;
}
修改默认对齐数
VS的默认对齐数是8。
#pragma预处理指令可以改变默认对齐数字
#include
#pragma pack(8)//设置默认对齐数为8
struct S1{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)
//设置默认对齐数为1
struct S2{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main(){
//输出的结果是什么?
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
结构体的传参
struct S{
int data[1000];
int num;
};
void print1(struct S tmp){
int i=0;
for(i=0;
i<10;
i++){
printf("%d ",tmp.data[i]);
}
printf("\nnum=%d\n",tmp.num);
}
void print2(const struct S* ps)//优先考虑使用这种形式比较优秀
{
int i=0;
for(int i=0;
i<10;
i++){
printf("%d ",ps->data[i]);
}
printf("%d\n",ps->num);
}
int main(){
struct S s={ {1,2,3,4,5,6,7,8,9,10},100};
print1(s);
print2(&s);
}
位段
讲的书比较少,C和指针提及。
【C语言|C语言--自定义类型详解(结构体+枚举+联合)】位段的声明和结构是类似的,有两个不同。
- 位段的成员必须是int,unsigned in,signed int
- 位段的成员名后边有一个冒号和一个数字
eg.性别 男 女 保密
? 00 01 10 11
//位段是可以节省空间的
struct A{
int _a:2;
// 2 bit位 只给2位,截断
int _b:5;
// 5 bit位
int _c:10;
// 10 bit位
int _d:30;
//30 bit 位};
//47 bit ---6 byte
//8byte
//4byte -->32bit2+5+10 此时不够30bit给_d
//+4byte -->32bit此时_d用哪里的bit呢?标准没有规定。但
//是此时知道够用了整体为8字节
int main(){
printf("%d\n",sizeof(struct A));
//8
}
struct S{
char a:3;
char b:4;
char c:5;
char d:4;
};
int main(){
struct S s={0};
//0 0 0 0 0 0 0 0
//先左边3个bit还是右边3个,C没有交代。假设右边开始
s.a=10;
s.b=12;
s.c=3;
s.d=4;
}
位段的内存分配
- 位段的成员可以是int,unsigned int,signed int,或者char(属于整型家族)类型
- 位段的空间上是按照需要以4个字节(int)或者1个字节(char)来一个个开辟
- 位段涉及很多不确定因素,位段不跨平台的。注意可移植程序避免。
- VS下一个一个给,剩下不够扔掉。从右边开始。C标准没规定。
文章图片
位段的跨平台问题
- int 位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机 器会出问题。
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
位段的应用 用于IP数据报的数据传输,节省空间,提高传输效率。
文章图片
枚举 枚举类型的定义
把可能的取值能一一列举。
enum Day{
Mon,
Thes,
Wed,
Thur,
Fri,
Sat,
Sun
};
//枚举常量//字面常量,const常量,define常量,枚举常量
enum Color{
RED=5,
GREEN,//6
BLUE//7};
//默认从0开始,向下递增,从最后一个赋值好的,剩余没初始化的递增
int main(){
enum Color c=GREEN;
if(c==GREEN)
{
printf("绿色\n");
}
}
枚举的优点
我们可以使用#define来定义,为什么要枚举
- 增加代码的可读性和标识性
- 枚举有类型,和#define比起有类型检查,更加严谨
- 防止了命名污染(封装)
- 便于调试
- 会显示是枚举类型
- 使用方便,一次可以定义多个变量
枚举的使用
enum OPTION{
EXIT,
ADD,
SUB,
MUL,
DIV}
void menu(){}
int main(){
int input=0;
do {
menu();
printf("请选择:");
scanf("%d",&input);
switch(input) {
case ADD:
break;
case SUB:
break;
}
}
}
联合(共用体) 这种类型定义的变量包含一系列的成员,特征是这些成员公用同一块空间。
联合类型的定义
union Un{
char c;
int i;
}
int main(){
Union Un u={0};
printf("%d\n",sizeof(u));
///4个字节
printf("%p\n",&u);
printf("%p\n",&(u.c));
printf("%p\n",&(u.i));
}
i和c同一时间只能用一个。改i也会改c,改c也会改i。
联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为 联合至少得有能力保存最大的那个成员)。
面试题:判断大小端
Union U{
char c;
int i;
}u;
int main(){
u.i=1;
if(u.c==1)
{
printf("小端");
}
else{
printf("大端");
}
}
联合大小的计算
- 联合的大小至少是最大成员的大小
- 当最大成员大小不是最大对齐数的整数倍的时候,要整数对齐
union Un1{
char c[5];
//5 1
int i;
//4 4
}
Union Un2{
short c[7];
//14 2
int i;
//4 8 4
}
练习–通讯录
- 存放1000个人的信息
- 信息:名字+性别+年龄+电话+住址
- 增加联系人
- 删除联系人
- 修改联系人
- 查找联系人
- 排序联系人
- 存文件
下面代码的结果是( )
int main()
{
unsigned char puc[4];
struct tagPIM
{
unsigned char ucPim1;
unsigned char ucData0 : 1;
unsigned char ucData1 : 2;
unsigned char ucData2 : 3;
}*pstPimData;
pstPimData = https://www.it610.com/article/(struct tagPIM*)puc;
memset(puc,0,4);
pstPimData->ucPim1 = 2;
pstPimData->ucData0 = 3;
pstPimData->ucData1 = 4;
pstPimData->ucData2 = 5;
printf("%02x %02x %02x %02x\n",puc[0], puc[1], puc[2], puc[3]);
return 0;
}
VS下的位段是右边开始赋值的。同时要注意,整个是一个字节内,所以这种情况下算值是
1 2 4 8 16 32…
填充序列是类似小端的低地址在低位,所以排列顺序是00 101 00 1。也就是0010 1001,即0x29。
因为为02 29 00 00
文章图片
下面代码的结果是:
#include
int main()
{
union
{
short k;
char i[2];
}*s, a;
s = &a;
s->i[0] = 0x39;
s->i[1] = 0x38;
printf(“%x\n”,a.k);
return 0;
}
注意这是小端机子。
所以s->i[0]=0x39。放在左边(即低地址)
s->i[1]=0x38。放在右边(即高地址)
两个字节合起来读。高地址的数字在高位。所以是3839
文章图片
推荐阅读
- C语言重难点进阶|自定义类型详解(结构体+枚举+联合)【C进阶】
- C语言初阶|自定义类型详解(结构体+枚举+联合)
- html|自定义类型~结构体~位段~枚举~联合~超详解~一遍就会
- 数据结构|二叉树(题集(二))
- 数据结构|堆(优先级队列)
- 数据结构|二叉树(题集(一))
- 小程序|C语言实现通讯录
- 数据结构|链表(题集)
- C语言笔记|c语言入门笔记