C语言自定义类型详解(结构体 枚举 联合)

目录

1.结构体类型

1.1声明

1.2结构的自引用

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

1.4结构体内存对齐

1.4.1结构体的对齐规则

1.4.2编译器的默认对齐数修改

1.5结构体传参

1.6结构体实现位段(位段的填充&&可移植性)

1.6.1位段的定义

1.6.2位段的内存分配

1.6.3位段的跨平台问题

1.6.4位段的应用->ip数据报

2.枚举

2.1枚举类型的定义

2.2枚举的优点

3.联合(共用体)

3.1联合类型的定义

3.2联合的特点

3.3联合大小的计算

1.结构体类型

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

1.1声明

struct tag   //struct是结构体关键字。tag是结构体名,自取
{
    member-list;    //成员变量
}variable-list;  //该结构体类型的变量(可省),分号不能丢
//若结构体放在函数之外,则表示全局声明,后面跟的变量也是全局变量

//第二种写法->匿名结构体类型
struct   //结构体名字被省略
{
    member-list;    //成员变量
}variable-list;  //该结构体类型的变量(不可省),分号不能丢
//第二种只能在声明的同时创建变量,只能用一次
//且声明两次相同的结构体类型创建的变量,编译器仍然判断这两个变量为不同类型

//第三种->结构体名的重定义
typedef struct tag   //struct是结构体关键字。tag是结构体名,自取
{
    member-list;    //成员变量
}Node;  //此时Node就是重命名后的名称
//以后定义该结构体类型的变量可以这样写->Node n1;
//类似的,下面实现对指针类型的结构体变量命名
typedef struct tag   //struct是结构体关键字。tag是结构体名,自取
{
    member-list;    //成员变量
}* Linklist;

1.2结构的自引用

举例:

struct Node{
  int date;
  struct Node* next;
  //不能在结构体类型内定义自身结构体变量,否则sizeof()无法计算大小  
};

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

举例:

//方式1
struct Node(
    int num1;  
    int num2;
)p1 = {1,2};
//方式2
struct Node(
    int num1;  
    int num2;
);
struct Str(
    char str[20];  
    int num2;
    struct Node* node;
);
int mian(){
    struct Node s1 = {1,2};
    struct Str s2 = {"zhangsan",23,{1,2}};
    return 0;
}

初始化时使用“{}”包含成员变量,可不完全初始化

1.4结构体内存对齐

1.4.1结构体的对齐规则

  1. 第一个成员在与结构体变量偏移量为0的地址处
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
  3. 对齐数 = 编译器默认的一个对齐数与该成员大小的较小值(VS中默认的值为8)
  4. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
  5. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

代码1:

struct S1
{
    char c1; //1
    int i;  //4
    char c2; //1
};

图解:

C语言自定义类型详解(结构体 枚举 联合)_第1张图片

 代码2:

struct S2
{
    char c1; //1
    char c2; //1
    int i;  //4
};

图解:

C语言自定义类型详解(结构体 枚举 联合)_第2张图片

 代码3:

struct S3
{
    char c1;
    struct S2 s2;
    double d;
};

图解:

C语言自定义类型详解(结构体 枚举 联合)_第3张图片

存在内存对齐的原因:

  • 平台原因(移植原因):

不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

  • 性能原因:

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。

原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

总体来说:

        结构体的内存对齐是拿空间来换取时间的做法。

由S1和S2可得到结论:让占用空间小的成员尽量集中在一起,可以既满足对齐,又节省空间

1.4.2编译器的默认对齐数修改

需要#pragma预处理指令

举例:

#include 
#pragma pack(8)  //设置默认对齐数为8
struct S1
{
    char c1;
    int i;   
    char c2;
};
#pragma pack() //取消设置的默认对齐数,还原为默认,之前设置的只对S1有影响

#pragma pack(1)  //设置默认对齐数为8
struct S2
{
    char c1;
    int i;   
    char c2;
};
#pragma pack() //取消设置的默认对齐数,还原为默认,之前设置的默认对齐数1只对S2有影响

1.5结构体传参

结构体传参包括值传参和地址传参

举例:

struct S
{
    int data[1000];
    int num;    
};
struct S s1 = {{0},1000};
void Print1(strcut S s)
{
    printf("%d\n",s.num);        
}
void Print2(struct S* s)
{
    printf("%d\n",s->num);
}
int main(){
    //方式1,值传递,参数过大时压栈并赋值临时变量既浪费空间,也浪费时间,性能下降
    Print1(s1);
    //方式二,地址传递,传递时只会传递一个地址,压栈时系统开销相对不大,所以传参时首选地址传递
    Print2(&s1);
    return 0;
}

1.6结构体实现位段(位段的填充&&可移植性)

