『C语言进阶』自定义类型详解

news2025/1/11 14:19:19

在这里插入图片描述
🔥博客主页 小羊失眠啦.
🔖系列专栏 C语言LinuxCpolar
❤️感谢大家点赞👍收藏⭐评论✍️


在这里插入图片描述

文章目录

  • 前言
  • 一、结构体
    • 1.1 结构体的基础知识
    • 1.2 结构体的声明
    • 1.3 结构体的特殊声明
    • 1.4 结构体的自引用
    • 1.5 结构体的初始化和访问
    • 1.6 结构体内存对齐
    • 1.7 修改默认对齐数
    • 1.8 结构体传参
  • 二、位段
    • 2.1 什么是位段
    • 2.2 位段的内存分配
    • 2.3 位段的跨平台问题
  • 三、枚举
    • 3.1 枚举类型的定义
    • 3.2 的使用
    • 3.3 枚举的优点
  • 四、联合(共同体)
    • 4.1 联合体的定义
    • 4.2 联合体的特点
    • 4.3 联合体大小的计算

前言

C语言提供了一些由系统已定义好的数据类型,如:int,float,char等,用户可以在程序中用它们定义变量,解决一般的问题,但我们要处理的问题往往比较复杂,只有系统提供的类型还不能满足应用的需求,C语言允许用户根据自己建立一些数据类型,并用它来定义变量,比如结构体,枚举,联合体这些自定义类型

接下来小羊就带铁汁们学习关于结构体,枚举以及联合体的相关内容~~


一、结构体

1.1 结构体的基础知识

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

声明结构体类型的一般形式:

struct 结构体名

​ {

​ //成员变量,是用来描述结构体对象的相关属性

​ 成员列表

​ };

1.2 结构体的声明

1.先声明结构体类型,再定义该类型的变量

struct stu
{			
	char name[20];		
	int stuid[20];		
	int age;
    int height;
};
 
int main()
{
    struct stu s1;
    struct stu s2;
    return 0;
} 

2.在声明类型的同时定义:

struct stu
{			
	char name[20];		
	int stuid[20];		
	int age;
    int height;
}s1,s2;

这种定义结构体的一般形式为:

struct 结构体名

{

​ 成员列表;

}变量名列表;

1.3 结构体的特殊声明

匿名结构体类型

#include<stdio.h>
struct
{
	char a;
	int b;
	double c;
}d;//结构体必须在这里命名,而且只能使用一次

struct
{
	char a;
	int b;
	double c;
}* ps;

int main()
{
	ps = &d;//这里不能这样使用,这是非法的
	//编译器认为等号两边是不同的结构体类型
	return 0;
}

仔细观察上面的这两个结构是不是除了变量名不一样,其他都完全一样

那问题来了,这两个结构体是相同的结构体吗???

答案当然不是,省略掉结构体标签后,编译器会认为这两个结构体是不一样的,即使这两个结构体成员是一样的,编译器也会把上面的两个结构体声明当成完全不同的类型

1.4 结构体的自引用

struct Node
{
     int Data;           
     struct Node* Next; 
};
#include<stdio.h>

typedef struct Node//重命名这个结构体为Node
{
    int Data;            
    struct Node* Next; 
}Node;

int main()
{
    struct Node n1 = { 0 };//写法一
    Node n2 = { 0 };//写法二
    return 0;
}

1.5 结构体的初始化和访问

初始化:

struct Student
{
	int num;
	char name[20];
	char sex[5];
	int age;
}s1 = { 1,"李四","女",19 };


int main()
{
	struct Student s2 = { 2,"张三","男",18 };
	printf("%d %s %s %d\n", s1.num, s1.name, s1.sex, s1.age);
	printf("%d %s %s %d\n", s2.num, s2.name, s2.sex, s2.age);
	return 0;
}

结构体嵌套初始化:

struct Point
{
    int a;
    double b;
};

struct Node
{
	int data;
	struct Point p;
	struct Node* next;
}n1 = { 10, {4,5}, NULL };

