目录
一、结构体:
1、结构体的声明:
2、结构体的自引用:
3、结构体变量的定义和初始化:
4、结构体内存对齐:
5、结构体传参:
6、位段:
二、枚举类型:
三、联合体:
一、结构体:
1、结构体的声明:
首先要了解什么是结构:
结构是一些值的集合,与数组不同的是结构的每一个成员变量可以使不同类型的变量。
其声明的时候用struct关键字加上名字,如下定义一个学生的结构体:
struct Student
{char name[20];int age;char sex[10];char id[20];
};
拓展:也可以声明为匿名结构体(就是没有名字的结构体),这种类型的结构体就没有办法使用了,只有在这种类型后面直接定义变量。
struct
{char name[20];int age;char sex[10];char id[20];
}s1,s2;
如上,这就定义了一个匿名结构体,其定义了s1,s2这样的全局变量,就可以在函数中使用
2、结构体的自引用:
如果在一个结构体中嵌套一个结构体,那么并不是直接在结构体中直接嵌套一个结构体的,因为这样的话,这个结构体的大小就会计算不了(结构体嵌套一个结构体,嵌套一个结构体里面又会有一个结构体,就会计算不了)那么,就需要在一个结构体中,存放执行向下一个结构体的指针,这样的话大小就可以计算了。
struct Node
{int a;struct Node* next;
};
3、结构体变量的定义和初始化:
1、可以在声明类型的同时定义变量:
struct Student
{char name[20];int age;char sex[10];char id[20];
}s1,s2;
2、可以在全局变量域中定义,并且在定义的同时赋初始值:
struct Node
{int x;int y;
}s1,s2;struct Node s3 = {3,5};
3、若在结构体中引用另一个结构体并初始化:
struct Node1
{int a;int b;
};struct Node2
{char x;struct Node1;float y;
};struct Node2 node = { 'w', { 1,2 }, 3.14f };
4、结构体内存对齐:
结构体对齐规则:1、第一个成员在与结构体变量偏移量为0的地址处。
2、其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数:编译器默认的对齐数(VS上是8)和该成员大小的 较小值可以用pragma pack(N)来修改默认对齐数,N是几,默认对齐数就修改为几,pragma pack()这串代码就是取消设置的默认对齐数,还原为默认3、结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4、如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
那么为什么会有结构体对齐的规则呢?
在大多数资料都是这么说的:
1、平台原因:不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。2、性能原因:数据结构 ( 尤其是栈 ) 应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问。
例如:
总的来说:结构体的内存对齐是用空间换时间的做法。
5、结构体传参:
从代码入手:
struct S {int data[1000];int num; };struct S s = { {1,2,3,4,5,6,7},13 };void print1(struct S s) {printf("%d\n", s.num); }void print2(struct S* s) {printf("%d\n", s->num); } int main() {print1(s);print2(&s);return 0; }
在如上代码中,print1就是传结构体即可,print2就是传结构体地址的。
建议:
由于在函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销,此时如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降,所以在结构体传参的时候,能传结构体的地址就传结构体的地址。
6、位段:
我们首先来看看位段是怎么定义的:
struct PPR
{int a : 2;int b : 6;int c : 13;int d : 31;char e : 7;unsigned int f : 2;
};
如上所示,
注意:
1、位段的成员必须是整型:int, unsigned int ,char等。
2、位段成员后面加上一个:和数字,来表示这个成员占有几个比特位(不能超过变量本身)
部分成员也可以不加冒号和数字。
理解:
位段时根据后面比特位的大小来给空间的,如:
struct PPR
{int a : 2;int b : 6;int c : 13;int d : 31;char x : 2;char e : 7;
};
对于上诉代码的位段理解:
首先第一个成员是int,就开辟32个比特位,接着把a,b,c可以都存放进去(2+6+13<32)
此时d的话放不进去,那么就在开辟32个比特位,将d存放进去。
在进行判断,发现只剩下一个比特位,x存放不进去,就在开辟char类型(8个比特位),然后将x放进去,在判断发现还剩下6个比特位,小于e,就在开辟8个比特位,将e存放进去。
以上就是位段的存放,但是位段有跨平台的问题:
1、int位段被当成有符号数还是无符号数是不确定的
2、位段中最大位的数目不能够确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题)
3、位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4、当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
二、枚举类型:
枚举类型就是将有限的值一 一列举出来,比如:星期,月份等等。
1、定义:用关键字enum来定义,总体和结构体大差不差,
例如星期的定义:(各个枚举常量之间用逗号隔开,最后一个枚举常量后面不需要逗号)
enum Day
{Mon,Tues,Wed,Thur,Fri,Sat,Sun
};
2、理解:
这些枚举常量都是有值的,如果均没有赋值就从0开始向下一次增加1;
也可以随便赋值(整型),也是依次向下增加1。
3、优点:
(1)增加代码的可读性和可维护性(2)和 #define 定义的标识符比较枚举有类型检查,更加严谨。(3)防止了命名污染(封装)(4)便于调试(5)使用方便,一次可以定义多个常量
三、联合体:
联合体也叫共用体,里面的那些成员变量共用同一块空间,
例如:
union Un
{char x;int i;
};
1、特点:
联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)但是当最大成员的大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。例如:
union u1
{char arr[5];int i;
};union u2
{short arr[7];int i;
};int main()
{printf("%d\n", sizeof(union u1));printf("%d\n", sizeof(union u2));return 0;
}
如上所示:
u1中:char arr[5]这个最大对齐数为1成员大小为5
int i这个最大对齐数为4成员大小为4,
综合来看:大小应该为5,又要是最大对齐数的整数倍,所以大小就是8.
u2中:short arr[7]这个最大对齐数为2成员大小为14
int i这个最大对齐数为4成员大小为4,
综合来看:大小应该为14,又要是最大对齐数的整数倍,所以大小就是16./2.
2、应用:
判断计算机的大小端:
int main()
{union Un{int i;char a;}un;un.i = 1;if (un.a == 1)printf("小端\n");elseprintf("大端\n");return 0;
}