目录
一、结构体
结构体是一种允许将不同类型的数据项组合成一个单一类型的方式。结构体可以包含零个或多个不同类型的成员(变量),这些成员可以是基本的数据类型(如int、float等),也可以是其他结构体、指针、数组等复合数据类型。
1.结构体类型的声明
结构体类型的声明方式如下:
struct tag
{
member-list;
} variable-list;
其中:
- truct 是声明结构体类型时必须使用的关键字;
- tag是结构体标签,它是对结构体类型的名称,是可选的。在结构体声明时,如果提供了标签,那么在后续代码中就可以通过这个标签来引用该结构体类型。
- member-list 是结构体成员列表,包含了结构体中的多个成员变量,成员之间用分号(; )分隔。每个成员可以是任何类型的数据,包括其他结构体。
- variable-list 是结构体变量的声明列表,是可选的。 如果在这里声明了变量,那么这些变量就具有前面定义的结构体类型。如果在这里没有声明变量,那么可以在其他地方通过结构体标签和结构体变量声明来创建结构体变量。
(1)普通结构声明
例子:描述一本书
struct Book
{
char name[20];
int price;
char id[12];
}b4,b5,b6;
//b4,b5,b6是全局的
int main()
{
struct Book b1;
struct Book b2;
struct Book b3;
//b1,b2,b3是局部的
return 0;
}
(2)特殊的声明
匿名结构体类型:在声明结构的时候,可以不完全的声明。
struct
{
char c;
int i;
char ch;
double d;
}s;
2.结构体的自引用
如下所示:
struct Node
{
int i;
struct Node* next; //指向下一个节点的指针,实现了自引用
};
3.结构体变量的定义和初始化
(1)正常初始化
struct S
{
char c;
int i;
}s1,s2;
int main()
{
struct S s3 = { 'x', 20 }; //初始化
return 0;
}
(2)结构体包含结构体初始化
struct S
{
char c;
int i;
}s1,s2;
struct B
{
double d;
struct S s;
char c;
};
int main()
{
struct B sb = { 3.14, {'L', 100}, 'c' }; //初始化
printf("%lf %c %d %c\n", sb.d, sb.s.c, sb.s.i, sb.c);
return 0;
}
4.结构体内存对齐
结构体内存对齐规则:
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处; 对齐数 - 编译器默认的一个对齐数与该成员大小的较小值。 VS中默认的值为8。
- 结构体总大小为最大对齐(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
(1)普通类型
struct S
{
char c1; //1字节 - 0
int i; //4字节 - 对齐数为4,因此放在4上,4个字节占到7
char c2; //1字节 - 对齐数为1,因此放在8上,1个字节占到8
//总大小;最大对齐数:4,整数倍即为12(0~8,,一共9个字节)。
};
int main()
{
struct S s = { 0 };
printf("%d\n", sizeof(s));
return 0;
}
(2)嵌套类型
struct S4
{
double d; //8字节 - 0~7
char c; //1字节 - 对齐数为1,因此放在8上,1个字节占到8
int i ; //4字节 - 对齐数为4,因此放在12上,4个字节占到15
//总大小;最大对齐数:4,整数倍即为16。
};
struct S5
{
char c1; //1字节 - 0
struct S4 s3; //16字节 - 对齐数为8,因此放在8~23
double d; //8字节 - 对齐数为8,因此放在24~31
//总大小为32
};
int main()
{
struct S5 s5 = { 0 };
printf("%d\n", sizeof(s5));
return 0;
}
5.结构体设计技巧
在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到
(1)让占用空间小的成员尽量集中在一起。
例如:
struct S1
{
char c1;
char c2;
int i;
};
(2)修改默认对齐数
例如:
//默认对齐数为8
//struct S1
//{
// char c1; //1 - 0
// int i; //4 - 4:4~7
// char c2; //1 - 1:8
// //总大小为:12
// //其中:1~3、9~11都浪费
//};
//把默认对齐数改为2
#pragma pack(2)
struct S1
{
char c1; //1 - :0
int i; //4 - 2:2~5
char c2; //1 - 1:6
//总大小为:8
//其中:1、7都浪费
};
#pragma pack()
int main()
{
printf("%d\n", sizeof(struct S1));
return 0;
}
6.结构体传参
struct S
{
int data[1000];
int num;
};
struct S s = { { 1, 2, 3, 4 }, 1000 };
//1.值传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//2.地址传参
void print1(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
- 上面的 print1和 print2 函数哪个好些 ?
- 答案是: 首选print2函数。
- 原因: 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递一个结构体对象的时候。结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
- 结论: 结构体传参的时候,要传结构体的地址。
7.结构体实现位段
位段的声明和结构是类似的,有两个不同
- 位段的成员必须是int、unsigned int 或signed int;
- 位段的成员名后边有一个冒号和一个数字。
举一个例子:
struct A
{
//先搞4个字节:int - 4个字节 - 32个bit位
int _a : 2; //_a成员占2个bit位
int _b : 5; //_b成员占5个bit位
int _c : 10; //_c成员占10个bit位
//17
//_d不够了,再弄4个字节
int _d : 30; //_d成员占30个bit位
};
int main()
{
printf("%d\n", sizeof(struct A)); //8
return 0;
}
二、枚举
- 枚举(Enumeration)是一种特殊的类,它用于表示一组命名的常量。
- 枚举类型就是一种类型。
1.枚举类型的定义
例如:
//声明枚举类型
enum Color
{
RED, //0
GREEN, //1
BLUE //2
};
int main()
{
enum Color c = BLUE;
printf("%d\n", RED);
printf("%d\n", GREEN);
printf("%d\n", BLUE);
//这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值
}
2.枚举的优势
- 增加代码的可读性和可维护性
- 和#define定义的标识符比较枚举有类型检查,更加严谨。
- 防止了命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量
例如:下面使用枚举,增加了代码的可读性
void menu()
{
printf("***************************\n");
printf("*****1.add *** 2.sub*****\n");
printf("*****3.mul *** 4.div*****\n");
printf("********* 0.exit **********\n");
printf("***************************\n");
}
//普通写法
int main()
{
int input = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
break;
case 0:
break;
default:
break;
}
} while (input);
return 0;
}
//使用枚举后,可以改成下面的形式,增加了代码的可读性
enum Option
{
EXIT, //0
ADD, //1
SUB, //2
MUL, //3
DIV //4
};
int main()
{
int input = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case ADD:
break;
case SUB:
break;
case MUL:
break;
case DIV:
break;
case EXIT:
break;
default:
break;
}
} while (input);
return 0;
}
三、联合体
1.联合类型的定义
- 联合也是一种特殊的自定义类型
- 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)
//联合类型的声明
union Un
{
char c;
int i;
};
int main()
{
//来拟合变量的定义
union Un u;
//printf("%d\n", sizeof(u)); //4个字节
printf("%p\n", &u);
printf("%p\n", &(u.c));
printf("%p\n", &(u.i));
return 0;
}
2.联合体的特点
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)
3.联合大小的计算
- 联合的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
union Un
{
char a[5]; //1 - 5
int i; //4
};
int main()
{
union Un u;
printf("%d\n", sizeof(u)); //4的倍数:8
}