结构体访问:

  • p.成员名
  • (*p).成员名
  • p->成员名

1.6 结构体内存对齐

结算结构体的大小,是一个很热门的考点:结构体内存对齐

#include<stdio.h>

struct S1
{
	char s1;
	int i;
	char s2;
};

struct S2
{
	int i;
	char s1;
	char s2;
};

int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

运行结果:

12
8

按照我们现在的所认知,char是一个字节,int是两个字节,所以这两个结构体大小不应该是1+1+4=6个字节吗??但运行起来的结果却跟我们想的不一样,那么就让铁汁们带着疑问往下看~~

我们可以通过宏(offsetof)来观察结构体中各成员变量距起始位置的偏移量

offsetof:计算结构体成员变量相较于结构体起始位置的偏移量

#include<stdio.h>
#include<stddef.h>
struct S1
{
	char s1;
	int i;
	char s2;
};

int main()
{
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", offsetof(struct S1,s1));
	printf("%d\n", offsetof(struct S1, i));
	printf("%d\n", offsetof(struct S1, s2));
	return 0;
}

运行结果:

12
0
4
8

好,接下来小羊来解开小伙伴们的疑惑~

首先得掌握结构体的对齐规则:

  • 第一个成员在与结构体变量偏移量为0的地址处。
  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。 VS中默认的值为8 Linux中没有默认对齐数,对齐数就是成员自身的大小
  • 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

在这里插入图片描述

第一个成员是s1,所以在偏移量为0处,它的对齐数是1,占一个字节;

第二个成员是i,它的对齐数是4,所以从偏移量为4开始,占四个字节;

第一个成员是s2,它的对齐数是1,所以从偏移量为8开始,占一个字节;

为什么存在内存对齐?

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

总体来说:

结构体的内存对齐是拿空间来换取时间的做法,我们让占用空间小的成员尽量集中在一起,就可以节省空间。

1.7 修改默认对齐数

#include <stdio.h>
#pragma pack(2)//设置默认对齐数为2
struct S1
{
    char c1;
    int i;
    char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

int main()
{
    printf("%d\n", sizeof(struct S1));
    return 0;
}

运行结果:

8

在不改变对齐数的情况下,运行结果应该是12

结构体在对齐方式不合适的时候,我们可以自己改变默认对齐数

1.8 结构体传参

#include<stdio.h>
struct S
{
	int a;
	int b;
};
struct S s = { 1,9 };

void Print(struct S* ps)
{
	printf("%d\n", ps->b);
}

int main()
{
	Print(&s);
	return 0;
}

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

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

所以结构体传参的时候,传结构体地址


二、位段

2.1 什么是位段

  1. 位段的成员必须是int、unsigned int或者signed int(还可以是char类型)
  2. 位段的成员后边有一个冒号和一个数字
#include<stdio.h>
struct A
{
	int a : 2;//a需要2个比特位
	int b : 5;//b需要5个比特位
	int c : 10;//c需要10个比特位
	int d : 28;//d需要28个比特位
};//这就是一个位段

int main()
{
	printf("%d\n", sizeof(struct A));
	return 0;
}

运行结果:

8

首先因为a是int类型,所以开辟了4个字节(32个比特位)的空间,a需要2个比特位,还剩30个比特位,刚好b需要5个比特位,就给b,还剩下25个比特位,刚好c需要10个比特位,给c后还剩15个比特位,而d需要28个比特位,还剩下的15个比特位不够,d就只能再开辟一个int类型空间即四个字节,所以一共是8个字节。

这时候相信铁汁们有个这样的疑问?:

那个还剩下来的15个比特位怎么用的?

#include<stdio.h>
struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};

int main()
{
	printf("%d\n", sizeof(struct S));
	return 0;
}

运行结果:

3

所以在这里我们可以猜测VS2022,上一次开辟的空间剩下的不够时,是被遗弃了,并没有再次使用,直接用新开的空间。

