自定义类型
1.1 自定义类型定义
内置类型:char short int long float double…
我们知道,在C语言中有许多内置类型,但是我要存储一个人的信息该要使用哪种类型呢?所以,这时候我们可以自定义一个类型,称为自定义类型
自定义类型:结构体、枚举、联合体
1.2 结构体的定义
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
// 全局变量
struct Stu
{
char name[20];
int age;
}s1,s2; // s1和s2是 struct Stu 类型的变量
// 分号不能丢
#include <stdio.h>
int main()
{
// 局部变量
struct Stu s3;
return 0;
}
1.3 匿名结构体的定义
#include <stdio.h>
// 匿名结构体类型
struct
{
char name[20];
int age;
}s1;
int main()
{
return 0;
}
可以看到,匿名结构体如同名字一般,没有名字
此外,匿名结构体只能使用一次
1.4 结构体的自引用
struct Node
{
int data;
struct Node* next; // 指向下一个节点,即为结构体的自引用
};
1.5 结构体的使用
struct Point
{
int x;
int y;
}p1 = {2,3};
struct Score
{
int n;
char ch;
};
struct Stu
{
char name[20];
int age;
struct Score s;
};
#include <stdio.h>
int main()
{
struct Point p2 = { 3,4 };
struct Stu s1 = { "zhangsan",20,{100,'a'} };
printf("%s %d %d %c\n", s1.name, s1.age, s1.s.n, s1.s.ch);
return 0;
}
1.6 结构体内存对齐
可能有人会有疑问,既然内置类型能用sizeof来求出大小,那么自定义类型呢?大小又是多少?
#include <stdio.h>
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\n", sizeof(struct S1));
return 0;
}
大小为6N?可能你以为char+int+char = 6B?
从运行结果中可以看出,结果并不是我们所想的那样…
结构体内存对齐规则
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。
(VS中默认的值为8)
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
根据上面的内存规则,我们可以知道在内存中存储的情况是这样的
c1放到偏移量为0的地址处,i放到偏移量为4的倍数的地址处,c2放到偏移量为1的倍数的地址处,总共为8B,然后根据第3条规则,求结构体的总大小,这里的最大对齐数为4,结果自然为4的倍数,然后偏移量为4和8的地址处已被占用,所以是结果为12B
1.7 offsetof的使用和嵌套结构体
size_t offsetof( structName, memberName );
简单来说,返回指向的当前结构体的地址处的偏移量
#include <stdio.h>
#include <stddef.h>
struct S1
{
char c1; // 1
int i; // 4
char c2; // 1
};
struct S2
{
char c1;
char c2;
int i;
};
struct S3
{
double d; // 8
char c; // 1
int i; // 1
};
struct S4
{
char c1; // 1
struct S3 s3; // 16
double d; // 8
};
int main()
{
printf("%u\n",sizeof(struct S1));
printf("%u\n", offsetof(struct S1, c1));
printf("%u\n", offsetof(struct S1, i));
printf("%u\n", offsetof(struct S1, c2));
printf("\n%u\n",sizeof(struct S2));
printf("%u\n", offsetof(struct S2, c1));
printf("%u\n", offsetof(struct S2, c2));
printf("%u\n", offsetof(struct S2, i));
printf("\n%u\n", sizeof(struct S3));
printf("%u\n", offsetof(struct S3, d));
printf("%u\n", offsetof(struct S3, c));
printf("%u\n", offsetof(struct S3, i));
printf("\n%u\n", sizeof(struct S4));
printf("%u\n", offsetof(struct S4, c1));
printf("%u\n", offsetof(struct S4, s3));
printf("%u\n", offsetof(struct S4, d ));
return 0;
}
我们来看一下struct S4 嵌套结构体的大小如何计算的
c1先放到偏移量为0的地址处,struct S3结构体对齐到8的倍数的偏移量的地址处,d偏移到8的倍数的偏移量的地址处,总共占用31个字节,然后最大对齐数为16,所以总大小为16的整数倍数,为32B
1.8 设置默认对齐数
#pragma pack(4) // 设置默认对齐数为4
struct S
{
int i;
double d;
};
#pragma pack()// 恢复默认对齐数
#include <stdio.h>
int main()
{
printf("%d\n",sizeof(struct S));
return 0;
}
vs默认对齐数为8,这里可以设置为4
1.9 结构体传参
struct S
{
int data[1000];
int n;
};
#include <stdio.h>
void print1(struct S ss)
{
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ",ss.data[i]);
}
printf("%d\n",ss.n);
}
void print2(const struct S* ps)
{
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%d ", ps->data[i]);
//printf("%d ", (*ps).data[i]);
}
printf("%d\n", ps->n);
}
int main()
{
struct S s = { {1,2,3},100 };
print1(s); // 传值调用 , 会导致压栈
print2(&s);// 传址调用
return 0;
}
上面的 print1 和 print2 函数哪个好些?
答案是:首选print2函数。
原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
2.1 位段
位段的内存分配
- 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
- 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
struct A
{
// 先开辟4个字节
// 32bit
int _a : 2; // 32bit - 2bit = 30bit
int _b : 5; // 30bit - 5bit = 25bit
int _c : 10; //25bit - 10bit = 15bit
// 这里又占用了30个bit位,但是,只剩下15个bit位,所以又开辟了4个字节,4B+4B = 8B
int _d : 30;
};
#include <stdio.h>
int main()
{
printf("%zu\n",sizeof(struct A));
return 0;
}
3.1 枚举
enum Day
{
//Mon,
Mon=1, // 索引从1开始
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
#include <stdio.h>
int main()
{
enum Day d = Fri;
printf("%d\n",Mon); // 打印下标,从0开始
printf("%d\n",Tues);
printf("%d\n",Wed);
printf("d = %d\n",d);
return 0;
}
4.1 联合
创建一个联合体
union Un
{
int i; // 4
char c; // 1
};
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)
union Un
{
int i; // 4
char c; // 1
};
#include <stdio.h>
int main()
{
union Un u;
u.i = 0x11223344;
u.c = 0x00; // 这里调试内存可以看到效果
printf("%zu\n", sizeof(u));
printf("%p\n", &u);
printf("%p\n", &(u.i));
printf("%p\n", &(u.c));
return 0;
}
这里通过调试可以清晰的看到,联合中是共用同一块内存空间的
4.2 计算联合的大小
union Un1
{
char c[5]; // 5 对齐数是2
int i; // 4 对齐数是4
};
union Un2
{
short c[7]; // 14 对齐数是2
int i; // 4 对齐数是4
// 是最大对齐数4的倍数
};
#include <stdio.h>
int main()
{
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));
return 0;
}
这里求Un1的大小时,c占用5个字节,i占用4个字节,所以只要开辟5个字节的内存空间,然后为对齐数4,所以大小是4的倍数的大小,所以是8个字节
Un2与上面同理