自定义结构

枚举

enum提供了创建常量的方式,可以替代const。使用#define和const可以创建符号常量,使用enum不仅可以创建符号常量,还能定义新的数据类型。

枚举类型的声明和定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//声明,创建一个数据类型,还没有分配存储空间
enum wT{
Monday,
Tuseday,
Wednesday,
Thursday,
Firday,
Saturday,
Sunday
};

//如果不给枚举常量赋初值,编译器会为每个枚举常量赋一个不同的整形值,从0开始。

//定义,创建一个对象
wT weekday;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;
int main()
{
enum wT{Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday}; // 声明wT类型
wT weekday;
weekday = Monday;
weekday = Tuesday;
//weekday = 1; // 错误 不能直接给int值,只能赋值成wT定义好的类型值
//可以写成weekday = wT(1),但是涉及类型转换的写法不推荐
cout << weekday << endl;

//Monday = 0; // 错误 类型值不能做左值,不是有存储空间的变量

int a = Wednesday; //内部有类型转换
cout << a << endl;

return 0;
}

使用细节:

  • 枚举值不能做左值;
  • 非枚举变量不可以赋值给枚举变量;
  • 枚举变量可以赋值给非枚举变量;

  enum只是定义了一个常量集合,里面没有元素,在内存中是当作int存储的。sizeof的值为4.

结构体和联合体

  结构体与数组有两点不同:结构体可以在一个结构中声明不同的数据类型;相同的结构体可以相互赋值,但数组不行。

  联合体(共用体)都是由不同的数据类型成员组成,但联合体同一时间只存放一个被选中的成员。如果对联合体的不同成员赋值,将会对其他成员重写。共用体的用途是当数据项使用多种格式单不会同时使用时,可以节省空间。

  结构体和联合体的在内存中是小端存储的,从低地址开始存放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//结构体
struct Student
{
char name[6];
int age;
Score s;
};

//联合体
union Score
{
double sc;
char level;
};


//结构体不允许对结构体本身进行递归定义,但可以使用指针类型指向本类型
struct person
{
struct person* per;
};

//结构体变量可以在定义时进行初始化赋值,赋值必须按顺序
//如果只定义前面的变量,后面未初始化的成员中,数值型和字符型的自动赋零
struct person{
char name[20];
char sex;
}boy1 = {"xiaoming", 'M'};

结构体中的位字段

  有些信息在存储时,并不需要占用一个完整的字节,只需要占用几个或一个二进制位。位了节省存储空间,C预言提供了一种数据结构,称为“位域”或“位段”。

  C/C++允许指定占用特定位数的结构成员。字段的类型应该为整形或枚举,接下来是冒号,冒号后面指定使用的位数,且可以使用没有名称的字段来提供间距。每个成员都被称为位字段。赋值时不能超过位域的允许范围,如果超过,仅将等号右侧值的低位赋给位域。

1
2
3
4
5
6
7
8
9
struct reg{
unsigned int SN:4;
unsigned int :4;
bool good:4;
};


// 可以像通常那样初始化这些字段,也可以使用标准的结构体表示法来访问位字段:
reg r = {14, true};

存储结构与结构体数据对齐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <string.h>
#include <iostream>
using namespace std;

int main()
{
union Score
{
double ds; //8字节
char level; //1字节
}; //按照最大值,占用8字节

struct Student
{
char name[6]; //6字节
int age; //32位环境 4字节
Score s; //8字节对齐
}; //整体按照8字节对齐,整体占用24字节

cout << sizeof(Score) << endl; // 8

Student s1;
//注意s1.name=“lili”写法是错误的,编译不过
//数组名不能当左值
strcpy_s(s1.name, "lili");
s1.age = 16;

//联合体占用同一块内存空间
s1.s.ds = 95.5;
s1.s.level = 'A';

cout << sizeof(Student) << endl; // 24


return 0;
}

  联合体内部公用一块内存空间,所以内部占用空间按照最大的数据类型来存储,联合体适合内存受限的场景,比如嵌入式系统。也因为这个原因,同一时间只有一个成员有效,写入一个成员时会覆盖其他成员。

  空结构体(不含数据成员)的大小是1,不是0。编译器必须要为其分配一个存储空间用于占位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct s1
{
char x;
int z;
short y;
};

sizeof(s1) = 12;

struct s2
{
char x;
short y;
int z;
};

sizeof(s2) = 8;

  • 结构体的每个成员在内存中的起始地址必须满足其类型的对其要求,32位系统要求如下:
    • char:任何地址
    • short:偶数倍地址
    • int:4的整数倍、
    • float:4的整数倍
    • double:8的整数倍
    • 指针:4的整数倍

  可以看到,如果结构体中存在一个double,则会按照8字节来对齐。然后所有成员按照顺序依次分配内存,编译器会在成员之间插入填充字节,确保下一个成员满足对齐要求。如果存在结构体嵌套,也按照这个对齐规则,所有结构体中的最大成员值来进行内存对齐。结构体的总大小波许是最大成员对齐值的整数倍。

  有一点需要注意,如果结构体中存放了一个数组,数组是按照单个变量一个一个摆放的,不要把数组视为一个整体。

  程序是可以修改默认编译选项的,修改结构体内存分配,如果设置为1,那就是连续的内存分配布局:

1
2
3
4
5
6
7
8
9
10
Visual C++:
#pragma pack(push) //将当前pack设置压栈保存(不一定需要)
#pragma pack(n)

#pragma pack(pop) //如果之前压栈了,这里需要恢复


g++:
__attribute__(aligned(n))
__attribute__(__packed__)