2.2 位段的内存分配

  • 位段的成员可以是 int 、unsigned int、signed int 或者是 char (属于整形家族)类型
  • 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
  • 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
struct S
{
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};
 
int main()
{
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;
	return 0;
}

在这里插入图片描述

2.3 位段的跨平台问题

  • int 位段被当成有符号数还是无符号数是不确定的。
  • 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
  • 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  • 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

总结:

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


三、枚举

枚举顾名思义就是列举,把可能的取值列举。(枚举是一个常量)

例如:

一周是从周一到周日,是有限的,可以一一列举

三原色,也可以一一列举

3.1 枚举类型的定义

#include<stdio.h>
enum day//星期
{
	Mon,
	Tues,
	Wed,
	Thir,
	Fri,
	Sta,
	Sun
};

enum Sex//性别
{
	MALE,
	FEMALE = 3,
	SECRET
};

enum Color//颜色
{
	RED,
	GREEN = 89,
	BLUE = 78
};

int main()
{
	enum Day a = Sun;
	printf("%d\n", Mon);
	printf("%d\n", Tues);
	printf("%d\n", Wed);
	printf("\n%d\n", MALE);
	printf("%d\n", FEMALE);
	printf("%d\n", SECRET);
	printf("\n%d\n", RED);
	printf("%d\n", GREEN);
	printf("%d\n", BLUE);
	return 0;
}

运行结果:

0
1
2

0
3
4

0
89
78

以上定义的enum Day,enum Sex,enum Color都是枚举类型,{}中 的内容都是枚举类型的可能取值,也叫枚举常量

这些可能取值都是有值的,默认从0开始,依次加1,也可以在定义时赋值初值

3.2 的使用

enum Color
{
     RED=1,
     GREEN=2,
     BLUE=4
};
 
 
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值

注意:

  • 枚举是一个常量,在定义枚举的时候(无论有没有赋值),不可以对可能取值的值进行改变,例如:Mon=3;(如果想让值发生改变,只能在定义枚举的时候进行赋值改变)

3.3 枚举的优点

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

四、联合(共同体)

4.1 联合体的定义

联合是一种特殊的自定义类型,这种类型定义的变量也包含一系列的成员,特征是这些成员功用一块空间(所以联合也叫共用体)

#include<stdio.h>

union Un
{
	char c;
	int i;
};

int main()
{
	union Un u;
	printf("%d\n", sizeof(u));
	printf("%p\n", &u);
	printf("%p\n", &(u.c));
	printf("%p\n", &(u.i));
	return 0;
}

运行结果:

4
000000406BD4F784
000000406BD4F784
000000406BD4F784

起始地址是一样的,共用一块地址,所以是4个字节

4.2 联合体的特点

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

习题:判断当前计算机的大小端存储:

低位放在低地址的是小端,地位放在高地址的是大端

常规写法:

#include <stdio.h>
 
int cheak_sys()
{
	int a = 1;//00 00 00 01(16进制)
	return *((char*)&a);
}
 
