【C语言进阶】C语言进阶教程:利用结构体、联合体和枚举自定义数据类型

📝个人主页🌹:Eternity._

⏩收录专栏⏪:C语言 " 登神长阶 "

🤡往期回顾🤡:C语言内存管理函数

🌹🌹期待您的关注 🌹🌹


❀C语言自定义类型


前言:在C语言的浩瀚宇宙中,自定义类型如同璀璨星辰,为开发者们提供了无限的可能性和创造力。作为一门历史悠久且功能强大的编程语言,C语言不仅以其接近硬件的特性和高效的执行效率著称,更以其灵活的数据结构定义方式赢得了广泛的赞誉。而自定义类型,正是这一灵活性的集中体现

当我们深入C语言的编程世界时,会发现仅仅依靠基本数据类型(如int、float、char等)往往难以满足复杂问题的需求。这时,自定义类型便成为了我们手中的利器。通过结构体(struct)、联合体(union)、枚举(enum)等高级特性,我们可以根据实际需求,设计出既符合逻辑又便于管理的数据结构。这些自定义类型不仅能够帮助我们更好地组织代码,提高代码的可读性和可维护性,还能在性能优化、内存管理等方面发挥重要作用

本文旨在通过深入浅出的方式,带领读者走进C语言自定义类型的世界。我们将从基础概念讲起,逐步深入到结构体、联合体、枚举等自定义类型的定义、使用及优化技巧。通过丰富的实例和详细的解析,帮助读者掌握自定义类型的设计方法和实现技巧,从而在C语言编程的道路上迈出坚实的一步

让我们一起,在C语言的海洋中扬帆起航,探索未知,创造未来!


📒1. 结构体

C语言中的结构体(Struct)是一种用户自定义的数据类型,它允许你将不同类型的数据项组合成一个单一的类型。结构体在C语言中非常有用,特别是在处理复杂的数据时,比如表示一个学生的信息(包括姓名、年龄、学号等)或者一个产品的详细信息(包括名称、价格、库存量等)


🌞结构体的声明

c 复制代码
struct tag
{
	member-list;
}variable-list;

学生的信息结构体代码示例 (C语言):

c 复制代码
struct Student
{
	char name[20];//名字
	int age;//年龄
	char id[20];//学号
};

特殊的声明

匿名结构体类型代码示例 (C语言):

c 复制代码
struct // 省略掉了结构体标签
{
	int a;
	char b;
	float c;
}x;

struct // 省略掉了结构体标签
{
	int a;
	char b;
	float c;
}a[20], *p;

注意:编译器会把上面的两个声明当成完全不同的两个类型


结构的自引用

在结构中包含一个类型为该结构本身的成员是被允许的

c 复制代码
struct Node
{
	int data;
	struct Node* next;
};

但是我们通常会用typedef来重命名,虽然将结构体重命名了,在结构体内部定义时还是要用原来的名称

c 复制代码
typedef struct Node
{
	int data;
	struct Node* next; // 此处是struct Node* 而不是 Node*
}Node;

🌙结构体变量的定义

c 复制代码
struct Point
{
	int x;
	int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = { 1, 2 };

struct Stu //类型声明
{
	char name[15];//名字
	int age; //年龄
};
struct Stu s = { "pxt", 20 };//初始化

struct Node
{
	int data;
	struct Point p;
	struct Node* next;
}n1 = { 10, {4,5}, NULL }; //结构体嵌套初始化
struct Node n2 = { 20, {5, 6}, NULL };//结构体嵌套初始化

⭐结构体内存对齐

来计算一下结构体的大小

来计算一下结构体的大小如果不了解的话可能会觉得是

6 6 13

为什么最终结果会是这样呢?

这就要掌握首先得掌握结构体的对其原则







为什么存在内存对齐?

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

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

修改默认对齐数

#pragma :修改默认对齐数

c 复制代码
#pragma pack(8)//设置默认对齐数为8
struct S1
{
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

#pragma pack(1)//设置默认对齐数为1
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;
}

🌄结构体传参

结构体传参有两种,传值调用和传址调用

两种方法都是可行的,但是也有区别

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


📚2. 位段

我们在之前初步了解结构体的时候简单介绍了一下位段,这里我们就不继续介绍了,不太清楚的可以回头了解一下


🍁位段的应用


📜3. 枚举

C语言中的枚举(Enumeration)是一种基本的数据类型,它允许程序员为整数指定一个更易读的名字。枚举类型本质上是一种特殊的整型,但它使得代码更加清晰易懂,因为它为整数值赋予了语义上的含义。枚举通过关键字enum来定义

枚举类型的定义:

c 复制代码
enum Color//颜色
{
	RED, // 枚举常量
	GREEN,
	BLUE
};

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

这些枚举常量都是有值的,默认从0开始,一次递增1,当然在定义的时候也可以赋初值

c 复制代码
enum Color//颜色
{
	RED = 1,
	GREEN = 3,
	BLUE = 5
};

枚举的优点

