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

目录

结构体类型

声明结构体

结构体的特殊声明

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

结构体的自引用

结构体内存对齐

对齐规则

内存对齐存在意义

默认对齐数的修改

结构体传参

结构体实现位段

了解位段是什么

位段的内存分配

位段有跨平台的问题及使用注意事项


C语言中有内置的类型,内置类型如下:

内置类型
char

short

int
long
long long
float
double
long double

这些都是C语言本身支持的现场类型,但是仅仅有内置类型是不够的。

比如,我们要定义一个人的变量,

人:3.14   ——   这种就是不行的

人是一个复杂的对象,有身高、体重、名字等。所以这就要用到一个自定义类型——结构体

C语言中也有自定义类型的。

结构体类型

声明结构体

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

//结构体的声明
struct tag  //tag就是标签名
{
    member-list;  //成员列表:1个或者多个,但是不能没有
}variable_list;  //变量列表

我们知道结构体的声明之后,我们就可以自定义一个学生练习一下:

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

结构体的特殊声明

在声明结构体的时候,可以不完全声明:匿名结构体类型

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

这两个结构体对比前面的,看出tag省略了

在使用这种形式的时候,要注意两点:

  1. 匿名的结构体类型,如果没有对结构体类型重命名的话,基本上只能使用一次,第二次往后基本不能用了。
  2. 编译器会把上面的两个声明当成完全不同的两个类型,所以下面操纵是非法的。
p = &x;

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

我们定义了这个类型,要怎么创建这种类型的变量并怎么初始化它呢?

局部结构体变量创建和初始化有两种:

一种按照结构体成员的顺序初始化的,如下:

//按照结构体成员的顺序初始化
struct Stu s = { "张三", 20, "男", "20230818001" };

我们想打印验证一下这个结构体变量,要怎么打印呢?

printf("name: %s\n", s.name);
printf("age : %d\n", s.age);
printf("sex : %s\n", s.sex);
printf("id : %s\n", s.id);

                        

注:   ·   和  ->都是用来访问结构体内的变量用的,· 是用来取的这个结构体中的元素,->是取得这个结构体中元素的地址所对应的元素。

例如:
struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
};             

*p=Stu;
Stu.name==(*p).name==p->name。

第二种初始化按照指定顺序初始化的(乱序):

//按照指定的顺序初始化
struct Stu s2 = { .age = 18, .name = "李四", .id = "20230818002", .sex = "女" };
printf("name: %s\n", s2.name);
printf("age : %d\n", s2.age);
printf("sex : %s\n", s2.sex);
printf("id : %s\n", s2.id);

                                

全局变量的定义有两种,初始化方式也跟局部变量相似,下面就只举例全局变量的创建:

struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}b2,b3; //全局变量           

struct Stu b1;//全局变量

结构体的自引用

先介绍typedef相关知识, typedef为C语言的关键字:作用是为一种数据类型定义一个新名字

typedef可以声明新的类型名来代替已有的类型名,不能增加新的类型。这里的数据类型有前面所说的内置数据类型(int,float等),还有自定义的数据类型(struct等)。

结构体自引用对于数据结构上的链表是非常有用的,

数据结构——其实是数据在内存种的存储和组织的结构,数据结构有多种

  • 线性数据结构:顺序表、链表、栈、队列。
  • 树形数据结构:二叉树
  • 等等……

结构体自引用的正确方式(链表形式):

struct Node
{
	int data;			//数据
	struct Node* next;  //指针——自己里包含一个自己同类型的指针
};

我们用typedef重命名一下:

typedef struct Node
{
	int data;			//数据
	struct Node* next;  //指针——自己里包含一个自己同类型的指针
}Node;    //将struct Node 类型重命名为 Node
//也可也写成
struct Node
{
	int data;			//数据
	struct Node* next;  //指针——自己里包含一个自己同类型的指针
};
typedef struct Node Node;  //typedef在后面重命名

注意,千万不能写成以下这些形式:

struct Node
{
 int data;
 struct Node next;
};
一个结构体中再包含一个同类型的结构体变量,这样结构体变量的大小就会无穷的大。
typedef struct   //匿名结构体类型
{
 int data;
 Node* next;  //这里面还有struct不能省略掉
}Node;

这个Node是对前面的匿名结构体类型的重命名产生的,但在匿名结构体内部提前使用Node类型来创建变量成员变量是不可以的。所以匿名结构体类型是不能实现这种自引用的。

所以,定义结构体尽量不要使用匿名结构体。

结构体内存对齐

这个牵扯到计算结构体的大小,并且还有它特有的对齐规则。

对齐规则

  1. 结构体的第一个成员对齐到结构体变量起始位置偏移量为0的地址处。(后面举例介绍)
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。                                             对齐数=编译器默认的一个对齐数与该成员变量大小的较小值                                                   VS中默认的一个对齐数为8                                                                                                       Linux中gcc没有默认对齐数,对齐数就是成员变量自身大小。
  3. 结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对齐数中最大的)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。