int main()
{
	int ret = 0;
	ret = cheak_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

用联合的方法写:

#include <stdio.h>
 
int cheak_sys()
{
	union Un
	{
		char c;
		int i;
	}u;
	u.i = 1;
	return u.c;
}
 
int main()
{
	int ret = 0;
	ret = cheak_sys();
	if (ret == 1)
		printf("小端\n");
	else
		printf("大端\n");
	return 0;
}

运行结果:

小端

4.3 联合体大小的计算

联合的大小至少是最大成员的大小。

当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。

(和结构体一样)但是注意起始地址是一样的

#include <stdio.h>
union Un1
{
	char c[5];
	int i;
};
union Un2
{
	short c[7];
	int i;
};
int main()
{
	printf("%d\n", sizeof(union Un1));//8
	printf("%d\n", sizeof(union Un2));//16
	return 0;
}

在这里插入图片描述

本次的内容到这里就结束啦。希望大家阅读完可以有所收获,同时也感谢各位铁汁们的支持。文章有任何问题可以在评论区留言,小羊一定认真修改,写出更好的文章~~

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1128253.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity开发过程中的一些小知识点

1、如何查询挂载了指定脚本的游戏物体 可以直接在Hierarchy面板上&#xff0c;搜索想要找的脚本名 2、如何将Unity生成的多个相同游戏物体获得序号 可以使用Unity的API Transform.GetSiblingIndex() 实现。 Transform.GetSiblingIndex()gameobject.idTransform.GetSiblingI…

PDF Reader Pro v2.9.8(pdf编辑阅读器)

PDF Reader Pro是一款PDF阅读和编辑软件&#xff0c;具有以下特点&#xff1a; 界面设计简洁&#xff0c;易于上手。软件界面直观清晰&#xff0c;用户可以轻松浏览文档&#xff0c;编辑注释和填写表单。功能强大&#xff0c;提供了多种PDF处理工具&#xff0c;包括阅读、注释…

高压放大器在扫描显微镜中的应用及优势是什么

随着科技的不断进步&#xff0c;扫描显微镜成为了现代显微镜技术的重要组成部分。它能够提供更高的分辨率、更广的视场和更强的功能&#xff0c;用于研究各种微观结构和材料的特性。而高压放大器在扫描显微镜系统中发挥着至关重要的作用。下面安泰电子Aigtek将详细介绍高压放大…

数据分享 I 第一至第四批专精特新“小巨人”企业数据

数据地址&#xff1a; 专精特新“小巨人”企业数据https://www.xcitybox.com/datamarketview/#/Productpage?id355 基本信息. 数据名称: 第一至第四批专精特新“小巨人”企业数据 数据格式: Shpxlsx 数据时间: 2022年 数据几何类型: 点 数据坐标系: WGS84坐标系 数据字…

c语言进阶部分详解(详细解析自定义类型——枚举,联合(共用体))

上篇文章介绍了结构体相关的内容&#xff0c;大家可以点击链接进行浏览&#xff1a;c语言进阶部分详解&#xff08;详细解析自定义类型——结构体&#xff0c;内存对齐&#xff0c;位段&#xff09;-CSDN博客 各种源码大家可以去我的gitee主页进行查找&#xff1a;唔姆 (Nerow…

Centos使用war文件部署jenkins

部署jenkins所需要的jdk环境如下&#xff1a; 这里下载官网最新的版本&#xff1a; 选择jenkins2.414.3版本&#xff0c;所以jdk环境最低得是java11 安装java11环境 这里直接安装open-jdk yum -y install java-11-openjdk.x86_64 java-11-openjdk-devel.x86_64下载jenkins最新…

python实验12_中文文本分析

实验12&#xff1a;中文文本分析 1.实验目标及要求 &#xff08;1&#xff09;掌握jieba库的常用方法。 &#xff08;2&#xff09;掌握使用WordCloud库绘制词云的方法。 &#xff08;3&#xff09;掌握使用networkx库绘制关系图的方法。 2. 实验主要内容 (1)分析文本“水浒…

ConcurrentHashMap底层具体实现知道吗?实现原理是什么

从这三个方面来回答&#xff1a; ConcurrentHashMap 的整体架构 ConcurrentHashMap 的基本功能 ConcurrentHashMap 在性能方面的优化 ConcurrentHashMap 的整体架构 这个是 ConcurrentHashMap 在 JDK1.8 中的存储结构&#xff0c;它是由数组、单向链表、红黑树组成. 当我们初始…

谈谈你对Spring的理解

谈谈你对Spring的理解 一&#xff0c;什么是Spring 1.介绍 Spring是一个用于开发Java应用程序的工具集合&#xff0c;它提供了许多方便的组件和工具&#xff0c;可以帮助开发者更轻松地构建企业级应用程序。 Spring Framework是Spring的核心部分&#xff0c;它可以帮助开发者…

windows的adb环境安装

需要配置androidhome和一些path

基于情感分析的网络舆情热点分析系统 计算机竞赛

文章目录 0 前言1 课题背景2 数据处理3 文本情感分析3.1 情感分析-词库搭建3.2 文本情感分析实现3.3 建立情感倾向性分析模型 4 数据可视化工具4.1 django框架介绍4.2 ECharts 5 Django使用echarts进行可视化展示5.1 修改setting.py连接mysql数据库5.2 导入数据5.3 使用echarts…

2023年信息科学与工程学院学生科协第二次软件培训

2023年信息科学与工程学院学生科协第二次软件培训 文章目录 2023年信息科学与工程学院学生科协第二次软件培训一维数组数组的概念定义格式一维数组的访问例题&#xff1a;练习题&#xff1a; 数组元素数量一维数组的初始化 二维数组定义格式二维数组的访问二维数组的存储结构二…

实现多余内容变成省略号

实现效果 代码 <p class"item-content">{{ item.content }}</p>样式 .item-content {white-space: nowrap;/* 禁止换行 */overflow: hidden;/* 隐藏溢出部分 */text-overflow: ellipsis;/* 使用省略号表示溢出部分 */ }

21.4 Python 使用GeoIP2地图定位

GeoIP2是一种IP地址定位库&#xff0c;它允许开发人员根据IP地址查找有关位置和地理位置的信息。它使用MaxMind公司的IP地址数据库&#xff0c;并提供一个方便的Python API。GeoIP2可以用于许多不同的应用程序&#xff0c;例如网站分析、广告定位和身份验证。GeoIP2提供了许多不…

达梦管理工具报错“结果集不可更新,请确认查询列是否出自同一张表,并且包含值唯一的列。”

在使用达梦数据库管理工具时&#xff0c;我们测试过程中时常需要更新表数据&#xff0c;有时为了便捷&#xff0c;会直接使用管理工具修改表数据的值&#xff0c;但偶尔会遇到“结果集不可更新&#xff0c;请确认查询列是否出自同一张表&#xff0c;并且包含值唯一的列。”的报…

天津五家受欢迎的python培训机构 python凭什么这么火?

在目前的编程语言中&#xff0c;Python的抽象程度是最高的&#xff0c;是最接近自然语言的&#xff0c;非常容易上手&#xff0c;许多想学编程的小伙伴都从python入手&#xff0c;今天我们就来聊聊关于大家非常困惑的一些点&#xff0c;下面就一起来看看吧。 python火的原因 …

Java的反射(reflection)机制的简单使用

目录 一、定义 二、用途 三、反射基本信息 四、反射相关的类 五、反射示例 六、反射的优点和缺点 一、定义 Java的反射机制是运行时的状态&#xff0c;可以通过反射来调用类里面的属性和方法&#xff0c;私有的属性和方法也可以调用&#xff0c;也可以对它们进行修改。 二…

React 核心与实战2023版

课程亮点: 完整的前后台项目(PC+移动;完成业务;)React 最新企业标准技术栈(React 18 + Redux + ReactRouter + AntD)React + TypeScript (为大型项目奠定了基础)课程内容安排: React 介绍 React 是什么? React 是由Meta公司研发,是一个用于 构建Web和原生交互界面…

升级 Xcode 15模拟器 iOS 17.0 Simulator(21A328) 下载失败

升级 IDE Xcode 15 后本地模拟器 Simulator 全被清空,反复重新尝试 Get 下载频频因网络异常断开而导致失败 ... 注:通过 Get 方式下载一定要保证当前网络环境足够平稳,网络环境不好的情况下该方法几乎成不了 解决办法 Get 方式行不通可以尝试通过 官网 途径先下载 模拟器安装包…

电脑待办事项提醒工具用哪个?

在一个繁忙的办公楼里&#xff0c;人们匆匆忙忙地穿梭着&#xff0c;电脑屏幕前的每个人都有着繁重的工作任务。为了应对这些任务&#xff0c;有人喜欢在纸上列出清单&#xff0c;有人则更愿意在电脑上记录&#xff0c;日常记录待办事项建议大家可以使用提醒工具敬业签。 敬业…