  • 增加代码的可读性和可维护性
  • 和#define定义的标识符比较枚举有类型检查,更加严谨
  • 防止了命名污染(封装)
  • 便于调试
  • 使用方便,一次可以定义多个常量

枚举的使用

c 复制代码
enum Color//颜色
{
	RED = 1,
	GREEN = 2,
	BLUE = 4
};
int main()
{
	enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
	return 0;
}

📝4. 联合体

C语言中的联合体(Union)是一种特殊的数据类型,它允许在相同的内存位置存储不同的数据类型。联合体提供了一种方式来存储可能具有不同大小和数据类型但同时在任何时候只使用一个的数据。这意味着,联合体中的所有成员共享同一块内存区域,这块区域的大小足以存储联合体中最长的成员

c 复制代码
//联合类型的声明
union Un
{
	char c;
	int i;
};

int main()
{
	//联合变量的定义
	union Un un;
	//计算连个变量的大小
	printf("%d\n", sizeof(un));
	return 0;
}

联合的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)

c 复制代码
union Un
{
	int i;
	char c;
};

int main()
{
	union Un un;
	// 下面输出的结果是一样的吗?
	printf("%d\n", &(un.i));
	printf("%d\n", &(un.c));
	//下面输出的结果是什么?
	un.i = 0x11223344;
	un.c = 0x55;
	printf("%x\n", un.i);
	return 0;
}

首先两个成员的地址输出来是一样的,其次un.c会和un.i共用一块空间,un.c覆盖了un.i的一部分


联合大小的计算

  • 联合的大小至少是最大成员的大小
  • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
c 复制代码
union Un1
{
	char c[5]; // 5 bytes
	int i;
};
union Un2
{
	short c[7]; // 7 * 2 = 14 bytes 
	int i;
};
int main()
{
	//下面输出的结果是什么?
	printf("%d\n", sizeof(union Un1)); // 8
	printf("%d\n", sizeof(union Un2)); // 16
	return 0;
}

📖5. 总结

随着我们对C语言自定义类型探索的深入,我们不难发现,这一特性不仅是C语言灵活性和强大功能的重要体现,更是编程艺术中不可或缺的一部分。通过自定义类型,我们能够构建出既符合实际需求又高效运行的数据结构,为复杂问题的解决提供了有力的支持

我希望每一位读者都能珍惜这次学习C语言自定义类型的机会,不仅要在技术上有所收获,更要在思维方式和解决问题的能力上有所提升。愿你们在未来的编程道路上,能够灵活运用自定义类型这一利器,创造出更加精彩和高效的程序作品

希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

相关推荐
霍格沃兹测试开发学社测试人社区7 分钟前
软件测试学习笔记丨Flask操作数据库-数据库和表的管理
软件测试·笔记·测试开发·学习·flask
今天我又学废了24 分钟前
Scala学习记录,List
学习
杨荧28 分钟前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
白子寰35 分钟前
【C++打怪之路Lv14】- “多态“篇
开发语言·c++
王俊山IT1 小时前
C++学习笔记----10、模块、头文件及各种主题(一)---- 模块(5)
开发语言·c++·笔记·学习
为将者,自当识天晓地。1 小时前
c++多线程
java·开发语言
小政爱学习!1 小时前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
k09331 小时前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
神奇夜光杯1 小时前
Python酷库之旅-第三方库Pandas(202)
开发语言·人工智能·python·excel·pandas·标准库及第三方库·学习与成长
Themberfue1 小时前
Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized
java·开发语言·线程·多线程·synchronized·