当前位置: 首页> 游戏> 单机 > 【C语言】自定义类型

【C语言】自定义类型

时间:2025/7/9 14:19:38来源:https://blog.csdn.net/2303_80828380/article/details/139832884 浏览次数:0次

目录

一、结构体:

1、结构体的声明:

2、结构体的自引用:

3、结构体变量的定义和初始化:

4、结构体内存对齐:

5、结构体传参:

6、位段:

二、枚举类型:

三、联合体:


一、结构体:

1、结构体的声明:

首先要了解什么是结构:

结构是一些值的集合,与数组不同的是结构的每一个成员变量可以使不同类型的变量。

其声明的时候用struct关键字加上名字,如下定义一个学生的结构体:

struct Student
{char name[20];int age;char sex[10];char id[20];
};

拓展:也可以声明为匿名结构体(就是没有名字的结构体),这种类型的结构体就没有办法使用了,只有在这种类型后面直接定义变量。

struct 
{char name[20];int age;char sex[10];char id[20];
}s1,s2;

如上,这就定义了一个匿名结构体,其定义了s1,s2这样的全局变量,就可以在函数中使用

2、结构体的自引用:

如果在一个结构体中嵌套一个结构体,那么并不是直接在结构体中直接嵌套一个结构体的,因为这样的话,这个结构体的大小就会计算不了(结构体嵌套一个结构体,嵌套一个结构体里面又会有一个结构体,就会计算不了)那么,就需要在一个结构体中,存放执行向下一个结构体的指针,这样的话大小就可以计算了。

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

3、结构体变量的定义和初始化:

1、可以在声明类型的同时定义变量:

struct Student
{char name[20];int age;char sex[10];char id[20];
}s1,s2;

2、可以在全局变量域中定义,并且在定义的同时赋初始值:

struct Node
{int x;int y;
}s1,s2;struct Node s3 = {3,5};

3、若在结构体中引用另一个结构体并初始化:

struct Node1
{int a;int b;
};struct Node2
{char x;struct Node1;float y;
};struct Node2 node = { 'w', { 1,2 }, 3.14f };

4、结构体内存对齐:

结构体对齐规则:
1、第一个成员在与结构体变量偏移量为0的地址处。
2、其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数:编译器默认的对齐数(VS上是8)和该成员大小的 较小值
        可以用pragma pack(N)来修改默认对齐数,N是几,默认对齐数就修改为几,
pragma pack()这串代码就是取消设置的默认对齐数,还原为默认
3、结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
4、如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,
结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

那么为什么会有结构体对齐的规则呢?

在大多数资料都是这么说的:

1、平台原因:不是所有的硬件平台都能访问任意地址上的任意数据的,某些硬件平台只能
在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:数据结构 ( 尤其是栈 ) 应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存访问仅需要一次访问。

例如:

总的来说:结构体的内存对齐是用空间换时间的做法。

5、结构体传参:

从代码入手:

struct S
{int data[1000];int num;
};struct S s = { {1,2,3,4,5,6,7},13 };void print1(struct S s)
{printf("%d\n", s.num);
}void print2(struct S* s)
{printf("%d\n", s->num);
}
int main()
{print1(s);print2(&s);return 0;
}

在如上代码中,print1就是传结构体即可,print2就是传结构体地址的。

建议:

由于在函数传参的时候,参数是需要压栈的,会有时间和空间上的系统开销,此时如果传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,所以会导致性能的下降,所以在结构体传参的时候,能传结构体的地址就传结构体的地址。

6、位段:

我们首先来看看位段是怎么定义的:

struct PPR
{int a : 2;int b : 6;int c : 13;int d : 31;char e : 7;unsigned int f : 2;
};

如上所示,

注意:

1、位段的成员必须是整型:int, unsigned int ,char等。

2、位段成员后面加上一个:和数字,来表示这个成员占有几个比特位(不能超过变量本身)

部分成员也可以不加冒号和数字。

理解:

位段时根据后面比特位的大小来给空间的,如:

struct PPR
{int a : 2;int b : 6;int c : 13;int d : 31;char x : 2;char e : 7;
};

对于上诉代码的位段理解:

首先第一个成员是int,就开辟32个比特位,接着把a,b,c可以都存放进去(2+6+13<32)

此时d的话放不进去,那么就在开辟32个比特位,将d存放进去。

在进行判断,发现只剩下一个比特位,x存放不进去,就在开辟char类型(8个比特位),然后将x放进去,在判断发现还剩下6个比特位,小于e,就在开辟8个比特位,将e存放进去。

以上就是位段的存放,但是位段有跨平台的问题:

1、int位段被当成有符号数还是无符号数是不确定的

2、位段中最大位的数目不能够确定(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题)

3、位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

4、当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

二、枚举类型:

枚举类型就是将有限的值一 一列举出来,比如:星期,月份等等。

1、定义:用关键字enum来定义,总体和结构体大差不差,

例如星期的定义:(各个枚举常量之间用逗号隔开,最后一个枚举常量后面不需要逗号)

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

2、理解:

这些枚举常量都是有值的,如果均没有赋值就从0开始向下一次增加1;

也可以随便赋值(整型),也是依次向下增加1。

3、优点:

(1)增加代码的可读性和可维护性
(2)和 #define 定义的标识符比较枚举有类型检查,更加严谨。
(3)防止了命名污染(封装)
(4)便于调试

(5)使用方便,一次可以定义多个常量

三、联合体:

联合体也叫共用体,里面的那些成员变量共用同一块空间,

例如:

union Un
{char x;int i;
};

1、特点:

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)
但是当最大成员的大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍。
例如:
union u1
{char arr[5];int i;
};union u2
{short arr[7];int i;
};int main()
{printf("%d\n", sizeof(union u1));printf("%d\n", sizeof(union u2));return 0;
}

如上所示:

u1中:char arr[5]这个最大对齐数为1成员大小为5

            int i这个最大对齐数为4成员大小为4,

综合来看:大小应该为5,又要是最大对齐数的整数倍,所以大小就是8.

u2中:short arr[7]这个最大对齐数为2成员大小为14

            int i这个最大对齐数为4成员大小为4,

综合来看:大小应该为14,又要是最大对齐数的整数倍,所以大小就是16./2.

2、应用:

判断计算机的大小端:

int main()
{union Un{int i;char a;}un;un.i = 1;if (un.a == 1)printf("小端\n");elseprintf("大端\n");return 0;
}
关键字:【C语言】自定义类型

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: