结构体(C保姆级讲解)

前言:

        为什么会有结构体,结构体可以用来面熟一个复杂对象,我们知道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.mfbz.cn/a/664574.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vitalik:Layer2 是以太坊社区文化的延伸

原文标题&#xff1a;《Layer 2s as cultural extensions of Ethereum》 撰文&#xff1a;Vitalik Buterin&#xff0c;以太坊联合创始人 编译&#xff1a;Chris&#xff0c;Techub News 在我最近关于 L1 和 L2 扩容差异的文章中&#xff0c;我最终得出的结论是&#xff0c; …

java——网络编程套接字

T04BF &#x1f44b;专栏: 算法|JAVA|MySQL|C语言 &#x1faf5; 今天你敲代码了吗 目录 2.网络编程套接字2.1 socket api2.2 TCP和UDP之间的区别有连接 vs 无连接可靠传输 vs 不可靠传输面向字节流vs面向数据报全双工 vs 半双工 2.3UDP数据报套接字编程UDP 回显服务器UDP客户端…

Mariadb操作命令指南

MariaDB简介 ​ 以下内容仅是站长或网友个人学习笔记、总结和研究收藏。不保证正确性&#xff0c;因使用而带来的风险与本站无关&#xff01; 数据库应用程序与主应用程序分开存在&#xff0c;并存储数据集合。 每个数据库都使用一个或多个API来创建&#xff0c;访问&#xf…

ch4网络层---计算机网络期末复习(持续更新中)

网络层概述 将分组从发送方主机传送到接收方主机 发送方将运输层数据段封装成分组 接收方将分组解封装后将数据段递交给运输层网络层协议存在于每台主机和路由器上 路由器检查所有经过它的IP分组的分组头 注意路由器只有3层(网络层、链路层、物理层) 网络层提供的服务 一…

ArcGIS教程(02):创建多模式网络数据集

启动“新建网络数据集”向导 命名网络并选择源要素类 输入网络数据集名称【ParisMultimodal_ND】&#xff0c;点击【下一页】 点击【全选】网络数据集中的要素类 点击【下一页】 设置连通性和高程策略 点击【连通性】 Metro_Entrances 的每个要素与街道要素类的折点重…

企业文件加密:保障知识产权与客户隐私

在数字化时代&#xff0c;企业文件的安全成为了保护知识产权和客户隐私的关键。随着网络攻击和数据泄露事件的日益增多&#xff0c;企业必须采取强有力的措施来确保其敏感信息的安全。文件加密技术作为一项重要的数据保护手段&#xff0c;对于维护企业的竞争力和客户信任至关重…

第八十三节 Java面向对象设计 - Java方法重载

Java面向对象设计 - Java方法重载 在同一类中具有多个具有相同名称的方法称为方法重载。 类中具有相同名称的方法可以是声明的方法&#xff0c;继承的方法或两者的组合。 重载方法必须具有不同数量的参数&#xff0c;不同类型的参数或两者。 方法的返回类型&#xff0c;访问…

数字孪生技术为何备受各行业青睐?

数字孪生技术近年来在各行业中受到越来越多的重视&#xff0c;这是因为它具备了显著的优势和广泛的应用前景。数字孪生是指利用数字化技术&#xff0c;在虚拟空间中创建一个与现实世界对应的虚拟模型&#xff0c;通过数据的实时交互和反馈&#xff0c;实现对物理实体的模拟和监…

3d模型移动中心点偏移太远怎么解决?---模大狮模型网

在3D建模和动画制作中&#xff0c;移动模型时确保中心点的准确性至关重要。然而&#xff0c;有时候在移动模型时&#xff0c;中心点可能会偏移得太远&#xff0c;导致操作不便甚至影响到后续的工作流程。本文将介绍在3D模型移动中心点偏移太远时的常见原因&#xff0c;并提供解…

Linux系统编程——动静态库

目录 一&#xff0c;关于动静态库 1.1 什么是库&#xff1f; 1.2 认识动静态库 1.3 动静态库特征 二&#xff0c;静态库 2.1 制作静态库 2.2 使用静态库 三&#xff0c;动态库 3.1 制作动态库 3.2 使用动态库一些问题 3.3 正确使用动态库三种方法 3.3.1 方法一&…

