【C语言复习】C语言中的自定义类型声明定义及内存计算

写在前面

自定义类型小节,主要涉及到结构体,共用体,联合体中的

  • 计算内存对齐方式
  • 结构体,共用体,枚举的区别
  • 位段的概念和已知概念的区别

本节内容比较简单,最重要的就是计算内存对齐。

自定义类型

结构体

结构是一些值的集合,这些值叫做成员变量。结构的每个成员可以是不同类型的变量。

结构的声明:
struct st{
	member-list;
}variable-list;

//e.g.
struct student{
	char name[20];
	int age;
	char sex[5];
	char id[20];
}NAMEOFASTU;

特殊声明,不完全声明:

可以在定义结构体的时候省略掉结构体的标签。但是如果这样,相同的定义方法定义两个匿名结构体,编译器默认是不同的类型。不同类型的结构体不能进行 x = y等操作。

struct{
	int a;
	int b;
	char c;
}x;
结构体自引用

结构体可以设定一个类型就是该结构体本身的成员变量 (指针)。

struct Node{
	int data;
	struct Node* next;
};

简写:每次都写struct Node非常麻烦,可以用如下方法:

typedef struct Node{
	int data;
	struct Node* next;
}Node;

本身定义了struct Node类型,而且还typedef了 struct Node 为 Node类型。之后在使用中就可以写成Node 了。

结构体变量的定义和初始化。

定义变量有两种方法:

  • 在声明类型的同时就定义变量,如下面例子X所示

  • 单独定义结构体变量Y

    //方法1
    struct A{
      int a; 
      int b;
    }X;
    //方法2
    struct A Y;
    

初始化变量:定义变量的同时赋值

struct A a = {10, 20};

同样的可以在声明结构体的时候进行初始化变量:

struct A{
	int a;
	int b;
}X = {10, 20};

可以嵌套初始化结构体:

struct A{
	int a;
	int b;
};
struct Node{
	int data;
	struct A a;
	struct Node* next;
}n1 = {10, {1, 2}, NULL};
//结构体嵌套初始化和声明
内存对齐规则
  1. 第一个成员在与结构体变量偏移量为0的地址处。(无偏移)

  2. 其他成员变量要对齐到对齐数的整数倍的地址处。

  3. 对齐数 = 编译器默认的对齐数 与 该成员大小的较小值。

    VScode中默认的值为4/8.

  4. 结构体总大小为最大对齐数的整数倍(每个成员变量都有自己的对齐数)

  5. 如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍(含嵌套结构体的对齐数)。

为什么存在内存对齐?

平台原因

性能原因:拿空间换取时间

如何设计结构体可以做到满足对齐的同时节省空间?

struct s1{
	char c1;
	int i;
	char c2;
};
struct s2{
	char c1;
	char c2;
	int i;
};

计算一下s1结构体的大小为: 4 + 4 + 4 = 12;

s2 : 4 + 4 = 8,因为c1和c2加起来不够4,后面2 + sizeof(int) > 4,所以两个char先对齐得4,加int的4,总共为8.

所以在声明结构体的时候,尽量让占空间小的放在一起,尽量节省空间。

修改默认对齐数:

#pragma pack();//恢复默认的对齐数
#pragma pack(8);//修改对齐数为8

所以在对齐方式不适合的时候,我们可以自己修改默认对齐数。

结构体传参

结构体传参,最好传递指针(地址),用指针去找到其中的成员变量,远比传递结构体本身方便得多。

位段

位段的声明

与结构体类似,但是不同点在于:

  1. 位段的成员必须是int (unsigned int,或signed int), char类型(属于整型的类型)。(int型能不能表示负数视编译器而定,比如VC中int就默认是signed int,能够表示负数)。

  2. 位段的成员名后面有一个冒号和数字。

  3. 位段的定义格式为: type [var]:digits

    位段名称var是可选参数,即可以省略。digits表示该位段所占的二进制位数。

如下:

struct A
{
	int a : 2; // 位段a,占2bit位
	int b : 5; // 位段b,占5bit位
	int c : 7; // 位段c,占7bit位
};

总共占据14bit位,开辟一个int是4个字节,32bit位。14 < 32,所以开辟一个int大小的空间就可以。

上述代码用sizeof查看,发现占用4个字节大小。

位段的注意事项
  1. 位段的成员都属于整型家族。
  2. 位段的空间是按照4个字节(int),或者1个字节(char)的方式来开辟的。
  3. 位段占的二进制位数不能超过该基本类型所能表示的最大位数,比如在VS中int是占4个字节,那么最多只能是32位;
  4. 无名位段不能被访问,但是会占据空间;
  5. 不能对位段进行取地址操作;
  6. 若位段占的二进制位数为0,则这个位段必须是无名位段,下一个位段从下一个位段存储单元 (这里的位段存储单元经测试在VS环境下 是4个字节)开始存放;
  7. 若位段出现在表达式中,则会自动进行整型升级,自动转换为int型或者unsigned int。
  8. 对位段赋值时,最好不要超过位段所能表示的最大范围,否则可能会造成意想不到的结果。
  9. 位段不能出现数组的形式。
位段在内存中的储存方式

对于位段结构,编译器会自动进行存储空间的优化,主要有这几条原则:

  1. 如果一个位段存储单元能够存储得下位段结构中的所有成员,那么位段结构中的所有成员只能放在一个位段存储单元中,不能放在两个位段存储单元中;如果一个位段存储单元不能容纳下位段结构中的所有成员,那么从剩余的位段从下一个位段存储单元开始存放。(在VS中位段存储单元的大小是4字节).

  2. 如果一个位段结构中只有一个占有0位的无名位段,则只占1或0字节的空间(C语言中是占0字节,而C++中占1字节);否则其他任何情况下,一个位段结构所占的空间至少是一个位段存储单元的大小;

位段的跨平台问题
  1. int位段被当成unsigned 或者是 signed 是不确定的,依平台而定。
  2. 位段的最大位数目不确定,(16位、32位机器不同)
  3. 位段中的成员在内存中从左向右、从右向左分配标准未定义。

枚举

枚举类型的定义

enum Day{
 Mon,
 Tues,
 Wed,
 Thur,
 Fri,
 Sat,
 Sun
};

Enum 之后的是枚举类型,{ }中的内容是枚举常量,就是枚举类型的可能取值。这些枚举常量默认从0开始,依次递增。在定义的时候也可以赋初值。如下

enum Color{
	RED = 1,
	GREEN = 2,
	BLUE = 3
};

枚举的优点

  1. 增加代码的可读性和可维护性
  2. 相比于#define定义的标识符,枚举有类型检查,更加严谨
  3. 防止命名污染。
  4. 便于调试
  5. 使用方便,一次可以定义多个常量

枚举的使用

enum Color{
	RED = 1,
	GREEN = 2,
	BLUE = 3
};

enum Color clr = GREEN; //只可以拿枚举常量给枚举变量赋值,不要用 enum Color clr = 5;

联合(共用体)

联合类型的定义

联合式特殊的自定义类型,包含一系列的成员,一大特征是这些成员共用同一块空间。所以联合体也叫共用体。

//声明
union Un{
	char c;
	int i;
};
//定义
union Un un;

特点

成员共用一块内存空间,所以一个联合体变量的大小,至少是最大成员的大小,因为联合体至少有能力保存最大的成员。

在内存中存储的大小

  1. 联合体大小至少是最大成员的大小。
  2. 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

自定义类型小节完。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值