例如下列例子,并了解这个规则怎么用:

struct S1
{
 char c1;
 int i;
 char c2;
};
printf("%d\n", sizeof(struct S1));


struct S2
{
 double d;
 char c;
 int i;
};
printf("%d\n", sizeof(struct S2));
//结构体嵌套问题
struct S3
{
 char c1;
 struct S2 s2;
 double d;
};
printf("%d\n", sizeof(struct S3));

第一个:

所以会输出:12.

注:若遇到数组,如char[5] 则就是存了五个char。

第二个:

所以这个输出16.

第三个:

所有会输出:32.

内存对齐存在意义

结构体内存对齐是牺牲空间来换取时间的做法。

1. 平台原因 (移植原因)
2. 性能原因
若又想节省一些空间,那么就让空间小的成员尽量集中在一起。
struct S1
{
    char c1;
    int i;
    char c2;
};  //占12

struct S2
{
    char c1;
    char c2;
    int i;
};  //占8

默认对齐数的修改

我们前面说VS中默认对齐数是8,那么我们可以修改吗?

我们可以用一个预处理指令修改,并且另一个指令可以取消修改,如下:

#pragma pack(1)//设置默认对⻬数为1
#pragma pack()//取消设置的对⻬数,还原为默认

根据自己所需,可以用这个预处理指令修改对齐数。

结构体传参

我们函数传参,可以传整型变量、数组、指针变量等。结构体变量也可以传。

实现具体如下列程序:

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

不过上面两个函数,print2的效率高一些。

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

结构体实现位段

了解位段是什么

位段实现是基于结构体的

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

  1. 位段的成员必须是int、unsigned int 或者signed int,C99中位段成员的类型可以选择其他类型。
  2. 位段成员名后边有一个冒号和一个数字。
    struct A
    {
     int _a:2;
    };

位段的内存分配

  1. 位段的成员可以是int、unsigned int、signed int 或者char类型等
  2. 位段的空间上是按照需要以4个字节(int)或1个字节(char)的方式来开辟的
  3. 位段很多不确定因素,因此不能跨平台的,可移植程序应避免使用位段。

我们看下一段代码:

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};

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

A所占内存是多少?

输出结果:

位段有跨平台的问题及使用注意事项

int位段被当成有符号还是无符号不确定

位段最大位的数目不确定。

位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃 剩余的位还是利用是不确定的。

所以位段可以达到结构体同样的效果,并且可以很好的节省空间,但有跨平台问题。

注意事项:位段内存中每个节分配一个地址,一个字节内部的bit位是没有地址的。所以不能对位段成员使用&操作符,不能使用scanf直接输入值,只能先输入放在一个变量中,然后赋值给位段的成员(使用位段结构体里面的类型尽量要一样,否则可控性就会比较差)。

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
int main()
{
	struct A sa = { 0 };
	//scanf("%d", &sa._b);//错误

	
	int b = 0;
	scanf("%d", &b);
	sa._b = b;
	return 0;
}

制作不易,求各位大佬三连qwq,若有不足的地方,请大佬们多多指点!

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

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

相关文章

QT 驾校系统界面布局编写

MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this);this->resize(ui->label_img->width(),ui->label_img->height());//图片自适应窗口大小ui->label_img->setScaledContents(true);//图片置…

WebMvcConfigurationSupport 注册自定义拦截器 Java SpringBoot

WebMvcConfigurationSupport 注册自定义拦截器 Java SpringBoot 说明 定义拦截器 拦截器(Interceptor)类,用于在处理请求之前进行一些操作。 实现方式,让类实现HandlerInterceptor。 在preHandle方法中进行请求拦截的逻辑操作…

【智能算法应用】智能算法优化BP神经网络思路

目录 1.思路2.设计 1.思路 在BP神经网络结构中,权值和阈值被视为模型的参数,它们在训练过程中需要通过反向传播算法进行学习,以使得网络的输出尽可能地接近真实标签。这意味着网络的目标是通过最小化均方误差(MSE)来调…

Flutter 在 Windows 下的开发环境搭建(Flutter SDK 3.19.2)【图文详细教程】

Git 下载与安装 对于 Flutter 3.19,Git 版本需要 2.27 及以上 Git 下载: Git 官网:https://git-scm.com/Git 下载淘宝镜像:https://registry.npmmirror.com/binary.html?pathgit-for-windows/ 对于 Git 的安装教程,…

基于springboot+vue的游戏交易系统