sprintboot中拦截器的使用

文章目录 1. 为什么要使用拦截器2.拦截器的注册3.创建一个登录拦截器 1. 为什么要使用拦截器 1.权限检查&#xff1a;进入程序判断是否登录&#xff0c;没有登录&#xff0c;直接返回跳转到登录界面 2.性能监控&#xff1a;通过拦截器在进入处理程序之前记录开始时间&#xff…

java家政上门系统源码,一套同城预约、上门服务的家政系统源码

一款同城预约、上门服务的家政系统源码&#xff0c;用户端、服务端、管理端各端相互依赖又相互独立&#xff0c;支持选择项目、选择服务人员的下单方式&#xff0c;支持多城市并且设置每个城市专属服务项目。 技术架构&#xff1a;java1.8springboot mysql htmlThymeleaf uni…

【AI+知识库问答】沉浸式体验了解 AI知识库问答fastGPT

之前写过一篇文章 【AI本地知识库】个人整理的几种常见本地知识库技术方案 &#xff0c; 由于当时主要是针对AI本地知识库&#xff0c; 所以没列fastGPT。 最近经常刷到fastGPT&#xff0c;这里单独水一篇。 FastGPT 是一个基于 LLM 大语言模型的知识库问答系统&#xff0c;…

UML静态图-对象图

概述 静态图包含类图、对象图和包图的主要目的是在系统详细设计阶段&#xff0c;帮助系统设计人员以一种可视化的方式来理解系统的内部结构和代码结构&#xff0c;包括类的细节、类的属性和操作、类的依赖关系和调用关系、类的包和包的依赖关系。 对象图与类图之间的关系&…

centos7 openssh9.7p 制作rpm包

centos7 openssh9.7p 制作rpm包 下载源码包&#xff1a;通过git开源打包源码准备编译打包环境编译打包上传rpm包到需要更新的服务器,并更新 下载源码包&#xff1a; 一般只用ssh源码就可以了 cd /root wget https://cdn.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssh-9.7p…

UI线程和工作线程

引用&#xff1a;windows程序员面试指南 工作线程 只处理逻辑的线程&#xff0c;例如&#xff1a;启动一个线程&#xff0c;用来做一个复杂的计算&#xff0c;计算完成之后&#xff0c;此线程就自动退出&#xff0c;这种线程称为工作线程 UI线程 Windows应用程序一般由窗口…

ST - 如何利用SCR轻松驱动AC/DC转换器启动?

过去十年&#xff0c;新装服务器的市场需求增长迅猛&#xff0c;2015到2022年复合年均增长率达到了11%。拉动市场增长的动力主要来自以下几个方面&#xff1a;首先&#xff0c;个人文件无纸化和企业办公数字化进程加快&#xff1b;其次&#xff0c;健康危机期间的居家办公&…

【LeetCode算法】第100题:相同的树

目录 一、题目描述 二、初次解答 三、官方解法 四、总结 一、题目描述 二、初次解答 1. 思路&#xff1a;二叉树的先序遍历。采用递归的先序遍历方法&#xff0c;首先访问根节点若不同则返回false&#xff0c;其次访问左子树和右子树。在访问左右子树时&#xff0c;需要注意…

大模型算法办备案全网最详细说明(+附件)

已成功备案产品&#xff08;近130家&#xff0c;不包括审核中的&#xff09; 一、大模型算法备案的强制性 二、生成式人工智能(大语言模型)安全评估要点 三、大模型备案必备材料重点说明 四、大模型备案填报流程 五、大模型备案时间成本对比 六、备案建议 附录、过程性材料 一…

977. 有序数组的平方 - 力扣

1. 题目 给你一个按 非递减顺序 排序的整数数组 nums&#xff0c;返回 每个数字的平方 组成的新数组&#xff0c;要求也按 非递减顺序 排序。 2. 示例 3. 分析 我们当然可以遍历数组平方元素&#xff0c;然后再使用sort排序&#xff0c;但这里时间复杂度就为 O(logN) 了。 我…