【C语言】一篇搞定自定义类型:结构体、枚举、联合体(共用体)附上简易通讯录项目源码(一)

简介: 【C语言】一篇搞定自定义类型:结构体、枚举、联合体(共用体)附上简易通讯录项目源码(一)

【C语言】一篇搞定自定义类型:结构体、枚举、 联合体(共用体)附上简易通讯录项目源码

1. 结构体

 1.1 结构体类型的声明

 1.2 结构的自引用

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

 1.4 结构体内存对齐

 1.5 为什么存在内存对齐

 1.6 修改默认对齐数

 1.7 结构体传参

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

2. 枚举

 2.1 枚举类型的定义

 2.2 枚举的优点

 2.3 枚举的使用

3. 联合

 3.1 联合类型的定义

 3.2 联合的特点

 3.3 联合大小的计算

4. 利用自定义类型实现简易通讯录程序


联合体(共用体)附上简易通讯录项目源码)


1. 结构体

结构的基础知识

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


1.1 结构体类型的声明

结构的声明

struct tag

{

member-list;

}variable-list;


例如描述一个学生:


struct Stu

{

char name[20];//名字

int age;//年龄

char sex[5];//性别

char id[20];//学号

};//分号不能丢


特殊的声明

在声明结构的时候,可以不完全的声明。

比如:


//匿名结构体类型

struct

{

int a;

char b;

float c; }x;

struct

{

int a;

char b;

float c; }a[20], *p;


上面的两个结构在声明的时候省略掉了结构体标签(tag)


那么问题来了?

在上边代码的基础上,下面代码合法吗?

p&x;

警告: 编译器会把上面的两个声明当成完全不同的两个类型。 所以是非法的。


1.2 结构的自引用

在结构体中包含一个类型为该结构体本身的成员是否可以呢?

来看如下两段代码


//代码1

struct Node

{

int data;

struct Node next;

};

//可行否?


代码1 这种自引用方式是错误的

如果可以,那sizeof(struct Node)是多少?

这就会形成无限套娃,这个结构体大小就会是无限大


正确的自引用方式应该是代码2这样的,通过指针(指针大小是确定的4/8字节)的方式来自引用


//代码2

struct Node

{

int data;//数据域

struct Node* next;//指针域

};


注意:代码3


//代码3

typedef struct

{

int data;

Node* next; }Node;

//这样写代码,可行否?


答案是 不行

因为在使用Node*的时候,Node这个类型还没有产生,所以还不能被使用


//解决方案:

typedef struct Node//在使用Node*之前把类型名Node声明

{

int data;

struct Node* next; }Node;//这个Node是重命名后产生的


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


有了结构体类型,那如何定义变量,其实很简单。


struct Point

{

int x;

int y; }p1; //声明类型的同时定义变量p1

———————————————————————————————————————

struct Point p2; //定义结构体变量p2

———————————————————————————————————————

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

struct Point p3 = {x, y};

———————————————————————————————————————声明

{

char name[15];//名字

int age;      //年龄

};

struct Stu s = {"zhangsan", 20};//初始化

———————————————————————————————————————

struct Node

{

int data;

struct Point p;

struct Node* next;

}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化

———————————————————————————————————————

struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化


1.4 结构体内存对齐

我们已经掌握了结构体的基本使用了。

现在我们深入讨论一个问题:计算结构体的大小。

这也是一个特别热门的考点: 结构体内存对齐


要计算结构体的大小首先得掌握结构体的对齐规则


1.第一个成员在与结构体变量偏移量为0的地址处。


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

对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值

------- VS中默认的值为8

--------Linux中的默认值为4


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


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


练习1


struct S1

{

char c1;

int i;

char c2;

};

printf("%d\n", sizeof(struct S1));


图解:

微信图片_20220415173232.png



练习2


struct S2

{

char c1;

char c2;

int i;

};

printf("%d\n", sizeof(struct S2));


图解:

微信图片_20220415173313.png

练习3


struct S3

{

double d;

char c;

int i;

};

printf("%d\n", sizeof(struct S3));


图解:

微信图片_20220415173342.png


练习4-结构体嵌套问题


struct S4

{

char c1;

struct S3 s3;

double d;

};

printf("%d\n", sizeof(struct S4));


图解:


微信图片_20220415173412.png

1.5 为什么存在内存对齐

大部分的参考资料都是如是说的:

1. 平台原因(移植原因):

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

2. 性能原因:

数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

总体来说:

结构体的内存对齐是拿空间来换取时间的做法。那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:让占用空间小的成员尽量集中在一起。


例如:

struct S1

{

char c1;

int i;

char c2;

};

struct S2

{

char c1;

char c2;

int i;

};


S1和S2类型的成员一模一样,但是S1和S2所占空间的大小有了一些区别


1.6 修改默认对齐数

之前我们见过了 #pragma 这个预处理指令,这里我们再次使用

可以改变我们的默认对齐数

请看代码:


#include <stdio.h>

