在结构体初阶中我们介绍过了结构体基础知识
我们现在来复习一下关于结构体的基础知识
- 结构体的声明包括了结构体标签、结构体成员列表
- 结构体可以匿名定义,如果是匿名定义的结构体只能在定义结构体时定义结构体变量,如
strucrt {int a; int b }s1;
,并且若结构体是匿名的,后续即使出现拥有相同结构成员的结构体也不会被当作一样的结构体,一般不推荐使用匿名结构体- 结构体的初始化需要加上
{}
,可以不按成员顺序进行初始化,这样在初始化列表中需要指定成员初始化如{.member = ?}
- 结构体定义时自引用不能自引用结构体本身类型成员,只能自引用结构体指针成员如
struct S{struct S* a}
而不能struct S{struct S a}
- 使用
typedef
重定义结构体时注意不要将结构体成员定义为typedef后的类型名如typedef struct{S a}S;
此时编译器不知道S是什么样子的类型- 访问结构体成员时有直接访问和间接访问两种方式,直接访问通过结构体变量名.成员进行访问,间接访问通过结构体指针->成员进行访问
- 结构体传参时为了节省在栈区给参数开辟的空间大小,最好传结构体指针
在了解结构体的基本使用后,我们来思考一个问题,如何计算结构体的大小?
这就涉及到了结构体内存对齐
先来看一个例子
struct S
{char c1;int i;char c2;
};
int main()
{printf("%d\n", sizeof(struct S));
}
😲答案居然是12,是不是和你想的亿点点不一样?
既然涉及到了内存对齐,那结构体的大小肯定不能按正常来计算啦
以下是结构体内存对齐的规则
再来分析一下这个题
练习2.
struct S2
{char c1;char c2;int i;
};
int main()
{printf("%d\n", sizeof(struct S2));
}
练习3.
struct S3
{double d;char c;int i;
};
int main()
{printf("%d\n", sizeof(struct S3));
}
练习4.
struct S3
{double d;char c;int i;
};struct S4
{char c1;struct S3 s3;double d;
};
int main()
{printf("%d\n", sizeof(struct S4));
}
注意第4条规则:嵌套结构体的对齐数是该结构体的最大对齐数整数倍
tips:我们可以通过宏
offsetof
来求出结构体各个成员的偏移量
,引入头文件stddef.h
平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常
性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
tips:在设计结构体时有一个小技巧可以让结构体占的空间小,那就是将占据字节数小的成员放在前面
使用预处理指令#pragma pack()修改默认对齐数
在VS环境下,默认对齐数是8,我们有时可以按照自己的需要进行修改,当默认对齐数为1时,表示不存在内存对齐
若不修改默认对齐数,则c2的偏移量是8,结构体大小是16
结论:当默认对齐数不合适时,我们可以自己修改
位段(或称“位域”,Bit field)为一种数据结构,可以把数据以位元的形式紧凑的储存,并允许程序员对此结构的位元进行操作。这种数据结构的好处:
而位域这种数据结构的缺点在于,其内存分配与内存对齐的实现方式依赖于具体的机器和系统,在不同的平台可能有不同的结果,这导致了位段在本质上是不可移植的。
注:
- 不可以对位段成员进行取地址
- 位段成员不可以是静态的
- 位段成员不可以是数组
//一个例子
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;//空间是如何开辟的?
}
关于这个问题,我们先假设两点
总结
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台问题的存在
枚举顾名思义就是一一列举。
把可能的取值一一列举。
比如我们现实生活中:
一周的星期一到星期日是有限的7天,可以一一列举。
性别有:男、女、保密,也可以一一列举。
月份有12个月,也可以一一列举
这里就可以使用枚举了
枚举本质上是int类型,可以在使用int时使用枚举
enum Day//星期
{Mon,Tues,Wed,Thur,Fri,Sat,Sun
};
enum Sex//性别
{MALE,FEMALE,SECRET
};
enum Color//颜色
{RED,GREEN,BLUE
};
上述中的
enum Day\enum Sex\enum Color
都是枚举类型,{}中的是一些枚举的可能取值称为枚举常量
enum spectrum
{red,orange,yellow,green,blue,violet
}color;int main()
{int c;color = blue;if (color == yellow){;}for (color = red; color <= violet; color++){;}return 0;
}
虽然枚举符(如 red 和 blue)是 int 类型,但是举变量可以是任意整数类型,前提是该整数类型可以储存枚举常量。例如,spectrum 的枚举符范围是0~5,所以编译器可以用unsigned char 来表示color 变量。
顺带一提,C 枚举的一些特性并不适用于 C++。例如,C 允许枚举变量使用++运算符,但是 C++标准不允许。所以,如果编写的代码将来会并入 C++程序,那么必须把上面例子中的 color 声明为 nt 类型才能C和 C++都兼容。
在声明数组时可以将枚举常量作为数组元素个数,也可以将枚举常量用作case
语句标签
默认情况下,枚举列表中的常量都被赋予 0、1、2等。因此,下面的声明中 nina 的值是 3:
enum kids {nippy, slats, skippy, nina, liz);
如果只给一个枚举常量赋值,没有对后面的枚举常量赋值,那么后面的常量会被赋予后续的值。例如,假设有如下的声明:
enum feline (cat,lynx = 10,puma,tiger};
cat 的值是0(默认),lynx、puma 和 tiger 的值分别是 10、11、12。
联合(union)是一种数据类型,它能在同一个内存空间中储存不同的数据类型(不是同时储存)。其典型的用法是,设计一种表以储存既无规律、事先也不知道顺序的混合类型。使用联合类型的数组,其中的联合都大小相等,每个联合可以储存各种数据类型。
创建联合和创建结构的方式相同,需要一个联合模板和联合变量。可以用一个步骤定义联合,也可以用联合标记分两步定义。下面是一个带标记的联合模板:
union hold
{
int digit;
double bigfl;
char letter;
}
根据以上形式声明的结构可以储存一个 int 类型、一个 double 类型和 char 类型的值。然而,声明的联合只能储存一个 int 类型的值或一个 double 类型的值或 char 类型的值。
可以初始化联合。需要注意的是,联合只能储存一个值,这与结构不同。有 3 种初始化的方法:把个联合初始化为另一个同类型的联合: 初始化联合的第 1 个元素:或者根据 C99 标准,使用指定初始化器
union hold valA;
valA.letter =R';
union hold valB = valA;//用另一个联合来初始化
union hold valC = (88};// 初始化联合的 digit 成员
union hold valD = {.bigfl = 118.2};// 指定初始化器
//最大对齐数4
union Un1
{char c[5];//对齐数1int i;//对齐数4
};//最大对齐数4
union Un2
{short c[7];//对齐数2int i;//对齐数4
};
int main()
{//下面输出的结果是什么?printf("%d\n", sizeof(union Un1));//8printf("%d\n", sizeof(union Un2));//16
}
//验证大小端
union check
{char ch;int i;
}a;int main()
{a.i = 1;if (a.ch == 1) printf("小端\n");else printf("大端\n");}
上一篇:latex 知识点总结