结构体(C保姆级讲解)

news2024/11/25 7:58:08

前言:

        为什么会有结构体,结构体可以用来面熟一个复杂对象,我们知道C语言中有哪些数据类型,有整型,有浮点型,有字符型,但是在生活中,我们需要描述一些比较复杂的东西,比如说一本书,有书名,有价格,有出版社等等,所以我们可以用结构体去描述。

结构体的声明

        一般结构体声明时,形式如图所示:

struct book
{
	int price;
	char name[20];
}p1,p2;

解析:

        struct为关键字,在声明结构体的时候,必须有关键字struct。

        book表示类型,就类似于int、char、float,只不过book是我们自定义的类型而已。

        {}里面的是描述book这个类型里面的成员,称为成员变量

        {}外边的p1,p2是两个定义的变量名,这里可写也可以不用写,在主函数中也可以定义

        类似于:
 

struct book
{
	int price;
	char name[20];
};
int main()
{
	struct book p1;
	struct book p2;
	return 0;
}

放在主函数中定义!

特殊的声明:

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

//匿名结构体类型
struct  //省略了结构体类型
{
	int a;
	char b;
	float c;
}x;

这样声明结构体是可以的

但是,我如果一个源文件中有两个匿名结构体可不可以呢?

答案是不行的,不然初始化的时候不知带是对哪一个结构体进行初始化的。

类似于这样是不可取的:

//匿名结构体类型
struct
{
 int a;
 char b;
 float c;
}x;
struct
{
 int a;
 char b;
 float c;
}a[20], *p;

这里声明了两个匿名结构体。

注:这里两个匿名结构体是相互独立的,谁有谁的地址,当我定义*p后,我用*p = &x是不行滴!!

初始化结构体

        声明好结构体后,接下来就是初始化结构体

struct book
{
	int price;
	char name[20];
};
int main()
{
	struct book a1 = { 3,"lisi" };//一般初始化
    struct book a2[2] = {{4,"zhangsan"},{"10","wangwu"}};//结构体数组初始化
    
	return 0;
}

  结构体也可以嵌套初始化:

struct Stu        //类型声明
{
	char name[15];//名字
	int age;      //年龄
};
struct Node
{
	int data;
	struct Stu p;//嵌套声明
	struct Node* next;
}n1 = { 10, {"lisi",5}, &n1};//嵌套初始化

结构体成员访问:
        

        结构体成员的访问有两种方式,第一种:

#include<stdio.h>
struct book
{
	int price;
	char name[20];
};
int main()
{
	struct book p1 = {10,"lisi"};
	printf("%d\n", p1.price);
	printf("%s\n", p1.name);
	return 0;
}

访问结构体中的成员变量用.去访问。

第二种用指针访问:

#include<stdio.h>
struct book
{
	int price;
	char name[20];
};
int main()
{
	struct book p1 = {10,"lisi"};
	struct book *p = &p1;
	printf("%d\n", p->price);
	printf("%s\n", p->name);
	return 0;
}

访问结构体中的成员变量用->去访问。

结构体传参:

代码如下:

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;
}

有两种方式,在这里通过代码去分析。

可以传地址进去,用指针接收。

也可以传结构体进去,用结构体接收。

结构体大小(内存对齐):
        

        由于结构体是由问我们自己定义的,那么结构体大小该如如何计算?

计算如下结构体大小:
        

struct arr
{
	int a;
	char b;
	char c;
};
int main()
{
	printf("%d\n", sizeof(struct arr));
	return 0;
}

如果直接想,答案应该是4+1+1 = 6;

但是:

答案是8!!

再来看一组:

        

struct arr
{
	char b;
	int a;
	char c;
};
int main()
{
	printf("%d\n", sizeof(struct arr));
	return 0;
}

和第一组相比,我交换了一下int a和char b的位置,答案是多少?

是6?还是8?
答案是:

竟然是12!

为什么呢?

按照结构体的对齐规则:

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

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

2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8

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

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

了解了对齐规则,我们再来看第一个案例,答案为什么是8?

struct arr
{
	int a;
	char b;
	char c;
};
int main()
{
	printf("%d\n", sizeof(struct arr));
	return 0;
}

通过画图讲解:

        

所以答案是8,可以自己画图分析。