#pragma pack(8)//设置默认对齐数为8

struct S1

{

char c1;

int i;

char c2;

};

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

———————————————————————————————————————

#pragma pack(1)//设置默认对齐数为8

struct S2

{

char c1;

int i;

char c2;

};

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

———————————————————————————————————————

int main()

{

   //输出的结果是什么?

   printf("%d\n", sizeof(struct S1));

   printf("%d\n", sizeof(struct S2));

   return 0;

}


运行结果:


微信图片_20220415173627.png

结论:

当你觉得结构体对齐数不合适的时候,可以用#pragma来修改对齐数,从而达到一个节省空间的效果,但是有可能会损失效率,要合理使用


1.7 结构体传参

直接上代码


struct S {

int data[1000];

int num;

};

struct S s = {{1,2,3,4}, 1000};

//结构体传参

void print1(struct S s) {

printf("%d\n", s.num);

}

//结构体地址传参

void print2(struct S* ps) {

printf("%d\n", ps->num);

}

int main()

{

print1(s);  //传结构体

print2(&s); //传地址

return 0; }


上面的 print1 print2 函数哪个好些?

答案是:首选print2函数。 原因:


函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。

如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降


结论:

结构体传参的时候,要传结构体的地址


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

什么是位段

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


1.位段的成员必须是 int、unsigned int 或signed int 。

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


比如:


struct A {

int _a:2;

int _b:5;

int _c:10;

int _d:30;

};

printf("%d\n", sizeof(struct A));


A就是一个位段类型,那位段A的大小是多少?

运行结果如图

微信图片_20220415173846.png

为什么是这样呢?接下来看


位段的内存分配:


位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型

位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。

位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。


所以上边的位段A就是这样的

微信图片_20220415173918.png

再来举一个例子


//一个例子

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;

//空间是如何开辟的?


大小为3个字节


空间开辟过程请看图解:

微信图片_20220415173952.png

位段的跨平台问题


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

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

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

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


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


版权声明:本文为CSDN博主「敲代码的布莱恩特」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/DerrickWestbrook/article/details/120380553

相关文章
|
10天前
|
JavaScript Java 测试技术
基于SpringBoot+Vue+uniapp的C语言在线评测系统的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue+uniapp的C语言在线评测系统的详细设计和实现(源码+lw+部署文档+讲解等)
|
17天前
|
搜索推荐 C语言
C语言冒泡排序(附源码和动态图)
冒泡排序是一种简单的排序算法,其基本思想是通过重复遍历待排序的数列,比较每对相邻元素的值,如果它们的顺序错误(即满足一定的排序条件,如从小到大排序时前一个元素大于后一个元素),就交换它们的位置。这个过程就像水底的气泡一样逐渐向上冒,因此得名“冒泡排序”。
|
25天前
|
C语言
枚举(C语言)
枚举(C语言)
|
13天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的C语言在线评测系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的C语言在线评测系统附带文章源码部署视频讲解等
25 0
|
17天前
|
Linux C语言
【编程小实验】C语言实现:无限循环写入文本文件,支持Ctrl+C中断与数据追加(附完整源码)
在Linux中,文件I/O(输入/输出)是程序与文件进行交互的基本方式,包括读取文件内容和向文件写入数据。这通常通过标准的C库函数来实现,下面是一些基本的文件读写操作和代码示例。
|
20天前
|
存储 SQL 网络协议
什么是PACS系统?一套C语言C/S架构PACS影像归档和通信系统源码
PACS系统是基于C/S架构的医学影像归档和通信系统,遵循IHE和DICOM3.0标准,采用Wintel平台与品牌服务器,配备SQL Server数据库,支持双机热备。它确保图像质量和高效传输,兼容多种医学设备,允许历史胶片扫描存储,并有严格的权限管理与安全机制,包括数据备份和故障恢复功能,旨在实现资源共享和效率提升。系统设计考虑了与医院HIS集成及未来扩展。
17 0
|
24天前
|
编译器 C语言
C语言枚举:深入探索下标默认值、自定义值及部分自定义情况
C语言枚举:深入探索下标默认值、自定义值及部分自定义情况
12 0
|
24天前
|
存储 编译器 C语言
C语言的联合体:一种节省内存的数据结构
C语言的联合体:一种节省内存的数据结构
18 0
|
26天前
|
编译器 C语言 C++
【海贼王编程冒险 - C语言海上篇】自定义类型:结构体,枚举,联合怎样定义?如何使用?
【海贼王编程冒险 - C语言海上篇】自定义类型:结构体,枚举,联合怎样定义?如何使用?
12 0
|
11月前
|
存储 C语言
【C语言】 条件操作符 -- 逗号表达式 -- []下标访问操作符,()函数调用操作符 -- 常见关键字 -- 指针 -- 结构体
【C语言】 条件操作符 -- 逗号表达式 -- []下标访问操作符,()函数调用操作符 -- 常见关键字 -- 指针 -- 结构体