文章目录
1.结构体
1.1结构体的概念
结构式一些值的集合,这些值称为成员变量,结构的每个成员变量都可以是不同类型的变量
1.2结构体变量的创建和初始化
- 结构体变量的创建
struct Book
{
char name[20];
char author[20];
float price;
}; b3, b4;//b3和b4为全局变量
struct Book b1;//全局变量
int main()
{
struct Book b2;//局部变量
return 0;
}
- 结构体变量的初始化
struct Book
{
char name[20];
char author[20];
float price;
};
int main()
{
struct Book b1 = { "liangmou3434","liangmou",29.99f };//按照成员变量创建的顺序来赋值
struct Book b2 = { .author = "liangmou",.name = "liangmou3434",.price = 28.88f };//乱序初始化结构体
printf("%s %s %f\n", b1.name, b1.author, b1.price);//liangmou3434 liangmou 29.990000
printf("%s %f %s\n", b2.author,b2.price, b2.name);//liangmou 28.879999 liangmou3434
return 0;
}
1.3结构体的特殊声明
匿名结构体-只能用一次
struct
{
char c;
int i;
double d;
} s = { 'R',9,3.14};
int main()
{
printf("%c %d %lf\n", s.c, s.i, s.d);//R 9 3.140000
return 0;
}
匿名结构体无法复制给匿名结构体的指针,编译器会把匿名结构体和匿名结构体的指针当成两种不一样的类型,且对于匿名结构体类型,如果没有对结构体类型重命名,基本只能使用一次
1.4结构体的自引用
在结构中包含一个类型为该结构本身的成员
匿名结构体中无法实现结构体的自引用
struct Node
{
int data;//结构体本身的成员变量
struct Node* next;//下一个节点的地址-链表的方式
};
int main()
{
return 0;
}
1.5结构体内存对齐
11.5.1结构体内存对齐的规则
- 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处
偏移量:离地址的起始位置有多少个字节 - 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
对齐数=(编译器默认的一个对齐数与该成员变量大小)的较小值
例,vs的默认对齐数是8,int类型的大小为4,所以对齐数就取4 - VS中默认的对齐数为8
Linux中gcc没有默认对齐数,对齐数就是成员自身的大小 - 结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的
整数倍。 - 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构
体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
struct S2
{
char c1;//1个字节 第一个成员变量存在离起始地址偏移量为0的位置,占一个字节
char c2;//1个字节 vs默认的对齐数为8 与1个字节的较小值为1 存在第二个字节位置
int i;//4个字节 vs默认的对齐数为8 与1个字节的较小值为4 存在4的倍数的字节位置 第四个字节位置 4 5 6 7 存放四个字节
};
int main()
{}
struct S2 s2 = { 0 };
struct S4 s4 = { 0 };
printf("%zd\n", sizeof(s2));//8 %zd打印无符号整数,因为sizeof返回的是size_t
return 0;
}
结构体的内存对齐是为了拿空间换取时间
在设计结构体时,既要满足对齐又要节省空间: 让占用空间小的成员尽量集中在一起
1.5.2修改默认对齐数
#pragma pack(number) : number为要修改成的对齐数的值
#pragma pack(1)//把默认对齐数改成1
struct S
{
char c1;
int i;
char c2;
};
//上面代码按照对齐数1计算
#pragma pack()//还原默认对齐数
int main()
{
struct S s = { 0 };
printf("%zd\n", sizeof(s));//没有修改默认对齐数的情况 -12
printf("%zd\n", sizeof(s));//修改默认对齐数后的情况 -6
return 0;
}
1.6结构体的传参
结构体的传参的时候要传结构体的地址
struct S
{
int arr[1000];
int n;
double d;
};
void print2(const struct S* ps)
{
for (int i = 0; i < 5; i++)
{
printf("%d ", ps->arr[i]);//打印结构体内数组的元素
}
printf("%d ", ps->n);
printf("%lf ", ps->d);//指针变量要用操作符-> 指向结构体内的元素
}
int main()
{
struct S s = { {1,2,3,4,5},100,3.14 };
print2(&s);
return 0;
}
1.7结构体实现位段
位段 位:二进制位
1.7.1位段的作用
位段是专门设计用来节省内存的
- 位段的成员必须是int,unsigned int 或者signed int,在c99中位段成员类型也可以选择其他类型
- 位段的成员后面有一个冒号和一个数字
//位段式的结构
struct S
{
int _a : 2;//只占2个比特位
int _b : 5;//只占5个比特位
int _c : 10;//只占10个比特位
int _d : 30;//只占30个比特位
};
1.7.2位段的内存分配
- 位段的成员可以是int,unsigned int,signed int 或者是char等类型
- 位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 6;
//共占3个字节
};
1.7.3位段的跨平台问题
- int位段被当成有符号数还是无符号数是不确定的
- 位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机器会
出问题。) - 位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。(由编译器决定)\
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结:跟结构相比,位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
1.7.4位段使用的注意事项
- 位段的几个成员共有同一个字节,这样有些成员的起始位置并不是某个字节的起始位置,那么这些位置处是没有地址的。内存中每个字节分配一个地址,一个字节内部的bit位是没有地址的。
- 所以不能对位段的成员使用&操作符,这样就不能使用scanf直接给位段的成员输入值,只能是先输入放在一个变量中,然后赋值给位段的成员
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
int main()
{
struct A sa = { 0 };
//scanf("%d", &(sa._b));//这是错误的由于位段是使用字节内的比特位,对于上一个字节剩下的比特位下一个成员变量如果足够,会继续使用
//而比特位在内存中是没有地址的,所以不能使用scanf直接给变量输入赋值
//正确示范
int b = 0;
scanf("%d", &b);
sa._b = b;
//需要重新创建一个变量,用scanf给变量输入值,再把值赋值给结构体内的成员变量
return 0;
}
2.联合体
2.1联合体的概念和特点
- ***像结构体一样,联合体也是由一个或者多个成员构成,这些成员可以不同的类型。
但是编译器只为最大的成员分配足够的内存空间。 - 联合体的特点是所有成员共用同一块内存空间所以联合体也叫共同体
- 给联合体其中一个成员赋值,其他成员的值也跟着变化
联合体和结构体的区别
![[Pasted image 20240813071223.png]]
2.2联合体的声明和创建变量并初始化
union Un
{
char c;
int i;
};
int main()
{
union Un u = { 0 };
printf("%zd\n", sizeof(u));//4
return 0;
}
2.3联合体的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)
union Un
{
char c;
int i;
};
int main()
{
union Un u = { 0 };
printf("%zd\n", sizeof(u));//4
printf("%p\n", &u);//008FFCF4
printf("%p\n", &(u.i));//008FFCF4
printf("%p\n", &(u.c));//008FFCF4
//联合体的成员变量是共用一块内存空间的,所以联合体也叫共用体
//所以联合体只能用一个成员的值-更改成员的值先改低地址的值
return 0;
}
2.4 联合体大小的计算
- 联合的大小至少是最大成员的大小
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
union Un
{
char c[5];//5个字节-对齐数为1 按照数据类型来确定对齐数的大小
int i;//4个字节 对齐数为4
//联合体的最大的成员变量是4个字节,联合体的总大小为9个字节,,最大对齐数为4 9不是最大对齐数的整数倍,要对齐到最大对齐数的整数倍-8
};
int main()
{
union Un u = { 0 };
printf("%zd\n", sizeof(u));//8
return 0;
}
3.枚举
把可能的值一一列举
枚举的关键字-enum
//枚举类型的定义方式
enum Day
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
int main()
{
enum Day d = Mon;
return 0;
}
枚举常量默认从0开始
可以在枚举内给成员赋一个默认值
3.1枚举类型的优点
- 加代码的可读性和可维护性
- 和#define定义的标识符比较枚举有类型检查,更加严谨
- 便于调试,预处理阶段会删除#define定义的符号
- 用方便,一次可以定义多个常量
- 枚举常量是遵循作用域规则的,枚举声明在函数内,只能在函数内使用