继续分析第二个,交换顺序后大小为什么会变?

可以自己练习一个:

struct S3
{
 double d;
 char c;
 int i;
};
printf("%d\n", sizeof(struct S3));

答案应该是多少呢?

答案是16!!

嵌套结构体大小:

        

struct arr
{
	char b;
	char c;
    int a;
};
struct S3
{
	double d;
	char c;
	int i;
	struct arr a;
};
int main()
{
	printf("%d\n", sizeof(struct S3));
	return 0;
}

它的大小是多少呢?

根据内存对齐规则第4条:

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

画图解释:

最后的大小是最大对齐数的整数倍,最大对齐数是8,所以总大小是24。

为什么会存在内存对齐

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

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

总体来说: 结构体的内存对齐是拿空间来换取时间的做法。

修改默认对齐数

        在vs中,默认对齐数是8,当然我们也可以进行修改,用到#pragma预处理指令。

#pragma pack(1)//设置默认对齐数是1
struct arr
{
	int a;
	char b;
	char c;
};
#pragma pack();//取消默认对齐数

可以设置默认对齐数为1,然后计算结构体大小。

#pragma pack(1)
struct arr
{
	char a;
	char b;
	int c;
};
int main()
{
	printf("%d\n", sizeof(struct arr));
}

答案是多少呢?

答案应该是6;

位段:

2.1 什么是位段

     

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

1.位段的成员必须是 int、unsigned int 或signed int 和char(整型家族)。

2.位段的成员名后边有一个冒号和一个数字,单位是比特位。

代码如下:

struct arr
{
	char a : 3;
	char b : 2;
	char c : 3;
};

此时,arr就是一个位段类型。

那么位段类型的大小是多少呢?

此时只占用了1个字节,答案是1;

如果是这样呢

struct arr
{
	char a : 3;
	char b : 6;
	char c : 4;
};
int main()
{
	printf("%d\n", sizeof(struct arr));
}

错误示例:

答案应该是3,不是2!!

枚举

枚举顾名思义就是一一列举。

把可能的取值一一列举。

枚举的定义:
 

关键字是enum!

enum Day//星期
{
 Mon,
 Tues,
 Wed,
 Thur,
 Fri,
 Sat,
 Sun
};
enum Sex//性别
{
 MALE,
 FEMALE,
 SECRET
};
enum Color//颜色
{
 RED,
 GREEN,
 BLUE
};

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

        我们用%d打印一下,看看每个枚举类型中的变量的值。

enum SEX
{
	MALE,
	FEMALE,
	SECRET
};
int main()
{
	printf("%d\n", MALE);
	printf("%d\n", FEMALE);
	printf("%d\n", SECRET);
}

那么这个对应的值可不可以修改呢?

答案是不可以的,因为是枚举常量,常量的值是不可以修改的。

但是在枚举{}内是可以修改的:

enum SEX
{
	MALE = 10,
	FEMALE = 9,
	SECRET = 2
};
int main()
{
	printf("%d\n", MALE);
	printf("%d\n", FEMALE);
	printf("%d\n", SECRET);
	
}

枚举的使用:


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

只能拿枚举常量给枚举常量赋值,才不会出现差异!

枚举的优点:

我们可以使用 #define 定义常量,为什么非要使用枚举?

枚举的优点:

1. 增加代码的可读性和可维护性

2. 和#define定义的标识符比较枚举有类型检查,更加严谨。

3. 防止了命名污染(封装)

4. 便于调试

5. 使用方便,一次可以定义多个常量

联合(共用体)

   联合体用关键字:union

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

        

//联合体的声明
union arr
{
	int a;
	char b;
};
int main()
{
	union arr a1;//联合体定义
	return 0;
}

 联合体共用一块空间,可以看看联合体内这两个成员的地址是否相同?

union arr
{
	int a;
	char b;
};
int main()
{
	union arr a1;//联合体定义
	printf("%p\n", &a1.a);
	printf("%p\n", &a1.b);
	return 0;
}

地址是一样的。

联合体的特点:

联合的成员是共用同一块内存空间的,

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

联合体大小的计算

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

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

例如:

union Un1
{
	char c[5];
	int i;
};
union Un2
{
	short c[7];
	int i;
};
int main()
{
	printf("%d\n", sizeof(union Un1));
	printf("%d\n", sizeof(union Un2));
}

