谈到自定义类型,首先想到的是struct 即我们常用的结构体。首先说一说什么是结构。。。
结构:它是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量
一、结构体声明是怎样的呢?
假如要定义一个结构体A
有两种:
1.struct A { 2.struct {
char c; char c;
...(标量、数组、指针、其他结构体等) ...(标量、数组、指针、其他结构体等)
}(它的变量列表) ; }(它的变量列表) ;
第2种定义虽然写起来更方便,但当添加结构体变量的时候就只可以用一次了。举个例子:
编译器会把上面两个声明当成完全不同的两个类型。那样是非法的
注意:结构体定义并不是定义一个变量,而是定义了一种数据类型,这种类型是自定义的,它可以和语言本身所自有的简单数据类型一样使用(如 int )。
结构体本身并不会被作为数据而开辟内存,真正作为数据而在内存中存储的是这种结构体所定义的变量。
对结构体的访问和赋值通常采用 “.”操作符,即结构体变量名 .成员变量名 如访问A 中的a 就是 x.a
也可以是“->”符,即x->a
二、结构体自引用,如下:
结构体的嵌套使用:
三、对结构体变量定义、初始化、传参:
1.在声明结构体时,在最后花括号后面就可以直接定义该结构体的变量或指针;
2. 也可以用struct 结构体名 结构体变量名;进行定义。
在进行初始化的时候需要注意 虽然允许对该变量整体初始化,但不允许整体赋值的。
即如有一结构体struct{
int x;
int y;
};
struct A a1={x,y};是可以的, 而a1.x=10; 或 a1.y=10; 这样赋值是错误的。
再一个就是在进行结构体参数传递时,我们应该是传递结构体变量的地址,因为函数传参,参数是需要压栈的,如果在传递一个结构体对象的时候,结构体过大,参数压栈的系统开销会很大,会导致性能下降。
接下来重点了 :结构体内存对齐
之所以内存对齐 原因有二 1.硬件要求;不是所有的硬件平台都能访问任意地址上的任意数据;只能在某些地址处取特定类型的数据。
2.数据结构应该尽可能地在自然边界上对齐,增加cpu的执行效率
至于怎么对齐,直接上例子:
对齐数:该变量所占大小与编译器默认的一个值的较小值(VS下为8,linux下为4)。
结构体最大对齐数:结构体中所有变量的对齐数的最大值。(ps:有没有注意到它是不可能超过编译器默认的对齐数~)
对齐规则:1..每个成员变量相对第一个成员变量地址偏移量大小要为此时对齐数的整数倍。
2.整个结构体的大小为其最大对齐数的整数倍。
再来一个难一点的例子:
这时候还要加上一条对齐规则:
3.如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,整个结构体大小要是所有对齐数中的最大对齐数的整数倍
struct B { char e[2]; //对齐数为1 < 8 按2对齐;直接放 存至[0,2) short h; //对齐数为2 < 8 按2对齐;已对齐,存至[2,4) //咔! 先停住,去算A的最大对齐数 //不难看处结构体A最大对齐数为8=min{max{int,double,short},8} //整个结构体按8对齐,从[8 开始存放A struct A { int a; //对齐数4 < 8 按4对齐;对齐了,存至[8,12) double b; //对齐数8 = 8 按8对齐;不齐,扩至[16 开始存,存至[16,24) short c; //对齐数2 < 8,按2对齐;对齐了,存至[24,26) }p; }x; //对整体B,其大小要为最大对齐数8的倍数,所以最后大小为32 int main(){ printf("%d",sizeof( x)); return 0; }
注意可以通过#pragma pack (n) 改变编译器默认对齐数,通过#pragma pack() 取消对其的修改。
比如对上面例子修改并在linux环境下运行:
1 #pragma pack (8) 2 struct A { 3 int a; 4 double b; 5 short c; 6 }p; 7 struct B { 8 char e[2]; 9 short h; 10 A p; 11 }x; 12 int main(){ 13 printf("%d",sizeof( x)); 14 return 0; 15 }答案为:20 和没加#pragma pack (8)一样
注意:#pragma pack的n值无论怎么修改都 只能改得比编译器默认对齐数小,改大了是没效果的,
同时,当n等于或超过所有数据成员长度的时候,这个n值的大小一样不产生任何效果。(ps:理由就是在选对齐数的时候就选的跟默认对齐数比 较小的那一个)
位段:
位段声明和结构体类似,只是
1.位段的成员必须是int unsigned int或signed int
2.位段的成员名后面有一个冒号和数字。
位段内存分配:
1. 位段的成员可以是 int 、unsigned int、 signed int 或者是 char (属于整形家 族)类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟 的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位 段
看一个例子:
-> _d在32位机器下 在最后剩下的15个比特位是存不下了的,这时候就会出现问题了。
所以位段虽然更好节省空间,但有几个跨平台问题:
1. int位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写 成27,在16位机器会出问题。)
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含多个个位段,一个位段成员比较大,无法容纳于当前 剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
枚举和联合体:
枚举:一一列举出来可能的取值。
优点:1. 增加代码的可读性和可维护性
2. 很#define定义的标识符比较枚举有类型检查,更加严谨。
3. 防止了命名污染(封装)
4. 便于调试;
5. 使用方便,一次可以定义多个常量
定义:
(ps: 不再是分号 ,改为了逗号)
{ }中的内容是枚举类型的可能取值,也叫枚常量 。这些可能取值都是有值的,默认从0 开始,一次递增1,当然在定义的时候也可以赋初值。
注意问题:
联合体(共用体):定义的变量包含一系列成员,这些成员公用一块空间。
其定义与用法和结构体极为类似。
特点:联合体的成员共用一块内存的,一个联合体变量的大小,至少是最大成员的大小。当最大成员大小不是最大对齐数的整数倍时,就要使其对齐到最大对齐数的整数倍。
对联合体的典型的应用就是用来判定计算机的存储方式。
1 #include <cstdio> 2 int checkSystem() 3 { 4 union check 5 { 6 int i; 7 char ch; 8 }c; 9 c.i=1; 10 return (c.ch==1); 11 } 12 int main() 13 { 14 checkSystem()==1 ? printf("小端!") : printf("大端!"); 15 return 0; 16 }
如果采用小端存储,整型i低位就会放置在低位地址处(记小端有个口诀:小 小 小),当输出ch就会输出 1
如果是大端,此时ch就会输出最高的一个字节也就是:0。
有错望指出~