一、结构体
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
- 结构体声明
结构体在声明是可以不完全声明。
struct Book { char name[20]; int price; char id[12]; }b4,b5,b6;//b4,b5,b6是全局的 //不完全声明,匿名结构体类型 struct { char c; int i; char ch; double d; } s; int main() { //b1,b2,b3是局部的 struct Book b1; struct Book b2; struct Book b3; return 0; }
- 结构体自引用
结构体自引用,结构体可以包含结构体。
struct Node { int d; struct Node* next; }; int main() { struct Node n; return 0; }
3.结构体内存对齐
3.1 第一个成员在与结构体偏移量为0的地址处存放;
3.2 其他成员变量都放在某一个对齐数的整数倍的地址处;
对齐数是编译器默认的对齐数与该成员字节的大小中较小的那一个,vs中默认的值为8;
3.3 结构体总大小为最大对齐数( 每个成员变量都有一个对齐数)的整数倍;
3.4 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
struct S { char c; int i; }; struct B { double d; struct S s; char c; }; int main() { printf("%d\n", sizeof(struct S)); printf("%d\n", sizeof(struct B)); struct S s1 = {'x', 20}; struct B sb = { 3.14, {'x', 20}, 'q' }; //.针对结构体变量 //->针对结构体指针 printf("%lf %c %d %c\n",sb.d, sb.s.c, sb.s.i, sb.c); return 0; }
4.为什么存在对齐数
4.1 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
4.2性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐;
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。结构体的内存对齐是拿空间来换取时间的做法。
5.设置默认对齐数
#pragma pack(2) struct S { char c1; int i; char c2; }; #pragma pack() int main() { struct S s = { 0 }; printf("%d\n", sizeof(s)); return 0; }
- 结构体传参
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销;
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降;
结构体传参的时候,要传结构体的地址。
struct S { int data[1000]; int num; }; struct S s = {{1,2,3,4}, 1000}; //结构体传参 void print1(struct S s) { printf("%d\n", s.num); } //结构体地址传参 void print2(struct S* ps) { printf("%d\n", ps->num); } int main(){ print1(s); //传结构体 print2(&s); //传地址 return 0; }
二、位段
1.位段声明
1.1位段的成员必须是 int、unsigned int 或signed int;
1.2位段的成员名后边有一个冒号和一个数字。
2.位段的内存分配
2.1位段的成员可以是int、unsigned int、signed int或者是char(属于整形家族)类型;
2.1位段的空间上是按照需要以4个字节(int)或者1个字节(char)的方式来开辟的;
2.3位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
3.跨平台问题
3.1int位段被当成有符号数还是无符号数是不确定的;
3.2位段中最大位的数目不能确定(16位机器最大16,32位机器最大32,写成27,在16位机
器会出问题);
3.3位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义;
3.4当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
舍弃剩余的位还是利用,这是不确定的。
struct A { // 4字节 - 32bit int _a : 2;// _a成员占2个bit位 int _b : 5;// _b成员占5个bit位 int _c : 10;// _c成员占10个bit位 //15 // 4个字节 - 32bit int _d : 30;// _d成员占30个bit位 }; int main() { printf("%d\n", sizeof(struct A)); return 0; }
三、枚举
枚举就是把可能的取值一一列举。
{}
中的内容是枚举类型的可能取值,也叫枚举常量
;
这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值。
enum Color { RED,//0 CREEN,//1 BLUE//2 }; int main() { enum Color c = BLUE; printf("%d\n", sizeof(c)); printf("%d\n", RED); printf("%d\n", CREEN); printf("%d\n", BLUE); return 0; }
为什么使用枚举:
1.增加代码的可读性和可维护性;
2.和#define定义的标识符比较枚举有类型检查,更加严谨;
3.防止了命名污染(封装);
4.便于调试;
5.使用方便,一次可以定义多个常量。
四、联合(共用体)
联合也是一种特殊的自定义类型;
这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间,所以联合也叫共用体;
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小,因为联合至少得有能力保存最大的那个成员;
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
int check_sys() { union U { char c; int i; }u; u.i = 1; return u.c; } int main() { int ret = check_sys(); if (1 == ret) printf("小端\n"); else printf("大端\n"); return 0; }
union Un1 { char c[5]; int i; }; union Un2 { short c[7]; int i; }; //下面输出的结果是什么? printf("%d\n", sizeof(union Un1)); printf("%d\n", sizeof(union Un2));