这两个的打印结果是多少呢?

联合体的应用

利用联合体判断大小端。

之前判断大小端的方法:

int Print(int *a)
{
	return *(char*)a;
}
int main()
{
	int a = 1;
	int b = Print(&a);
	if (b == 1)
	{
		printf("小端存储");
	}
	else
		printf("大端存储\n");
	return 0;
}

利用联合体判断:

union arr
{
	int a;
	char b;
};
int main()
{
	union arr a1 ;
	a1.a = 1;
	printf("%d\n", a1.b);
	if (a1.b == 1)
			{
				printf("小端存储");
			}
			else
				printf("大端存储\n");
	return 0;
}

将1存入int型变量中,之后用char类型变量将其取出。

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

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

相关文章

java调用科大讯飞离线语音合成SDK --内附完整项目

科大讯飞语音开放平台基础环境搭建 1.用户注册 注册科大讯飞开放平台账号 2.注册好后先创建一个自己的应用 创建完成后进入应用选择离线语音合成&#xff08;普通版&#xff09;可以看到我们开发需要的SDK,选择windows MSC点击下载。 3.选择你刚刚创建的应用&#xff0c;选择…

磁盘配额的具体操作

磁盘配额&#xff1a; linux的磁盘空间有两个方面&#xff1a;第一个是物理空间&#xff0c;也就是磁盘的容量 第二个inode号耗尽&#xff0c;也无法写入 linux根分区&#xff1a;根分区的空间完全耗尽&#xff0c;服务程序崩溃&#xff0c;系统也无法启动了。 为了防止有人…

Vue2 + Element UI 封装 Table 递归多层级列表头动态

1、在 components 中创建 HeaderTable 文件夹&#xff0c;在创建 ColumnItem.vue 和 index.vue。 如下&#xff1a; 2、index.vue 代码内容&#xff0c;如下&#xff1a; <template><div><el-table:data"dataTableData"style"width: 100%"…

神经网络与深度学习——第3章 线性模型

本文讨论的内容参考自《神经网络与深度学习》https://nndl.github.io/ 第3章 线性模型 线性模型 线性模型&#xff08;Linear Model&#xff09;是机器学习中应用最广泛的模型&#xff0c;指通过样本特征的线性组合来进行预测的模型&#xff0c;给定一个 D D D维样本 x [ x …

定时器与PWM的LED控制

目录 一、基础概念定时器定时器类型定时器特性 PWM定义占空比原理 二、实验1.LED周期性亮灭定时器TIM2配置GPIO引脚设置工程相关参数配置Keil编写程序 2.LED呼吸灯(PWM)呼吸灯原理Keil编写程序Keil虚拟示波器&#xff0c;观察 PWM输出波形设置点击setup&#xff0c;并设置观察引…

贪心算法拓展(反悔贪心)

相信大家对贪心算法已经见怪不怪了&#xff0c;但是一旦我们的决策条件会随着我们的步骤变化&#xff0c;我们该怎么办呢&#xff1f;有没有什么方法可以反悔呢&#xff1f; 今天就来讲可以后悔的贪心算法&#xff0c;反悔贪心。 https://www.luogu.com.cn/problem/CF865Dhttp…

[图的搜索]5.图解狄克斯特拉算法及其代码演示

狄克斯特拉算法 与前面提到的贝尔曼-福特算法类似&#xff0c;狄克斯特拉&#xff08;Dijkstra&#xff09;算法也是求解最短路径问题的算法&#xff0c;使用它可以求得从起点到终点的路径中权重总和最小的那条路径路径。 图解 01 这里我们设A为起点、G为终点&#xff0c;来讲…

“揭秘乐园通行证:Spring JWT的魔法之旅

嗨&#xff0c;我将带你深入了解如何利用JWT打造一个既安全又高效的网络乐园。从基础概念到实战技巧&#xff0c;再到安全策略&#xff0c;每一步都充满惊喜。你将学会如何为乐园设置无状态的门票系统&#xff0c;如何通过RBAC和ABAC确保游客安全&#xff0c;以及如何在微服务架…

统计信号处理-匹配滤波器实现与验证(matlab仿真)

