【c】结构体,枚举,联合类型详解

结构体

什么是结构体?

先来一段百度百科对于c语言中结构体的解释:

在C语言中,结构体(struct)指的是一种数据结构,是C语言中聚合数据类型(aggregate data type)的一类。结构体可以被声明为变量、指针或数组等,用以实现较复杂的数据结构。结构体同时也是一些元素的集合,这些元素称为结构体的成员(member),且这些成员可以为不同的类型,成员一般用名字访问。

上面这段解释基本涵盖了结构体的基础认知。简单的来说,结构体就是一些值的集合,这些值被称为成员变量,并且可以是不同的类型。就比如,我们使用程序去写一个通讯录,会需要描述名字,电话号码,更详细的还可以描述性别,工作职位,家庭住址等等。这个时候就可以使用结构体去完成数据的封装。
结构体声明为:

struct tag
{
member-list;
}variable-list;

用一个更具体的示例来解释(上述的通讯录):

struct contact
{
char name[20];
int age;
char gender[20];
int number[15];
}; //这里的分号容易忽略。

这样就封装完成了一个struct contact类型,我们可以使用这个类型去开辟一块空间,存放名字,年龄等数据。

struct contact s1;

这样就开辟了一块名叫s1的空间来存放需要的数据。如果想要存放多组,还可以定义成数组的类型。

struct contact s[number];

因为每次懒得写struct contact,可以通过typedef将其命名为一个简单的单词,如:

typedef struct contact 
{
}con;

这样就可以直接使用con来代替struct contact开辟空间了。如果结构体用的少,也可以直接在声明时就一并开辟空间。

typedef struct contact 
{
}con;
con contact1[20];

如上开辟了一块类型为con的结构体数组的空间,数组名为contact1,大小为20个con结构体的大小。
有些时候使用也可以是匿名结构体的类型。

struct 
{
int age;
char name[20];
}

这就是一个匿名结构体类型,但用的很少。

那么可以在结构体中实现自引用吗?答案是可以的,但是对写法要求。
以下是一个错误案例:

struct a
{
int age;
struct a next;
}

这样的引用会引起编译错误,无限嵌套的和套娃,是不符合程序规范的。可以参考链表的实现方式,使用指针进行一个前后的连接。

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

其中的next用来存放下一个结构体的地址
有些人可能会犯一些小错误,如:

typedef struct a 
{
int age;
 b *next;
}b;

将struct a类型命名为b,但b出现在b *next之后,这样编译也是无法通过的。类似于函数声明那块的知识。必须先声明才可以使用。不声明就需要函数1在函数2里被用时,函数1排列在函数2前,让编译器知道函数1是存在的。
那么此时需要定义指针为struct a *next。
以上就是结构体的一些基本知识点。

结构体变量的定义与初始化

在上文我们讲述了结构体类型是如何创建的,也简单介绍了关于变量的一些小知识。
定义变量常用以下两种写法。

struct stu
{
int age;
char name[20];
}stu1;
struct stu stu2;

以上代码块就定义了stu1和stu2两个变量,其类型是一致的,并不会因为定义方式不同而发生改变。
变量也是需要赋值的,下面介绍几种常见的赋值写法。
在定义结构体变量的同时,对其赋初值。

struct stu stu3={12,"chengzi"};

也可以跟在结构体类型的后面。

struct stu
{
int age;
char name[20];
}stu1={12,"chengzi"};

如果在stu类型里有嵌套其他结构体类型的话,,也是照常书写。如

struct stu
{
int age;
char name[20];
struct xxx p;
}stu1={12,"chengzi",{对p进行初始化,本质就是一个嵌套}};

计算结构体内存大小

结构体的内存大小计算牵扯到内存对齐这个知识点。
还是先搬一段百度百科的解释:

内存对齐”应该是编译器的“管辖范围”。编译器为程序中的每个“数据单元”安排在适当的位置上。
在这里插入图片描述

计算如下结构体的大小。

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

既然单独提出来,答案肯定不会是1+4+1啦。
想要计算,我们就需要着重去了解内存对齐的计算规则

首先规定,结构体中的第一个成员变量在于结构体偏移量为0的地址处。

这是什么意思呢,就像数组的首元素地址和数组名地址是相同的一个道理。
在这里插入图片描述
偏移量从0开始计算!
那么c1作为第一个成员变量,就放置在如上图的位置了。

第二点,剩余的成员变量就必须对齐到对齐数整数倍的地址处。

什么是对齐数呢?简单的说,对齐数就是编译器默认的一个对齐数与该成员大小较小值。在vs中默认的那个值一般是8。
知道了这个知识点,就好做下一步了。此时已经将c1放好,接下来就要放i,i的大小是4(单位为字节),4与8进行比较,取对齐数为4,即i的内存分配应该分配到偏移量为4的倍数的位置。那么就放在第五个字节处。char也是同理,放置好就如下图。
在这里插入图片描述
可以看到一共占了9个字节。那结构体的大小就是9了吗?

还有第三点,结构体大小是其成员变量的最大对齐数整数倍

那么,最大对齐数为4,也就是说大小必须是4的整数倍,比9大也是4的整数倍的最接近的就是12了。

那么如果在结构体中嵌套了结构体的话,嵌套的结构体会对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍

为何会存在内存对齐这种情况?
第一,平台的可移植性。并非所有硬件都可以访问任意地址的任意数据,其只能在某些地址去取址。
第二,就像结构体的内存对齐一样,这是一种拿空间换时间的办法,这样的话,可以使得访问只需要一次,如果是未对齐,数据类型各异,长度也各异,就需要取多次,时间效率会降低。

