目录
一、结构体
1、什么是结构体
结构体是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
2、为什么要有结构体
在实际的过程中,一个对象往往是有不同的属性来描述的,例如学生这个对象,包括姓名,年龄,学号,性别,成绩等等不同的属性。因此我们往往还需要一组类型不同的数据来描述这个对象,因为数据类型不同,显然不能用一个数组来存放,而是用自定义类型结构体来描述。
3、结构体的声明
struct tag //struct-结构体关键字 tag-结构体标签 struct tag-结构体类型
{
member-list; //成员列表
}variable-list; //variable-list 变量列表名
3.1、变量列表名可以省略。
//结构体声明:声明一个结构体类型;用于描述学生
struct stu
{
//成员变量
char name[20]; //学生姓名
short age; //学生年龄
char tale[12]; //学生电话
char sex[5]; //学生性别
};
//上述这种写法表明仅定义一个结构体类型,不占据空间
3.2、不省略变量列表名。
//结构体声明:声明一个结构体类型;用于描述学生
struct stu
{
//成员变量
char name[20]; //学生姓名
short age; //学生年龄
char tale[12]; //学生电话
char sex[5]; //学生性别
}s1,s2,s3;
//上述这种写法不仅定义了一个结构体类型,还创建了 s1,s2,s3 三个struct stu 类型的全局变量。
3.3、特殊的声明——匿名结构体
在声明结构的时候,可以不完全的声明。
//匿名结构体类型——这种类型结构体存在唯一性
struct
{
int a;
char b;
float c;
}x;//需要直接创建结构体变量
4、结构体的定义、初始化与访问
访问结构体成员有 ' . ' 和 ' -> '两种方式。其中 ' . '用于结构体变量直接访问成员,而 ' -> '用于结构体指针访问成员。
#include<stdio.h>
#include<string.h>
struct stu
{
//成员变量
char name[20]; //学生姓名
short age; //学生年龄
char tale[12]; //学生电话
char sex[5]; //学生性别
}s1;
void print(struct stu* s1)
{
printf("%s %d %s %s\n", s1->name, s1->age, s1->tale, s1->sex);
}
void print2(struct stu s2)
{
printf("%s %d %s %s\n", s2.name,s2.age,s2.tale,s2.sex);
}
int main()
{
//先定义后初始化
strcpy(s1.name, "李四");
s1.age = 20;
strcpy(s1.tale, "222222222222");
strcpy(s1.sex, "男");
//结构体指针访问成员
print(&s1);
//定义的同时进行初始化
struct stu s2 = { "张三",20,"111111111111","男" };
//结构体变量访问成员
print2(s2);
return 0;
}
5、结构体内存对齐
5.1、什么是内存对齐
结构体成员是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,成员放置的位置一定会在自身大小的整数倍上开始,这就是所谓的内存对齐。
5.2、为什么存在内存对齐
大部分的参考资料都是如是说的:
1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能 在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的 内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:结构体的内存对齐是拿空间来换取时间的做法。
5.3、对齐规则
1. 第一个成员在结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到 对齐数 的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
下面我们来看一下具体的计算:
struct S1
{
char c1;
int i;
char c2;
};//大小为 12 字节
二、位段(位域)
1、什么是位段
信息的存取一般以字节为单位。实际上,有时存储一个信息不必用一个或多个字节,为了节省空间,常常在一个字节中放几个信息。C语言允许在一个 结构体 中以位为单位来指定其成员所占 内存 长度。
2、位段的声明
(1)位段的宽度不能超过它所依附的数据类型的长度。
(2)C语言标准还规定,只有有限的几种数据类型可以用于,位段的成员必须是 int、unsigned int 或signed int 。
(3)但编译器在具体实现时都进行了扩展,额外支持了其他类型。
struct B
{
char _a : 2; //数字表示占据的位数
char _b : 4;
char _c : 6;
};
3、位段的内存分配
(1)位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的(2)位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
4、位段的存储
C语言标准并没有规定位域的具体存储方式,不同的编译器有不同的实现,但它们都尽量压缩存储空间。
1) 当相邻成员的类型相同时,如果它们的位宽之和小于类型的 sizeof 大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止;
如果它们的位宽之和大于类型的 sizeof 大小,那么后面的成员将从新的存储单元开始,其偏移量为类型大小的整数倍。
2) 当相邻成员的类型不同时,不同的编译器有不同的实现方案
3) 如果成员之间穿插着非位域成员,那么一般不会进行压缩。
struct A //A就是一个位段类型。
{
int _a; //_a 没有限制占用位数,则根据数据类型即可推算出它占用 4 个字节(Byte)的内存
//后面的数字用来限定成员变量占用的位数
int _b : 4;//占用 4 位的空间
int _c : 10;//占用 10 位的空间
};
struct B
{
char _a : 2;
char _b : 4;
char _c : 6;
};
struct C
{
//如果成员之间穿插着非位域成员,那么一般不会进行压缩。
int _a : 2;
int _b;
int _c : 6;
};
struct D
{
//当相邻成员的类型不同时,不同的编译器有不同的实现方案
char _a : 2;
int _b : 4;
char _c : 6;
};
int main()
{
printf("%d\n", sizeof(struct A)); //大小为 8 字节
printf("%d\n", sizeof(struct B)); //大小为 2 字节
printf("%d\n", sizeof(struct C)); //大小为 12 字节
printf("%d\n", sizeof(struct D)); //大小为 12 字节
return 0;
}
5、位段的跨平台问题
(1). int 位段被当成有符号数还是无符号数是不确定的。
(2). 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。(3). 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
(4). 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
三、枚举
1、枚举的声明
注意事项:
(1)枚举常量, 以 ' , ' 分开,最后一项列表后面不用符号
(2)每一项常量都有默认值,每项都比前一项增加 1,默认从 0 开始
(3)枚举常量都是全局常量
(4)它们不占用数据区(常量区、全局数据区、栈区和堆区)的内存,而是直接被编译到命令(转换成了默认值)里面,放到代码区,所以不能用 & 取得它们的地址。
enum typename //enum是枚举关键字 ; enum typename是枚举类型
{
valuenamelist1,
valuenamelist2,
valuenamelist3
}variablelist; 创建枚举类型的全局变量(可以不创建) 不能省略 ';'
enum color
{
RED,//默认值为 0
GREEN,//默认值为 1
BLUE//默认值为 2
};
enum week
{
//可以自己 设置 默认值
MON = 1, //设置默认值 = 1
TUES, //默认值为 2
WED, //默认值为 3
THURS = 256, //设置默认值为 256
FRU, //默认值为 257
SAT, //默认值为 258
SUN //默认值为 259
};
int main()
{
printf("RED=%d GREEN=%d BLUE=%d\n", RED, GREEN, BLUE);
printf("MON=%d TUES=%d WED=%d THURS=%d FRU=%d SAT=%d SUN=%d\n", MON, TUES, WED, THURS, FRU, SAT, SUN);
//枚举类型变量需要存放的是一个整数,长度和 int 应该相同
printf("%d %d %d %d\n", sizeof(RED), sizeof(GREEN), sizeof(BLUE), sizeof(enum color));
return 0;
}
2、枚举的优点
我们可以使用 #define 定义常量,为什么非要使用枚举?
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
6.#define命令虽然能解决问题,但也带来了不小的副作用,导致宏名过多,代码松散
4、 联合体(共用体)
1、联合类型的定义
联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
2、联合的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
3、联合大小的计算
联合的大小至少是最大成员的大小。当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
union un//联合体的声明
{
char c;
int i;
};
union un s;//联合体的定义
int main()
{
s.i = 0x11223344;
s.c = 0x55;
printf("%#x %#x\n\n",s.c,s.i);
printf("%d\n", sizeof(s));
return 0;
}