什么是匹配滤波器 匹配滤波器是一种信号处理技术&#xff0c;它用于从噪声中提取信号&#xff0c;特别是在信号与噪声比率较低的情况下。匹配滤波器之所以存在&#xff0c;是因为它在信号检测和估计方面具有几个关键的优势&#xff1a; 最大化信噪比&#xff1a;匹配滤波器设计…

数字化校园建设让学习更加广阔

校园构建数字化校园的亮点是什么&#xff1f;校园以智能服务、才智办理、数字讲堂为中心内容的智慧校园建造&#xff0c;不只使师生作业和日子更高效&#xff0c;并且使他们有更多的时刻投入到智能教育和智能学习中去&#xff0c;进步教育质量&#xff0c;使学生走出校门时紧跟…

项目管理主要文档介绍

1、商业论证&#xff1a;一般由项目发起人创建&#xff0c;用于论证项目是否对组织有财务方面的收益。商业论证创建于项日开始之前&#xff0c;用于判断项目是否需要被开展。 2、项目章程&#xff1a;一般由项日经理创建,并由发起入和关键相关力提供输人&#xff0c;最后经项目…

MATLAB函数模块光显示zeros/poles怎么办?

出现下面这种图了怎么办&#xff1f;是做错了吗&#xff1f; 这种图就是它显示不完整了&#xff0c;把它拉大点就可以完全显示了。

【机器学习】深入探索机器学习:利用机器学习探索股票价格预测的新路径

❀机器学习 &#x1f4d2;1. 引言&#x1f4d2;2. 多种机器学习算法的应用&#x1f4d2;3. 机器学习在股票价格预测中的应用现状&#x1f389;数据收集与预处理&#x1f389;模型构建与训练&#x1f308;模型评估与预测&#x1f31e;模型评估&#x1f319;模型预测⭐注意事项 &…

检定记录内容解析:非红外二氧化硫气体检测仪的维护与验证

在工业生产与环境保护中&#xff0c;二氧化硫作为一种常见的有害气体&#xff0c;其浓度的监测和控制显得尤为重要。 非红外二氧化硫气体检测仪以其独特的检测原理和高灵敏度&#xff0c;在二氧化硫监测领域发挥着不可或缺的作用。 在这篇文章中&#xff0c;佰德将详细介绍非…

【原创】springboot+mysql校园通讯录管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

MongoDB CRUD操作:插入文档

MongoDB CRUD操作&#xff1a;插入文档 文章目录 MongoDB CRUD操作&#xff1a;插入文档使用MongoDB Atlas UI插入文档插入单个文档插入多个文档插入行为自动创建集合_id字段原子性写确认 在MongoDB中插入文档的集中方式&#xff1a; 使用编程语言提供的驱动程序&#xff0c;在…

【第七节】C++的STL基本使用

目录 前言 一、STL简介 1.1 STL基本概念 1.2 STL六大组件 1.3 STL优点 二、STL三大组件 2.1 容器 2.2 算法 2.3 迭代器 三、STL常见的容器 3.1 string容器 3.1.1 string容器基本概念 3.1.2 string容器的常用操作 3.1.2.1 string 构造函数 3.1.2.2 string 基本赋…

U盘格式化怎么操作?快来学这4种法

U盘格式化怎么操作&#xff1f;在计算机领域中&#xff0c;格式化通常指对存储设备&#xff08;如硬盘、U盘&#xff09;进行格式化操作&#xff0c;清空其中的数据并重新建立文件系统&#xff0c;以便进行数据存储和管理。 U盘格式化一共有哪些方法&#xff1f;在格式化U盘之…

SJ701-II安全帽耐冲击穿刺测试仪

一、主要用途 主要用于安全帽耐冲击性能和耐穿刺性能试验。 二、仪器特征 整机创新全新结构&#xff0c;并获得国家专利&#xff08;专利号201420182139.8&#xff09; 1、整机结构&#xff1a;首创采用欧标型材组装成型&#xff0c;内藏式线路折叠式结构。结构美观耐用&…

07.爬虫---使用session发送请求

07.使用session发送请求 1.目标网站2.代码实现 1.目标网站 我们以这个网站作为目标网站 http://www.360doc.com/ 注册用户 注册后从登录界面获取到这些信息 2.代码实现 import requestssession requests.Session() url http://www.360doc.com/ajax/login/login.ashx u…