博主主页:猫头鹰源码 博主简介:Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战,欢迎高校老师\讲师\同行交流合作 ​主要内容:毕业设计(Javaweb项目|小程序|Pyt…

目标检测——PP-PicoDet算法解读

PP-YOLO系列,均是基于百度自研PaddlePaddle深度学习框架发布的算法,2020年基于YOLOv3改进发布PP-YOLO,2021年发布PP-YOLOv2和移动端检测算法PP-PicoDet,2022年发布PP-YOLOE和PP-YOLOE-R。由于均是一个系列,所以放一起解…

嵌入式学习41-数据结构2

今天学习了链表的增删改查 (暂定!!后续再补内容) 高内聚 :一个函数只实现一个功能 …

klipper源码分析之simulavr测试

分析Klipper源码,有时需要结合下位机一起分析,这样才能更加全面的了解Klipper的工作原理。如果手头上有打印机主板,电脑当做上位机运行Klipper,这样是比较方便。如果手头上没有打印机主板,可以用simulavr模拟AVR下位机…

ECharts绘制盒须图

一、箱线图 反应一组数据的分布情况,通过四分位数以图形的方式展示数值数据的局限性、分布和偏度组的方法。 四分位数:即把所有数值由小到大排列并分成四等份,处于三个分割点的数值就是四分位数。 下四分位:第一四分位Q1&#…

Android TransactionTooLargeException排查定位

Android TransactionTooLargeException排查定位 工具: https://github.com/guardian/toolargetoolhttps://github.com/guardian/toolargetool android TransactionTooLargeException问题的修复,一种简单的修复就是在Fragment的onCreate里面&#xff0…

Nginx实现原理全解析:构建高效Web服务器的关键

1、Nginx是什么 Nginx(engine X)是一个开源的轻量级的HTTP服务器,能够提供高性能的HTTP和反向代理服务。与传统的Apache服务器相比,在性能上Nginx占用系统资源更小、支持高并发,访问效率更高;在功能上&…

uniapp uni-file-picker @delete删除时候不知道删的是第几个,真坑,用着他的组件写着代码还得改着他的源码

正常删除触发事件中返回个文件地址那些东西 根本没什么用 因为这个项目是上传阿里云oss的,所以上传之后获取网络地址就可以给后端传参了,所以本地地址一点用没有,我只是想在删除的时候知道删了第几个就行。 于是、改源码吧 组件源码中找到这…

Ubuntu双系统/home分区扩容

一、Windows系统中利用磁盘管理分出空闲区域,如果多就多分一些 二、插入安装Ubuntu的U盘启动盘,lenovo电脑F12(其他电脑可选择其他类似方式)选择U盘启动项,然后选择ubuntu,出现安装界面,再选择t…

Basic RNN

文章目录 回顾RNNRNN CellRNNCell的使用RNN的使用 RNN例子使用RNN Cell实现使用RNN实现 嵌入层 Embedding独热向量的缺点Embedding LSTMGRU(门控循环单元)练习 回顾 DNN(全连接):和CNN相比,拥有巨大的参数量,CNN权重共…

【Godot4.2】自定义简单的参数化2D网格节点

概述 在某些情况下我们可能需要在Godot中自定义2D网格,因为此时可能用TileMap会显得太“重”,因为我们可能只需要其作为网格的功能却不需要它的其他功能,比如绘制瓦片地图。而且我们可能需要在网格功能的基础上,添加更多自定义的…

服务器版本命令查看

1、# uname -a (Linux查看版本当前操作系统内核信息) 2、# cat /proc/version (Linux查看当前操作系统版本信息) 3、# cat /etc/issue 或 cat /etc/redhat-release (Linux查看版本当前操作系统发行版信息&…

jmeter打开文件报异常无法打开

1、问题现象: 报错部分内容: java.desktop does not export sun.awt.shell to unnamed module 0x78047b92 [in thread "AWT-EventQueue-0"] 报错部分内容: kg.apc.jmeter.reporters.LoadosophiaUploaderGui java.lang.reflect.Invo…

MPI4.1文档2:MPI术语与MPI操作过程

第2章 MPI术语与约定 MPI Terms and Conventions 这一章节解释了MPI文档中使用的符号术语和惯例,以及其中所做的一些选择和背后的原因。 2.1 文档符号表示 Document Notation Rational. 在整个文档中,对界面规范中所做设计选择的理由以这个格式(首尾包…

Modbus TCP转Profinet网关如何实现Modbus主站与多设备通讯

在工业控制领域中,Modbus TCP转Profinet网关(XD-ETHPN20)扮演着连接不同设备间通讯的重要角色。当将Modbus主站与十几台服务器进行通讯时,通过modbus tcp转profinet网关(XD-ETHPN20)设备将不同协议间的数据…

LabVIEW NV色心频率扫描

LabVIEW NV色心频率扫描 通过LabVIEW软件开发一个能够实现对金刚石氮空位(Nitrogen-Vacancy,NV)色心的频率扫描系统。系统通过USB协议与硬件设备通信,对NV色心进行高精度的频率扫描,满足了频率在2.6 GHz到3.2 GHz范围…
最新文章