如何设计结构体?

遵循一条原则,让占用空间小的成员挤在一起。这样就达到了时间空间的双向节省。

可以联系上面的内存对齐试想一下这样做的道理。

修改默认对齐数

默认对齐数是可以修改的,只需要使用以下的代码。

pragma pack(8)  //设置默认对齐数为8
pragma pack()   //取消设置的默认对齐数,还原为默认

结构体传参

这一点在之前的文章中就已经讲过了,结构体传参最好不要传整个结构体,而是传址,这样更节省空间,而传整个结构体会导致压栈开销过大,以致性能下降。

void show(struct S *p);
void show(struct S s); //不建议

位段

位段也是结构体中不可或缺的一个知识点。
先引用一段百度百科的解释:

位段,C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据

这一段话基本就将位段解释的很清楚了,他将单位化为了,以此达到缩少内存的目的。
位段的成员可以是整型家族中的int,unsigned int,char等。
位段并不支持跨平台,所以请谨慎使用。

使用如下:

struct S
{
char a:3;
char b:4;
char c:5:
char d:4;
}
struct 	S s={0};
s.a=10; s.b=12; s.c=3; s.d=4;

在本机vs编译器中:
结构体中的a:3,就是指a占了3个位,b:4,就是指b占了4个字节。
给a赋值为10,首先10放在char中为00001010,只需要三位,那么截取010(默认都是小端存储)。
同理,b存放1100,c存放00011,d存放0100,那么就这样放就行了吗?答案是否。
在存放这些值时,是不断进行开辟的,当内存不够就新开辟8个字节,不断循环。
实际存储为:01100 010 00000011 00000100
先存放010,在存放1100,存好要存00011时发现空间不够,就放弃目前这个char型空间,开辟一个新的,0100也是同理。

为什么位段不支持跨平台

int型被当作有符号还是无符号不确定。
位段的最大位数不确定。
内存中从左还是从右开始分配不确定。
包含俩位段,当无法容纳第二位段。是舍弃还是继续用第一个位段剩余的位不确定。


枚举

枚举—一一进行列举。
一年里有12个月,这些可以列举,手机有各种型号,这些可以列举,性别等等都是枚举的对象。
拿颜色举个例:

enum color
{
red; 
blue;
green;
black;
yellow;
};

在enum color这个类型中放入了五种颜色。此时的enum color就是一个枚举类型
{}中则是枚举类型的可能取值,也被称为枚举常量。他们默认从0开始,一次递增1。

printf("%d,%d,%d",red,blue,green);

打印出0,1,2
也可以在定义时对齐赋初值。

enum color
{
red=3;
blue=2;
};

枚举和define的功能差不多,那为何我们使用枚举?

可以增强代码可读性,可维护性
比如在一个程序设计菜单中,用到switch结构,case后面会跟很多数字,我们用枚举类型来代替数字进行表达,就会增强可读性,让人一看就知道这个case是用以哪种情况。
使用方便,可以一次定义多个变量
比define会更加严谨一些。


联合

联合作为一种自定义类型,其中包含的一系列成员,其公用一块空间,这也是联合类型最大的特征。
联合类型的声明如

union un
{
char c;
int i;
};

这样一个类型大小在vs编译器上为4个字节。

联合类型的大小至少是最大成员的大小
当最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍

union un
{
short c[7];
int i;
};

其最大成员为c[7],大小为14个字节,但14字节不是最大对齐数的整数倍,所以此大小至少为16。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C语言中,结构体是一种自定义数据类型,可以将不同类型的变量组合在一起,形成一个新的数据类型结构体定义的基本形式如下: ```c struct 结构体名 { 数据类型 成员名1; 数据类型 成员名2; // ... }; ``` 其中,结构体名是用户自定义的名称,成员名是结构体中每个成员的名称,数据类型可以是任意C语言的数据类型,包括基本数据类型和自定义数据类型结构体变量的定义方式如下: ```c struct 结构体名 变量名; ``` 读取结构体中的成员变量可以通过“.”运算符来实现,例如: ```c #include <stdio.h> struct Person { char name[20]; int age; }; int main() { struct Person p; printf("请输入姓名:"); scanf("%s", p.name); printf("请输入年龄:"); scanf("%d", &p.age); printf("姓名:%s,年龄:%d\n", p.name, p.age); return 0; } ``` 枚举是一种特殊的数据类型,用于定义一组常量。枚举的定义方式如下: ```c enum 枚举名 { 常量名1, 常量名2, // ... }; ``` 其中,枚举名是用户自定义的名称,常量名是枚举中每个常量的名称。枚举常量的值默认是从0开始自动递增的,也可以手动指定值。例如: ```c #include <stdio.h> enum Weekday { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday }; int main() { enum Weekday today = Tuesday; printf("今天是星期%d\n", today + 1); return 0; } ``` 联合是一种特殊的数据类型,它的成员变量共享同一块内存空间。联合的定义方式如下: ```c union 联合名 { 数据类型 成员名1; 数据类型 成员名2; // ... }; ``` 其中,联合名是用户自定义的名称,成员名是联合中每个成员的名称,数据类型可以是任意C语言的数据类型,但所有成员的大小不能超过联合的大小。例如: ```c #include <stdio.h> union Number { int i; float f; }; int main() { union Number n; n.i = 123; printf("int: %d, float: %.2f\n", n.i, n.f); n.f = 3.14; printf("int: %d, float: %.2f\n", n.i, n.f); return 0; } ``` 以上就是C语言中自定义数据类型中的结构体枚举联合的基本用法和注意事项。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值