C语言自定义类型:结构体

结构体简介:

c语言里int 、float、double、等等类型来表示一个对象,但有时也有未能表达的对象,比如表示一个人的类型,这个类型里有人的身高、体重、年龄等等,这就需要很多个类型来拼凑,这就很不方便。于是,c语言就提供了一个新的自定义类型结构体,可以由自己的需求定义,它是由基本类型组成(可以是不同类型,也可以是相同类型)是一个集合,比如,一个结构体里可以有int、floa、char、int[num]等等,他和数组一样都是一个集合,但不同的是数组只能由相同类型的数据构成,结构体可以有不同的类型构成。

结构体的声明:

1.每一个结构体成员后面必须有一个;(分号),注意这个分号是不能省略的

2.结构体的成员最少一个,不可以一个成员也没有

像上面这种定义就是错误的

3.末尾有一个分号,也不能去掉

举例:

struct Stu
{
	char name[20];//姓名
	int age;      //年龄
	char sex[5];  //性别
	char id[20];  //学号
};  //注意分号不能掉

结构体成员访问操作符:

. 操作符: 结构体变量 . 成员名

->操作符: 结构体指针->成员名

结构体变量的创建和初始化:

结构体变量的创建

结构体变量的创建和其他数据类型一样,类型+变量名(正常创建),但不同的是他也可以在定义的时候就创建

结构体变量的创建可以在定义的时候就创建

正常创建

结构体变量的初始化(可以在创建的时候初始化,使用初始化列表),可以按照顺序初始化,也可以使用操作符指定初始化

按照顺序初始化

#include <stdio.h>
struct Stu
{
	char name[20];
	int age;
	char sex[5];
	char id[20];
};
int main()
{
	struct Stu _stu = { "wang",18,"man",202415789 };

	return 0;
}

指定初始化(使用初始化列表)

struct Stu
{
	char name[20];
	int age;
	char sex[5];
	char id[20];
};
int main()
{
	struct Stu _stu = { .age = 18,.id = "21456",.name = "wang",.sex = "man" };

	
	return 0;
}

初始化注意点

关于基本类型可以直接使用=操作符,可以不使用初始化列表

对于数组,不能直接向上面这样初始化

上面这两种初始化方法都是错误的:

第一个拿到的是数组id首元素的地址,数组的地址是一个常量地址,他的定义是(数组元素类型)int* const ,const加在*号的后面修饰,修饰的是指针本身(不是指针指向的对象),指针本身不能被改变。

第二种有两个错误,首先拿到的是数组的第20个元素位置,char(字符类型),而"wang"是一个字符串类型,把一个字符串类型赋值给一个字符类型,这属于类型不匹配错误。其次,数组的长度为20,第一个下标为0,最后一个下标为19,数组的下标范围为0-19,没有20下标,这属于数组越界错误,访问了不属于你空间的数据。

结构体的特殊声明(匿名声明)

如图,上面这种代码并没有结构体类型名,但一样可以创建变量,这种声明的方式叫匿名申明,是合法的。

如图,上面代码有错误吗?虽然两个结构体的匿名申明是合法的,并前结构体成员也是一模一样,但其实编译器会把这两种结构体认为是两个不同的类型。因为是匿名申明,所以编译器也不知道,所以就会自动认为这是两种不同的类型。

匿名申明一般的使用场景是只使用一次,他只能申明的时候创建变量,因为他没有结构体类型名,使用很局限。

结构体的自引用

结构体不能自己包含自己

看上去很像递归,但其实大错特错,自己包含自己,又不像递归有限制条件,那他的的内存多大呢?无限大吗?所以这是不合法的。

像这种可以(顺序表),它包含了一个自己类型的指针,指针的大小为4/8,所以他是合法的。

结构体的重命名

在申明的时候可以进行重命名,这样子在创建变量的时候就很方便。

上面的代码是非法的,因为编译器在翻译代码是顺序执行的,所以在申明的时候就不能使用(c语言不能,但是c++中可以,因为c++中升级成了类)

结构体内存(内存对齐)

结构体的内存计算,并不只是简单的成员变量内存相加,而是有一个对齐规则,为了高效读取。

内存对齐

对齐知识

