人一能之,己百之;人十能之,己千之。 ——《中庸》
目录
一.结构体
1.结构的基础知识
2.结构体的声明
3.结构体成员的类型
4.结构体变量的定义和初始化:
5.结构体成员的访问:
6.结构体传参
7.结构体内存对齐:结构体的大小
8.为什么要有内存对齐?
二.结构体的位段
1.什么是位段:
2.位段的内存分配:
3.位段的跨平台问题
4.位段的应用:
三.枚举
1.枚举的定义
2.枚举的优点:
四.联合(共用体)
1.联合体等定义:
2.联合体的大小
一.结构体
1.结构的基础知识
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的的变量。
2.结构体的声明
结构体的定义如下所示,struct为结构体关键字,tag为结构体的标志,member-list为结构体成员列表,其必须列出其所有成员;variable-list为此结构体声明的变量。
struct tag {
member-list
} variable-list ; //这个;不能少
在结构体中,必须有struct tag和member-list是必须要有的,而variable-list是可以没有的,variable-list只是一个结构体变量,而且是全局变量,这里可以不写也行。
struct tag {
member-list
} ;
3.结构体成员的类型
结构体成员可以是标量,数组,指针,甚至是其他结构体。当我们想定义一个人时,我们可能会想到他的身高,体重,名字等我们就用float来定义,体重用int,名字用char。
struct people
{
int weight;//体重
char name[20];//数组来存名字
float height;//身高
};
4.结构体变量的定义和初始化:
struct people
{
int weight;//体重
char name[20];//名字
float height;//身高
};
int main()
{
struct people man = { 55,"小陈",172 };//man就是结构体变量,是局部变量
struct people max = { .weight = 55,.name = "小陈",.height = 172 };
//这两种结构体的初始化都可以
return 0;
}
typedef是在计算机编程语言中用来为复杂的声明定义简单的别名,它与宏定义有些差异。它本身是一种存储类的关键字,与auto、extern、mutable、static、register等关键字不能出现在同一个表达式中。
这里我们写struct people感觉有点麻烦,我们就可以使用typedef来给结构体定义一个简单的别名,之后我们就可以使用别名来代替结构体类型。上述初始化代码我们就可以这样写。
typedef struct people M;
struct people//第一次使用时不能用M来代替
{
int weight;//体重
char name[20];//名字
float height;//身高
};
int main()
{
M man = { 55,"小陈",172 };
//这里就是用M来代替struct people
M max = { .weight = 55,.name = "小陈",.height = 172 };
//这两种结构体的初始化都可以
return 0;
}
当结构体的成员有结构体,我们又该如何对结构体初始化呢?
struct book
{
char name[20];
struct people;
int price;
};
struct people
{
int weight;
char name[10];
float height;
};
int main()
{
struct book b1 = { "活着",{60,"富贵",170},50 };//用大括号括起来
//从结构体book里面从上往下初始化结构体成员
return 0;
}
5.结构体成员的访问:
1.结构体变量.结构体成员名
2.结构体指针->结构体成员名
struct people
{
int weight;
char name[10];
float height;
};
void print(struct people* b2)
{
printf("%d %s %f\n", b2->weight, b2->name, b2->height);
}
int main()
{
struct people b1 = { 55,"小陈",172};
printf("%d %s %f\n", b1.weight, b1.name, b1.height);
print(&b1);
return 0;
}
6.结构体传参
结构体传参也有两种,一种是传结构体变量过去,还有一种就是传地址过去,这两者有什么区别吗?
struct people
{
int weight;
char name[10];
float height;
};
void print1(struct people* b2)
{
printf("%d %s %.1f\n", b2->weight, b2->name, b2->height);
}
void print2(struct people b2)//形参
{
printf("%d %s %.1f\n", b2.weight, b2.name, b2.height);
}
int main()
{
struct people b1 = { 55,"小陈",172};
printf("%d %s %.1f\n", b1.weight, b1.name, b1.height);
print1(&b1);//传地址
print2(b1);//传变量,实参
return 0;
}
当我们传参的时候,如果传的结构体变量,那形参就要开辟结构体成员内存的总空间来接收实参,因为函数传参的时候,参数是需要压栈的。如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降。
所以我们尽量使用传地址的形式,也就是传指针,因为我们知道指针大小就是4或者8个字节。
所以这样就比较节省空间。
结论:结构体传参的时候,要传结构体的地址。
7.结构体内存对齐:结构体的大小
C语言马上都已经学完了,我们还没学过计算结构体的大小。今天我们就将好好的学习一下,在这里我会详细的讲解如何计算结构体的大小。
struct A
{
int a;
char c1;
}s1;
struct B
{
char c1;
int a;
char c2;
}s2;
struct C
{
char c1;
int a;
char c2;
char c3;
}s3;
int main()
{
printf("%d\n", sizeof(struct A));
printf("%d\n", sizeof(struct B));
printf("%d\n", sizeof(struct C));
return 0;
}
在这里计算结构体的大小会不会是A结构体有一个int和char,大小是5个字节。B有6个字节,C有7个字节。其实想想也不会这么简单,不然我们也不会系统的学习。
很奇怪,为什么结构体的大小会是8,12,12呢?而且结构体C还比结构体B的变量多一个。
这里就要请出结构体内存对齐这个东西了。
1.结构体的第一个成员永远放在0偏移处。
2.第二个成员开始,以后每一个成员都要到某个对齐数的整数倍处,这个对齐数是:成员自身大小和默认对齐数的较小值。
注:VS环境下,默认对齐数是8。gcc环境下,没有默认对齐数,对齐数就是成员自身的大小。
3.当成员全部存放进去后,结构体的总大小是所有成员的对齐数中最大对齐数的整数倍。如果不够,则浪费空间对齐。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
上述的话是什么意思呢?我们使用上面的结构体例子来说明一下,就懂了。
对于结构体A:
对于结构体B:
对于结构体C:
对于结构体嵌套结构体的:
就是上面的第四点:如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
struct A
{
char c1;
int a;
double b;
};
struct B
{
char c2;
int a1;
struct A s1;
};
int main()
{
printf("%d", sizeof(struct B));
return 0;
}
8.为什么要有内存对齐?
1. 平台原因:
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件地址处取某些特定类型的数据,否则抛出文件异常。
2.性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
struct A//这个结构体的大小是8
{
char c1;
char c2;
int a;
};
struct B//这个结构体的大小是12
{
char c1;
int a;
char c2;
};
虽然结构体成员都是一样,但是顺序不同也会使结构体的大小不同。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:让占用空间小的成员尽量集中在一起。
二.结构体的位段
1.什么是位段:
顾名思义位段就是二进制位(比特位)。
位段的声明和结构都是类似的,有两个不同:
1.位段的成员必须是整型家族的:int,unsigned int或者signed int,char。
2.位段成员名后边有一个冒号和一个数字。
2.位段的内存分配:
1.位段的空间是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的。
2.位段涉及很多不确定的因素,位段是不跨平台的,注意可移植性的程序应当避免使用位段。
struct A
{
char a : 3;//这些就是位段
char b : 4;
char c : 5;
char d : 4;
}S;
int main()
{
S.a = 10;
S.b = 12;
S.c = 3;
S.d = 4;
return 0;
}
1.我们假设分配到内存空间的比特位是从右向左的
2.分配的内存剩余的比特位不够使用时,浪费掉。
VS2022环境测试数据:
结果确实按照我们假设的一样。
3.位段的跨平台问题
1. int位段被当成有符号数还是无符号数是不确定的。
2.位段中最大位的数目不能确定。(16位机器最大16, 32位机器最大32, 写成27, 在16位机器会出问题。
3.位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4.当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结:
跟结构相比, 位段可以达到同样的效果,并且可以很好的节省空间,但是有跨平台的问题存在。
4.位段的应用:
在网络上传输数据的时候,可以通过位段来节省空间。
三.枚举
1.枚举的定义
枚举顾名思义就是一一给列举出来。
想我们现实中星期,性别和月份等可以一一列举出来。
枚举的可能取值默认时从0开始的,依次递增1。
enum sex
{
MALE,
FEMALE,
SECRET
};
int main()
{
enum sex s = MALE;
printf("%d\n", MALE);
printf("%d\n", FEMALE);
printf("%d\n", SECRET);
return 0;
}
enum sex
{
MALE,
FEMALE=5,//也可赋值,后面依次自增1
SECRET
};
int main()
{
enum sex s = MALE;
printf("%d\n", MALE);
printf("%d\n", FEMALE);
printf("%d\n", SECRET);
return 0;
}
2.枚举的优点:
为什么使用枚举?
我们可以使用#define定义常量,为什么非要使用枚举?枚举的优点:
1.增加代码的可读性和可维护性。
2.和#define定义的标识符比较枚举有类型检查,更加严谨。
3.便于调试。
4.使用方便, 一次可以定义多个常量。
四.联合(共用体)
1.联合体等定义:
联合也是一种特殊的自定义类型。
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合叫共用体)
union un
{
char c;
int i;
};
int main()
{
union un u;
printf("%d\n", sizeof(u));
return 0;
}
特点:联合的成员的共用同一块空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
2.联合体的大小
联合体的大小至少是最大成员的大小
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
这里的对齐规则是和结构体的内存对齐是一样的,但是联合体是公用一块空间的。
比如我们举例说明:
union un
{
char c;
int i;
};
int main()
{
union un u;
printf("%d\n", sizeof(u));
printf("%p\n", &u);
printf("%p\n", &(u.i));
printf("%p\n", &(u.c));
return 0;
}
之前我们不是写了一个判断大小端的题吗?我们通过int a=1;(char*)p=&a, 强制类型转换,访问一个字节来判断。但是这里我们可以通过共用体来实现。非常的简单。
union un
{
char c;
int a;
};
int main()
{
union un s;
s.a = 1;
if (s.c == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
全部的内容就结束了,感谢支持。 文章来源:https://www.toymoban.com/news/detail-421984.html
文章来源地址https://www.toymoban.com/news/detail-421984.html
到了这里,关于【C语言进阶】自定义类型之结构体,枚举和联合的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!