【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

相关文章
|
2月前
|
C语言
【数据结构】栈和队列(c语言实现)(附源码)
本文介绍了栈和队列两种数据结构。栈是一种只能在一端进行插入和删除操作的线性表,遵循“先进后出”原则;队列则在一端插入、另一端删除,遵循“先进先出”原则。文章详细讲解了栈和队列的结构定义、方法声明及实现,并提供了完整的代码示例。栈和队列在实际应用中非常广泛,如二叉树的层序遍历和快速排序的非递归实现等。
284 9
|
2月前
|
存储 搜索推荐 算法
【数据结构】树型结构详解 + 堆的实现(c语言)(附源码)
本文介绍了树和二叉树的基本概念及结构,重点讲解了堆这一重要的数据结构。堆是一种特殊的完全二叉树,常用于实现优先队列和高效的排序算法(如堆排序)。文章详细描述了堆的性质、存储方式及其实现方法,包括插入、删除和取堆顶数据等操作的具体实现。通过这些内容,读者可以全面了解堆的原理和应用。
126 16
|
2月前
|
搜索推荐 算法 C语言
【排序算法】八大排序(下)(c语言实现)(附源码)
本文继续学习并实现了八大排序算法中的后四种:堆排序、快速排序、归并排序和计数排序。详细介绍了每种排序算法的原理、步骤和代码实现,并通过测试数据展示了它们的性能表现。堆排序利用堆的特性进行排序,快速排序通过递归和多种划分方法实现高效排序,归并排序通过分治法将问题分解后再合并,计数排序则通过统计每个元素的出现次数实现非比较排序。最后,文章还对比了这些排序算法在处理一百万个整形数据时的运行时间,帮助读者了解不同算法的优劣。
170 7
|
2月前
|
搜索推荐 算法 C语言
【排序算法】八大排序(上)(c语言实现)(附源码)
本文介绍了四种常见的排序算法:冒泡排序、选择排序、插入排序和希尔排序。通过具体的代码实现和测试数据,详细解释了每种算法的工作原理和性能特点。冒泡排序通过不断交换相邻元素来排序,选择排序通过选择最小元素进行交换,插入排序通过逐步插入元素到已排序部分,而希尔排序则是插入排序的改进版,通过预排序使数据更接近有序,从而提高效率。文章最后总结了这四种算法的空间和时间复杂度,以及它们的稳定性。
139 8
|
2月前
|
C语言
【数据结构】二叉树(c语言)(附源码)
本文介绍了如何使用链式结构实现二叉树的基本功能,包括前序、中序、后序和层序遍历,统计节点个数和树的高度,查找节点,判断是否为完全二叉树,以及销毁二叉树。通过手动创建一棵二叉树,详细讲解了每个功能的实现方法和代码示例,帮助读者深入理解递归和数据结构的应用。
171 8
|
2月前
|
C语言 Windows
C语言课设项目之2048游戏源码
C语言课设项目之2048游戏源码,可作为课程设计项目参考,代码有详细的注释,另外编译可运行文件也已经打包,windows电脑双击即可运行效果
46 1
|
2月前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
137 4
|
2月前
|
存储 C语言
【数据结构】顺序表(c语言实现)(附源码)
本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
98 3
|
3月前
|
安全 编译器 Linux
【c语言】轻松拿捏自定义类型
本文介绍了C语言中的三种自定义类型:结构体、联合体和枚举类型。结构体可以包含多个不同类型的成员,支持自引用和内存对齐。联合体的所有成员共享同一块内存,适用于判断机器的大小端。枚举类型用于列举固定值,增加代码的可读性和安全性。文中详细讲解了每种类型的声明、特点和使用方法,并提供了示例代码。
34 3
|
3月前
|
存储 C语言 C++
深入C语言,发现多样的数据之枚举和联合体
深入C语言,发现多样的数据之枚举和联合体
深入C语言,发现多样的数据之枚举和联合体

热门文章

最新文章