偏移量:结构体中的某个成员相对于结构体其实地址的字节偏移量,偏移量从0开始,规定第一个成员对齐到偏移量为0的地址处

对齐数:编译器中默认的默认对齐数与该成员变量大小的较小值,默认对齐数一般看架构32位/64位,每一个成员变量都有一个自己的对齐数,所以对齐数有最大和最小概念之分。

vs中32位,默认对齐数是4字节,64位下则是8字节,这和机器传输的线数有关。拿32位机器举例:32位机器可看作成由32根线组成,由32根线传输数据,32位即32bit,一共4个字节,所以一次可以传输4个字节(这里不代表只能读取4个字节,(理论上)也可以读取1个字节,这里是拿空间换时间,为了效率),所以把默认对齐数设置成为4个字节,可以使得读取数据时最高效。

Linux下的gcc编译器没有默认对齐数

对齐规则

对齐规则分为成员对齐和结构体整体对齐

成员对齐:规定第一个成员对齐到偏移量为0的地址处,其他成员对齐到对齐数的整数倍地址处

结构其整齐对齐:结构体的内存大小必须为最大对齐数的整数倍(每一个成员变量都有一个自己的对齐数

如图()

如下分析

这里浪费了2个字节,是拿空间换时间

为什么存在内存对齐

平台原因

不是所有的平台都能访问任意地址上的任意数据的,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

性能原因

上面也提到过,是拿空间换时间,数据结构(尤其是栈),应该尽可能的在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次的内存访问;而对齐的内存仅需要一次访问。假设一个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证double类型的数据的地址都对齐成8的倍数,那么就可以用一个内存操作读或者写值了。否则,我们可能需要执行两此内存访问,因为对象可能被分放在两个8字节的内存块中。

以下是vs中的32位机器演示,默认对齐数为4字节

修改默认对齐数( 预处理指令)

如果你不想浪费空间,也可以修改默认对齐数,但效率可能会下降

#pragma pack(num) 修改默认对齐数为num

#pragma pack() 还原为默认对齐数

#include <stdio.h>
int main()
{
	
#pragma pack(1)//设置默认对齐数为1
	struct C
	{
		char c1;
		int i;
		char c2;
	}_s2;
#pragma pack()//还原为默认对其数
	struct S
	{
		char c1;
		int i;
		char c2;
	}_s1;
	printf("%zd\n", sizeof(_s2));
	printf("%zd\n", sizeof(_s1));

	return 0;
}

建议修改默认对齐数尽量为2的倍数,为了效率的保障

结构体传参

#include <stdio.h>
//结构体传参
struct S
{
	int data[1000];
	int num;
}s = { {1,2,3,4},1000 };


//结构体传参 
void print1(struct S s)
{
	printf("%zd\n", s.num);
}
void print2(struct S* ps)
{
	printf("%zd\n", ps->num);
}

int main()
{
	print1(s);
	print2(&s);

	return 0;
}

上面这个代码,那个函数效率更好,肯定是print2。

从效率看:print1使用结构体传参,会给形参开辟一块S类型结构大小的内存空间,在拷贝赋值,而print2函数,则是开辟一块4/8字节的内存,用一个结构体指针接受变量s地址,直接访问结构体变量s。

从功能看:传结构体能做的事,指针肯定能做,但指针能做的事,传结构体未必能做,传指针可以改边实参,但传一个结构体就做不到。

总结,结构体在传参的时候可以优先考虑结构体指针传参

结构体实现位段

位段:是 C 语言中一种特殊的结构体成员,用于将一个整型变量按位划分为多个独立的段,每个段可以单独存储数据。位段的定义方式是通过在结构体中使用 :位数 来指定每个成员占用的二进制位数。所谓位段,位就是指计算中最小的单位bit,段就是指一个成员变量。

位段的内存分配不同于不同的结构体内存对齐,而是根据你的需求开辟指定的位(bit),能省下很多空间,但不代表完全按照你的需求开辟空间,内存分配从左向右还是从右向左开始,一次不够会共用内存,还是另外开辟一块内存存储这些都是不确定的,c语言并没有明确规定,所以位段是不支持跨平台的。

位段的内存分配

如图演示

位段使用注意点

上面的介绍说明了,位段的成员可以只占用几个bit位,这超过计算机中内存中的最小的分配单元字节(byte),所以是不能对位段的成员使用取地址的,因为一个字节才有一个地址,几个bit位是没有地址概念的。

上面的这种用法是错误的,它错误的使用了对位段成员取地址,而因该直接赋值

如下图,是正确的给位段成员赋值

位段的应用

位段的使用其实非常小众,运用非常少,比较经典的运用就是网络协议中IP数据报的格式

如下图(是我引用别人的博客图)

如何节省结构体的内存(当内存比较珍贵时)

1.修改默认默认对齐数(上面介绍过)

2.使用位段(使用比较少)

3.尽量把内存小的成员变量集中在一起

s1和s2的内存一样吗,还是谁更大一些

s2更大一些(如果对这里还不太懂,说明结构体的内存对齐你还没有搞明白,还需要再看一下)

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

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

相关文章

【整体介绍】

ODO&#xff1a;汽车总行驶里程 Chime: 例如安全带没系的报警声音 多屏交互就是中控屏的信息会同步到主驾驶的仪表盘上 面试问题&#xff1a;蓝牙电话协议HFP 音乐协议A2DP 三方通话测试的逻辑

线性规划:机器学习中的优化利器

一、线性规划的基本概念 线性规划&#xff08;Linear Programming, LP&#xff09;是运筹学中数学规划的一个重要分支&#xff0c;用于在一组线性不等式的约束条件下&#xff0c;找到线性目标函数的最大值或最小值。其问题可以表述为&#xff1a; 在一组线性约束条件 s.t.&am…

SurgiTrack:外科手术视频中的细粒度多类别多工具跟踪|文献速递-视觉大模型医疗图像应用|文献速递-视觉大模型医疗图像应用

Title 题目 SurgiTrack: Fine-grained multi-class multi-tool tracking in surgical videos SurgiTrack&#xff1a;外科手术视频中的细粒度多类别多工具跟踪 01 文献速递介绍 手术器械跟踪在计算机辅助手术系统中发挥着至关重要的作用&#xff0c;可为一系列应用提供有价…

亚博microros小车-原生ubuntu支持系列:1 键盘控制

背景&#xff1a;电脑配置不太行&#xff0c;我在ubuntu再运行vmware&#xff0c;里面运行亚博官方的虚拟机镜像ubuntu&#xff0c;系统很卡。基本上8G内存给打满了。还是想把亚博官方的代码迁移出来&#xff0c;之前售后就说除了官方镜像虚拟机&#xff0c;需要自己摸索迁移。…

总结5..

#include<stdio.h> struct nb {//结构体列队 int x, y;//x为横坐标&#xff0c;y为纵坐标 int s, f;//s为步数&#xff0c;//f为方向 }link[850100]; int n, m, x, y, p, q, f; int hard 1, tail 1; int a[52][52], b[52][52], book[52][52][91]; int main() { …

鸿蒙系统 将工程HarmonyOS变成OpenHarmony

DevEco Studio软件创建工程后需要修改两个地方&#xff1a; 修改第二个build-profile.json5文件 将原先内容&#xff1a; {"app": {"signingConfigs": [],"products": [{"name": "default","signingConfig": &q…

什么样的问题适合用递归

递归是一种通过函数调用自身来解决问题的方法。递归适用于那些可以被分解为相似子问题的问题&#xff0c;即原问题可以通过解决一个或多个更小规模的同类问题来解决。递归通常需要满足以下两个条件&#xff1a; 递归基&#xff08;Base Case&#xff09;&#xff1a;问题的最简…

C# 网络协议第三方库Protobuf的使用

为什么要使用二进制数据 通常我们写一个简单的网络通讯软件可能使用的最多的是字符串类型&#xff0c;比较简单&#xff0c;例如发送格式为(head)19|Msg:Heart|100,x,y,z…&#xff0c;在接收端会解析收到的socket数据。 这样通常是完全可行的&#xff0c;但是随着数据量变大&…

认识BOM

BOM 弹出层 可视窗口尺寸 屏幕宽高 浏览器内核和其操作系统的版本 剪贴板 是否允许使用cookie 语言 是否在线

国产编辑器EverEdit - 大纲视图

1 大纲视图 1.1 应用场景 在编辑较长代码文件时&#xff0c;使用大纲视图可以方便的检视当前文件的变量、函数等信息&#xff0c;方便在不同函数间跳转&#xff0c;对整个文档的全貌了然于胸。   在编辑XML文档时&#xff0c;通过展示XML文件的层次结构、节点布局&#xff0…

(2)STM32 USB设备开发-USB虚拟串口

例程&#xff1a;STM32USBdevice: 基于STM32的USB设备例子程序 - Gitee.com 本篇为USB虚拟串口教程&#xff0c;没有知识&#xff0c;全是实操&#xff0c;按照步骤就能获得一个STM32的USB虚拟串口。本例子是在野火F103MINI开发板上验证的&#xff0c;如果代码中出现一些外设的…

68,[8] BUUCTF WEB [RoarCTF 2019]Simple Upload(未写完)

<?php // 声明命名空间&#xff0c;遵循 PSR-4 自动加载规范&#xff0c;命名空间为 Home\Controller namespace Home\Controller;// 导入 Think\Controller 类&#xff0c;以便扩展该类 use Think\Controller;// 定义 IndexController 类&#xff0c;继承自 Think\Control…

AutoGen入门——快速实现多角色、多用户、多智能体对话系统

1.前言 如https://github.com/microsoft/autogen所述&#xff0c;autogen是一多智能体的框架&#xff0c;属于微软旗下的产品。 依靠AutoGen我们可以快速构建出一个多智能体应用&#xff0c;以满足我们各种业务场景。 本文将以几个示例场景&#xff0c;使用AutoGen快速构建出…

阿九的python 爬虫进阶课18.3 学习笔记

文章目录 前言1. 爬取大标题2. 爬取小标题3. 证券栏下的标题4. 某篇文章里的具体内容 前言 网课链接&#xff1a;https://www.bilibili.com/video/BV1kV4y1576b/新浪财经网址&#xff1a;https://finance.sina.com.cn/需先下载库&#xff1a; conda install lxml布置爬取的一…

WGCAT工单系统部署教程

第一步、安装JDK WGCAT部署所在主机需要JDK环境&#xff08;JDK1.8、JDK11都可以&#xff09;&#xff0c;OpenJDK也可以&#xff0c;更高版本JDK也支持&#xff0c;一般推荐使用JDK1.8或JDK11 参考&#xff1a;linux CentOS系统安装jdk教程_centos安装jdk-CSDN博客 第二步、…

自动化01

测试用例的万能公式&#xff1a;功能测试界面测试性能测试易用性测试安全性测试兼容性测试 自动化的主要目的就是用来进行回归测试 新产品--第一个版本 (具备丰富的功能)&#xff0c;将产品的整体进行测试&#xff0c;人工创造一个自动化测试用例&#xff0c;在n个版本的时候…

Mysql触发器(学习自用)

一、介绍 二、触发器语法 注意&#xff1a;拿取新的数据时用new&#xff0c;旧数据用old。

python-leetcode-简化路径

71. 简化路径 - 力扣&#xff08;LeetCode&#xff09; class Solution:def simplifyPath(self, path: str) -> str:# 使用栈来处理路径stack []# 分割路径&#xff0c;以 / 为分隔符parts path.split(/)for part in parts:if part or part .:# 空字符串或 .&#xff0…

STMCubeMX配置STM32F103ZET6

1 配置时钟 配置RCC。 配置 SYS。将Timebase Source配置为TIM1, SysTick留给FreeRTOS用。 注意: 由于第一次配置的时候忘记配置这个步骤,导致工程第一次烧录成功后,后面一直无法烧录,报以下错误: keil no target connect Error: Flash Download failed - Target DLL h…

Yearning开源MySQL SQL审核平台

一款MYSQL SQL语句/查询审计工具&#xff0c;为DBA与开发人员使用. 本地部署&#xff0c;注重隐私&#xff0c;简单高效的MYSQL审计平台。 它可以通过流程审批&#xff0c;实现真实线上环境sql的审核和执行&#xff0c;还可以回滚执行&#xff0c;能够确保线上SQL更新的可靠性…