相对节省空间的操作

1.6.1位段的定义

位段的声明和结构体是类似的,有两个不同:

  1. 位段的成员必须是整型家族
  2. 位段的成员名后面有一个冒号和一个数字(数字不能超过类型的大小)

举例:

struct A //A就是一个位段类型
{
    int a:2; //只给a分配两个bit
    int b:5; //b分配五个bit
    int c:10; //10个bit
    int d:30; //30个bit
};

在VS2019编译器x86环境下中,位段A的大小为八个字节,64个bit

1.6.2位段的内存分配

  1. 位段的成员是整型家族
  2. 位段的空间上是按照需要以四个字节(int)或者一个字节(char)的方式来开辟的
  3. 位段设计很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段

举例:

struct A 
{
    char a:3; //给a分配3个bit
    char b:4; //b分配4个bit
    char c:5; //5个bit
    char d:4; //4个bit
};
struct A s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;

分析(VS2019平台):

C语言自定义类型详解(结构体 枚举 联合)_第4张图片

 通过查看内存显示证明:

C语言自定义类型详解(结构体 枚举 联合)_第5张图片

1.6.3位段的跨平台问题

  1. int位段被当成有符号数还是无符号数是不确定的
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题)
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的

总结:跟结构相比,位段可以达到相同的效果,而且可以很好的节省空间,但是有跨平台的问题存在

1.6.4位段的应用->ip数据报

2.枚举

将有限较少的可能的全部取值一一列举

2.1枚举类型的定义

举例:

enum Day //星期
{
    Mon,  //注意都是逗号
    Tues,
    Wed,
    Thur,
    Fri,
    Sat,
    Sun    //最后一个元素不用加逗号       
};
//本质上还是一种类型,只有定义出变量后才会分配空间
//经笔者验证,这种写法需要将定义放在需要它的文件内
int main(){
    enum Day d = Fri;  //创建enum Day变量并进行赋值时,赋值只能选择以上七个选项
    return 0;
}
  • {}中的内容是枚举类型的可能取值,也叫枚举常量
  • 这些可能取值都是有值的,默认从0开始,一次递增1,当然在定义时也可赋初值

例如:

enum Day //星期
{
    Mon = 1,  //表示此时从1开始递增
    Tues,  //其他取值也可任意赋值,后续的值都是从前面最后一次赋值依次递增
    Wed,
    Thur,
    Fri,
    Sat,
    Sun 
};
int main(){
    enum Day d = Fri;  //此时Fri = 5
    return 0;
}

C语言自定义类型详解(结构体 枚举 联合)_第6张图片

2.2枚举的优点

  • 增加代码的可读性和可维护性

比如代码运行在菜单界面后用户做出选择,代码编写时用枚举中的可能取值代替数字,可以让程序员逻辑更清晰

  • 和#define定义的标识符比较枚举有类型检查,更加严谨

#define定义的标识符无符号,枚举定义出来的就是枚举类型,只能用枚举类型中的可能取值进行赋值,而不能用数字等等(C语言或许语法要求不严格,.c不报错,C++严格,.cpp会报错)

  • 防止了命名污染(封装)
  • 便于调试

C语言自定义类型详解(结构体 枚举 联合)_第7张图片

但枚举类型可以在调试中看到

  • 使用方便,一次可以定义多个常量(不用一直#define)

3.联合(共用体)

3.1联合类型的定义

联合也是一种特殊的自定义类型

这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以联合也叫共用体)

举例:

#include 
//联合类型的声明
union Un
{
	char c;
	int i;
};
//两个成员不能同时用
int main() {
	//联合类型的定义
	union Un un;
	//计算联合变量的大小
	printf("%d\n", sizeof(un));
	printf("%p\n", &un);
	printf("%p\n", &un.c);
	printf("%p\n", &un.i);
	return 0;
}

运行结果:

C语言自定义类型详解(结构体 枚举 联合)_第8张图片

原因:

C语言自定义类型详解(结构体 枚举 联合)_第9张图片

3.2联合的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小

确认当前计算机大小端存储方法2:

//联合实现
int check_sys() {
	union {
		int i;
		char ch;
	}nn;
	nn.i = 1;
	return nn.ch;
}

int main() {
	int ret = 0;
	ret = check_sys();
	if (ret) {
		printf("小端\n");
	}
	else {
		printf("大端\n");
	}
	return 0;
}

3.3联合大小的计算

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

举例:

union Un {
	char str[5];  //5
	int i;  //4
};

int main() {
	union Un un;
	printf("%d", sizeof(un));
	return 0;
}

运行结果:

原因:

C语言自定义类型详解(结构体 枚举 联合)_第10张图片

你可能感兴趣的:(c语言,开发语言)