文章目录
-
- 结构体(包含内存对齐)
- 枚举
- 联合
结构体
1.结构体是什么
结构体是一个或多个值的集合,每个值是结构体的成员变量,每个成员变量可以是不同的类型的变量。
2.结构体声明
例如描述一个人(姓名,年龄,性别,身份证号,等等)
struct people //struct 关键字 people可自定义
{
char name[10];//名字
int age; //年龄
char sex[5]; //性别
char id[20]; //身份证号
}; //有分号!
注意:特殊声明方式(不完全声明)
//匿名结构体类型
struct
{
char name[10];
int age;
char sex[5];
char id[20];
};
//1.匿名结构体定义变量只能以下面的方式定义
struct
{
char name[10];
int age;
char sex[5];
char id[20];
}x; //定义多个只需在x后增加变量名即可,注意 ; 不可以丢
//或
struct
{
char name[10];
int age;
char sex[5];
char id[20];
}*P;
//2.下面写法非法,会报错
*p = &x;
/*
1.因为匿名结构体编译器识别时,即使上面两者的成员列表顺序,类型,大小都一致
也会认为是两个不同的结构体类型。
2.所以,结构体类型一般只能用一次。
*/
3.结构体自引用
//结构体自应用
/* 错误写法
struct MyStruct
{
int a;
struct MyStruct b;
};
错误的用法,此时编译器访问 Mystruct 结构体类型时,有自调用本身,相当于陷入了无穷循环时。
结构体的大小就无法确定。
*/
//正确写法
struct MyStruct
{
int a;
struct MyStruct* b; //定义为指针时,指针大小是固定的。
};
/* 错误写法
typedef struct MyStruct2
{
int a;
Ms* b; //这里会出现报错,因为 Ms 是由 typedef(对一个完整类型重命名) 重命名而来,
//此时的 MS 相当于还未创建出阿来
}MS;
*/
//正确写法
typedef struct MyStruct2
{
int a;
struct MyStruct2* b;
}MS;
4. 结构体变量的定义和初始化
//定义
struct people
{
char name[10];
int age;
char sex[5];
char id[20];
}p1; //声明时,直接在结构体后定义变量
struct people p2; //定义结构体变量
//初始化
struct people2
{
char name[10];
int age;
char sex[5];
char id[20];
}p1 = {"李四",20,"女","0987654321"}; //声明时,直接在结构体后定义变量 ,同时初始化赋值
struct people2 p2 = { "张三", 18, "男", "1234567890" }; //定义结构体变量,同时赋值初始化。
//结构体嵌套初始化
struct AA
{
int x;
char y[10];
};
struct BB
{
int h;
struct AA z;
struct BB* i;
};
struct BB stu = { 15, { 14, "chushihua" },NULL };
5. 计算结构体大小 (结构体内存对齐)
引言:计算结构体大小需了解 对齐规则 和 偏移量
5.1 结构体对齐规则
1. 第一个成员在与结构体变量偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体 的整 体 大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
对齐数 = 编译器默认对齐数 与 该成员变量的大小 相比 的 较小值 (不同编译器默认对齐数 可能不同。例:vs为8)
5.2 偏移量
以机构体的起始地址开始,每增加一个字节,从0开始依次+1递增
5.3 结构体大小计算过程(以vs编译器为例)
成员变量占多少取决于类型大小(例如:int 4个字节,char 1个字节)
结构体类型 S1,S2,成员列表顺序不同,二者大小不同,所以在计算结构体类型是,注意 成员顺序
5.4 为什么存在内存对齐
大部分的参考资料都是如是说的:
1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些 硬件平台只能 在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于为了访问未对齐的 内存,处理器需要作两次内存访问;而对齐的内存访问仅需要 一次访问。
总体来说: 结构体的内存对齐是拿空间来换取时间的做法。
5.5 如何设计,满足对齐规则,又尽量避免空间的浪费
让占用空间小的成员尽量集中在一起 例:
struct MyStruct
{
char c1;
char c2;
int a;
};
#pragma 预处理指令,可更改默认对齐数。
# pragma pack(4) //默认对齐数改为 4.
#pargma pack() //还原对齐数为默认
注:默认对齐数,建议设为2的次方数
6. 结构体传参
struct MyStruct2
{
int a;
char c1[100];
};
void print1(struct MyStruct2 str) //结构体传参
{
printf("%d\n", str.a);
}
void print2(struct MyStruct2* str) //结构体地址传参
{
printf("%d\n", str->a);
}
/*
建议用地址传参。因为在传参过程中,形参是实参的在栈上的临时拷贝。
若结构体过于庞大,可能会造成栈溢出。
*/
int main()
{
struct MyStruct2 str = { 1, "zhangsan" };
//print1(str);
print2(&str);
return 0;
}
枚举
1.什么是枚举
可以列举出来的,有限的值。例如星期,月份,性别,等等。
2.如何定义枚举类型
enum MyEnum
{ //对应的值
man, //0
girl=1, //1
unknown //2
};
/*
1.枚举变量的值,默认从0开始,依次+1递增
2.也可以初始赋值,例如:girl。
3.假设,girl 初始赋值为3,那么 unknow 的值则变为4.
*/
3.枚举类型的使用
int main()
{
enum MyEnum
{ //对应的值
man, //0
girl = 3, //1
unknown //2
};
enum MyEnum a1 = man; //保持同类型赋值
enum MyEnum a2 = girl;
enum MyEnum a3 = unknown;
/*
此时 a1-a3 的值 与 枚举的值相对应
*/
printf("%d", a1);
printf("%d", a2);
printf("%d", a3);
return 0;
}
4.枚举的优点
1. 增加代码的可读性和可维护性
2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试
5. 使用方便,一次可以定义多个常量
联合(共用体)
1.联合类型的定义:
联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块 空间(所以联合也叫共用体)。
2.联合类型的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为 联合至少得有能力保存最大的那个成员)。 例:
union Un
{
int i;
char c;
};
union Un un;
// 下面输出的结果是一样的
printf("%d\n", &(un.i));
printf("%d\n", &(un.c));
3.联合类型声明
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
4. 联合类型的大小
1.联合类型的大小至少是成员变量中最大的大小。
2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。