自定义类型:结构体、枚举、联合
一.结构体
1.结构体的基本概念
结构体是不同类型元素的集合。
2.结构体的声明
struct tag
{
member_list;
}variiable-list;
其中,struct是关键字;tag相当于结构体的标签,可以省略;member-list是成员列表,至少要有一个成员;variable-list是变量列表,可以定义结构体变量,这里也可以不写。注意:大括号后面的“;”不能省略!
例如:
struct Stu
{
int num[20];
char sex;
char name[30];
};//在主函数中定义struct Stu zhangsan即定义了一个结构体变量zhangsan.
又如:
struct
{
int num[20];
char sex;
char name[30];
struct Stu
{
int num[20];
char sex;
char name[30];
struct Stu
{
int num[20];
char sex;
char name[30];
struct Stu
{
int num[20];
char sex;
struct Stu stu1;//这种定义不对,因为定义变量就要开辟空间,而在这里,由于之前未被定义,所占内存大小不确定。
struct Stu
{
int num[20];
char sex;
struct Stu * p;//定义了一个结构体指针变量,可以确定开辟空间的大小,可行。
}
访问方式有两种,一种是结构体变量点操作符(.),另一种是结构体指针指向操作符(->)。
struct Stu
{
int num[20];
char sex;
char name[30];//char *name;
->指向操作符:p->name和(*p).name都表示name这个成员。
5.结构体变量的定义和初始化
结构体可以整体初始化,但不能整体赋值,与数组类似。
结构体的定义可以在定义结构体类型时的可变参数列表定义变量,也可以在主函数中定义。
(1)在定义可变参数列表中定义
struct Stu
{
char name[32];
int age;
char sex;
}stu1;//定义结构体变量stu1
(2)在主函数中定义
struct Stu
{
char name[32];
int age;
char sex;
}
int main()
{
struct Stu stu1;//定义结构体变量stu1,变量的类型是struct Stu
.........
}
有如下几种方式:
(1)在定义可变参数列表时初始化
struct Stu
{
char name[32];
int age;
char sex;
}stu1={"zhangsan",19,'m');
(2)在主函数中定义变量时初始化
struct Point
{
int x;
int y;
};
int main()
{
struct Point p1={3,4};
......
}
6.结构体的内存对齐
(1)存在内存对齐问题的原因:
平台原因(硬件原因):不会所有的硬件平台都能访问任意地址上的任意数据,某些硬件平台只能在某些地址上取某些特定类型的数据,否则抛出硬件异常。
(2)性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次。如:当规定只能从4的倍数字节处访问,定义char a,int b;内存对齐和不对齐的情况下对b的访问如下:
内存不对齐: 内存对齐:
可以看到,当内存不对齐时,CPU对b的访问需要两次,而当内存对齐时,CPU对b的访问只需要一次。内存对齐可以减少访问次数,提高性能。本质上是以牺牲空间为代价,减少访问的时间。
结构体的内存对齐规则:
1.第一个成员在结构体变量偏移量为0的地址处。
2.其它成员变量要对齐到对齐数的整数倍处。其中,对齐数=min{编译器默认的一个对齐数,该成员大小}。VS中默认数是8,Linux默认为4.
3.结构体总大小为最大对齐数的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍。
例题:(在VS下)
1.struct S1
{
char c1;对齐数是1 1
int i;//对齐数是4 1+3=4
char c2;//对齐数是1 1+3+4+1=9
}//9不是4的整数倍。所以结构体的大小是12.
2.struct S3
{
double d;//对齐数是8 8
char c;//对齐数是1 1+8=9
int i;//对齐数是4 1+8+3+4=16,16是8的倍数。
};//所以结构体S3的内存大小是16
struct S4
{
struct S3 arr[3];//每个元素的对齐数是16,最大对齐数是8,16*3=48
char x;//对齐数是1,48+1=49
struct S3 y;//对齐数是16,最大对齐数是8, 49+7+16=72
char a[3];//每个元素的对齐数是1,72+1*3=75
double d;//对齐数是8,75+5+8=88
};//结构体S4 的内存大小是88个字节。
拓展:修改默认对齐数:如:#pragma pack(1)//把默认对齐数修改为1。注意:修改的数只能是1,2,4,8,16.
#pragma (1)
#pragma()//恢复为原来的默认对齐数
7.结构体传参
在结构体传参时,不会发生降维问题。而在传参时就会开辟新的空间,形成临时变量。如果结构体过大,则会造成很大的开销,所以建议结构体传参时最好传结构体指针。
二.位段
1.位段与结构体的区别
位段的声明和结构体类似,但是有所区别,体现在1、位段的成员只能是整型(int、char、long、unsigned int)2、位段的成员名后边有一个冒号和一个数字。
2.举个例子
如:
struct A
{
int _a:2;//表示_a只占32个比特位中的2个比特位
int _b:5;//表示_b只占32个比特位的5个比特位
int _c:10;//表示_c只占32个比特位的10个比特位
int _d:30;//表示_d只占32个比特位的30个比特位
};
位段的存储是一种压缩存储,严格依赖于计算机的内部结构、硬件设置。
以下是上述例子的一种存储情况:
可以看到,在给a,b,c分配完空间后,剩下的空间不够分配d,所以重新开辟4个字节的空间,所以该位段只占8个字节。
在位段中的读取:
val._a=0,化为二进制是00(因为只占两个比特位),在读取时,还是00,所以输出为0;val._b=511,化为二进制是1 1111 1111,因为b只占5个字节,所以实际上在内存中的存储应该是11111,在读取时,因为最高位是1,所以按负数来读,减1:11110,取反:10001,所以读val._b是-1;val._c=0,在内存中的存储是00 0000 0000,读取时还是00 0000 0000,所以val._c=0.
3.位段的几点说明
(1)位段涉及很多不确定因素,它的可移植性较差,位段是不跨平台的。
(2)位段的成员是整型家族:int、char、unsigned char、unsigned int、long等。
(3)位段的空间是按照定义以4个或1个或8个字节来开辟空间的。
(4)位段的跨平台问题:a.int位段被当成有符号数还是无符号数是不确定的。
b.位段中最大位的数目是不确定的。(如果是16位机器,则最大位是16;如果写成大于16的数字,则会出问题。如果是32位机器,则最大位是32。)
c.位段中的成员是从左往右还是从右往左分配也要根据平台而定。
d.当一个结构包含两个位段,第二个位段较大,无法容纳第一个位段剩余的位时,是舍弃剩余的位还是利用,这也是不确定的。
三.枚举
1.定义:
enum tag
{
枚举常量列表(每个枚举常量占一行,常量间用“,”隔开,最后一个不用写“,”。
}变量列表;
如:enum Sex
{
MALE,//默认是0
FEMALE,//默认是1
SECRET//默认是2
};
又如边定义边赋值:
enum Sex
{
MALE=1,
FEMALE=4,
SECRET=8
};
2.关于枚举的几点说明:
a.枚举类型的变量只能被赋值为常数,可以是枚举定义中的常数,也可以自己赋值。
b.如果在枚举定义时,各枚举常量未被赋值,则默认第一个常量是0,从后依次加1.
c.宏和枚举的区别:
宏是简单的文本替换,没有语法上的检查。
枚举是一种类型,有类型上的检查。
四.联合
1.联合的定义:
union tag
{
成员列表;
}变量列表;
如:
union Un
{
int i;
char c;
};
2.联合的特点:
联合体共用一段空间,联合体的大小至少是最大成员的大小。当最大成员的大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。
联合体有多个变量,谁用的时候谁就是第一个变量,从第一个字节开始。所以所有的变量的地址相同。
3.联合体的应用:可以判断大小端。