自定义类型1:结构体的深入学习

文章目录

  • 前言
  • 一、结构体类型的声明
    • 1、结构体回顾
      • 1.1、结构体声明
      • 1.2、结构体变量的创建和初始化
    • 2、结构的特殊声明
    • 3、结构体的自引用
  • 二、结构体的内存对齐
    • 1,什么叫偏移量
    • 2、对齐规则
    • 3、为什么存在内存对齐
    • 4、修改默认对齐数
  • 三、结构体传参
  • 四、结构体实现位段
    • 1、什么是位段
    • 2、位段的内存分配
    • 3、位段的跨平台问题
    • 4、位段的使用注意事项


前言

在结构体初阶学习中小编讲述了一些关于自定义类型结构体相关的内容,以下内容是对于自定义类型结构体的深入了解,建议读者先去从结构体初阶学习进行学习。


提示:以下是本篇文章正文内容,下面案例可供参考

一、结构体类型的声明

1、结构体回顾

结构体初阶学习中,我们已经对结构体有了初步的了解,接下来让我们对前面内容进行温故一下

1.1、结构体声明

结构体的语法形式:

struct tag
{
成员变量;
};

例如描述一个学生

struct stu 
{
	char name[20]; //名字
	int age;   //年龄
	float height;  //身高
	float weight;  //体重
	char id[20]; //学号
}; //分号不能丢

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

结构体变量的创建
根据上面建立的结构体我们可以创建结构体变量

struct stu 
{
	char name[20]; //名字
	int age;   //年龄
	float height;  //身高
	float weight;  //体重
	char id[20]; //学号
}s1,s2; 

struct stu s4;  
int main()
{
	struct stu s3; 
	return 0;
}

在这里s1,s2,s4属于结构体的全局变量,s3属于结构体的局部变量。

结构体的初始化
在这里用结构体变量s3进行举例,结构体的初始化有三种情况:

  1. 当你不知道赋值给结构体什么样的值的时候,可以先给他赋予空值
int main()
{
	struct stu s3 = {0};
	return 0;
}
  1. 按照结构体成员变量的创建顺序一一对应进行赋值
int main()
{
	struct stu s3 = { "张三", 21 ,175.3f,55.5f,"200020845" };
	return 0;
}

3.如果不想按照结构体成员变量的创建顺序进行赋值,可以使用点+结构体成员的方式进行乱序赋值

int main()
{
	struct stu s3 = { .age = 21,.id = "200020845" ,.height = 175.3f ,.height = 55.5f ,.name = "张三" };
	return 0;
}

2、结构的特殊声明

在这里思考一下把结构体的名字去掉,还能对结构体进行创建么
在编程中(特别是在像C、C++或Go这样的语言中),当你定义了一个匿名结构体类型(即没有给它指定一个名字的结构体),那么这个结构体类型通常只能在它被定义的那个位置(或作用域内)被使用一次。这是因为匿名结构体没有名字,所以无法在其他地方通过名字来引用它。

在这里插入图片描述

在这里显示为声明符号,把结构体的名字去掉是不能在主函数中进行结构体变量创建的,结构体连名字都没有,主函数都不认识它,怎么能够对他进行变量的创建呢,这就跟大街上有人想要喊你,但是他不知道你的名字,只能喊一句喂,那总不能所有的人都以为是你喊他吧,然后回头对你进行目光注视。在这里结构体是一样的道理。但是是否也能够使用它呢?是可以的,正确的变量创建如下:

struct 
{
	int i;
}s = {2};

int main()
{
	printf("%d\n",s.i);
	return 0;
}

分析
在这里我们把这种创建没有名字的结构体称作匿名结构体,匿名结构体的正确使用就是在创建变量的时候在创建结构体的后面紧接着创建结构体变量,这种匿名结构体的变量创建和初始化都需要在创建结构体之后紧跟着创建,而不能在其他地方,这是因为结构体没有名字,其他的地方都不认识这个结构体,无法通过名字对他进行创建和初始化。

综上:
匿名结构体类型只能在定义的时候用一次,下次你在想使用这个类型的时候,没有名字无法找到,也就无法使用。

3、结构体的自引用

在函数中,函数可以对自己进行函数使用称为递归,那么结构体是否能在结构体的内部对结构体进行引用呢?答案是可以的,结构体也是可以对自己进行自引用的。那么结构体怎么对自己进行自我引用呢?

如果我们按照函数的思维那么结构体的自我引用就是如下代码

//以下结构体自我引用的错误示范
struct stu
{
	int i;
	struct stu s;
};

那么这行代码是否是正确的呢?答案是错误的?首先我们对它进行求字节就会陷入一个无限的循环当中,在结构体里面创建一个结构体,结构体中带着一个结构体的变量i,那么结构体的大小就会无限的循环:4+4+4…直到内存溢出的情况。那么结构体到底如何自引用呢?其实函数换个思路方向也是正确的,函数的名字也就代表着函数的地址,那么我们结构体对自己进行解引用也可以带上自己的地址,这样我们可以通过结构体也可以找到地址对自己进行引用。

struct stu
{
	int i;
	struct stu *s;
};

在这里一个结构体自引用包括两个部分,一个是结构体本身存在的成员变量,另外一个则是结构体对自己进行解引用的下一个结构体的地址,又因为地址在内存中要么占4个字节,要么占8个字节,所以结构体的大小又是确定的。综上,结构体对自己进行解引用,需要的是自己本身的地址,而不是结构体变量本身。

出错点
在结构体自引用的过程中,还会出现下面这种情况:

typedef struct Node
{
 	int data;
 	Node* next;
}Node;

在这里我们重新用typedef对结构体变量进行重命名Node,然后我们在结构体内部自引用自己的时候直接使用重命名的结构体变量名,这样就会发生错误,,因为Node是对前⾯的匿名结构体类型的重命名产⽣的,但是在匿名结构体内部提前使用Node类型来创建成员变量,这是不行的。因此在定义结构体变量的时候不要使用匿名结构体。


二、结构体的内存对齐

在学习结构体的内存对齐,我们通过一串代码引出:
求下列代码的输出结果

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

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

在这里我们需要求解的是结构体的大小,在结构体中存在三个成员变量,分别占1个字节,4个字节,1个字节。总共6个字节,那么结构体变量是否是6个字节呢?下面看到我们的运行结果
在这里插入图片描述
在这里结构体求出的字节数是12,这是怎么回事呢?这就涉及到了结构体的对齐规则了

1,什么叫偏移量

在学习结构体的对齐规则前我们得先了解一个概念:偏移量

在C语言中,偏移量(Offset)通常指的是从一个基准位置(如数组的起始地址、结构体成员的起始地址等)开始,到另一个位置的距离。这个距离通常表示为字节数。在结构体中,每个成员相对于结构体起始地址都有一个固定的偏移量。

创建一个结构体变量
在这里插入图片描述
在这里创建了一个结构体变量,结构体地址存放在如图中,那么由结构体地址处向后的一个字节为第一个元素,因为第一个元素相对于首元素的位置为0个字节,结构体跨过一个字节内容到下一个字节内容那么就是偏移量为1,也就是第二个字节的内容相对于结构体地址需要跨过一个字节,也就是偏移量为1个字节。

2、对齐规则

结构体对齐规则主要分为以下几点:

  1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处:
    对齐数 = 编译器默认的⼀个对齐数 与 该成员变量大小的较小值。
    在vs中,编译器默认的对齐数是8;
    linux中,gcc默认没有对齐数,对齐数就是成员变量本身的大小
  3. 结构体总大小为最大对齐数(结构体成员变量中每个结构体变量都有对齐数,所有对齐数中最大的)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体成员对齐到⾃⼰的成员中最大对齐数的整数倍处,结构
    体的整体大小就是所有最⼤对齐数数(含嵌套结构体中成员的对齐数)的整数倍。

实战

结构体的创建

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

开辟空间和规则如下:

在这里插入图片描述

分析
根据所创建的结构体,我们在内存中开辟内存空间,结构体变量在内存中存储需要满足上面的条件:
看到第一个条件,结构体的第一个成员变量对齐到结构体变量起始位置偏移量为0的地址处,即结构体的第一个成员变量从偏移量为0的地方向后进行存储,即上面的绿色部分。

第二个条件:结构体成员变量对齐到对齐数的整数倍处,所谓对齐数就是编译器自己默认的对齐数和结构体的成员变量大小的较小值。这里第二个变量为整型变量,大小为4个字节,然后使用vs的话,vs编译器默认的对齐数是8,8和4比较,4较小,所以结构体第二个成员变量对齐到偏移量4的整数倍处。也就是如图中的橙色部分。第三个为字符型变量为1个字节,与8相比,1较小,所以偏移量为1的倍数就是下面的蓝色部分。

第三个条件则结构体变量的总大小为最大对齐数的整数倍,在这里s1的对齐数为1,i的对齐数为4,s2的对齐数为1,所以最大对齐数为4,根据上面分析前面一共已经占了9个字节,9不是4的倍数,12是4的倍数,所以结构体变量总大小为12个字节。

3、为什么存在内存对齐

平台的兼容性
平台兼容性的角度来看,不是所有的硬件平台都能访问任意地址上的任意数据。某些硬件平台只能在特定的地址边界上访问特定类型的数据。如果数据没有按照这些硬件平台的要求进行对齐,那么在访问这些数据时可能会出现硬件异常或错误。因此,为了保证数据在不同硬件平台上的正确访问,结构体内存对齐是必要的。

性能优化角度上
从性能优化的角度来看,对齐的内存访问通常比未对齐的内存访问更高效。这是因为处理器在访问对齐的内存时,可以一次性读取或写入所需的数据,而无需进行额外的内存访问或数据拆分。相反,如果数据未对齐,处理器可能需要执行多次内存访问才能完成数据的读取或写入,这会增加处理器的负担并降低程序的性能。因此,为了优化内存访问性能,结构体内存对齐也是非常重要的。

性能方面,平台方面举例
假设我们内存储存的话不是按照内存对齐的方式进行存储的话,下面创建一个结构体

struct s 
{
	char i;
	int x;
	char c;
};

在这里假设内存不存在对齐的话,我们数据存储在内存中因该是连续存储的。
在这里插入图片描述
在这里假设数据访问是四个字节进行访问的,那么首先拿出的就是i加上x的三个字节数据,然后在取出x的一个字节数据加上c,然后我们还需要取出x的数据,就得在进行一次字节访问,才能提取数据。假设内存对齐的话
在这里插入图片描述

在这里进行内存对齐可以保证数据,使用一次内存操作就可以将所有的数全部取出来了。这样就省去了内存再次操作的时间。
总体来说,内存对齐是拿内存换取时间的做法。

那在结构体中,又想要内存对齐,又想要节省空间的话,就尽量将占用空间较小的成员变量放在一起。

在这里插入图片描述
S1 和 S2 类型的成员⼀模⼀样,但是 S1 和 S2 所占空间的大小有了⼀些区别。

4、修改默认对齐数

#pragma是预处理指令,Microsoft的Visual C++编译器提供了一个#pragma pack指令,允许开发者控制结构体成员的对齐方式。这个指令可以改变编译器默认的对齐行为,使得结构体成员可以按照指定的字节边界进行对齐,而不是按照编译器默认的对齐规则。

在这里插入图片描述
在这里#pragma pack(1)是把vs默认的对齐数8改成1,也就是说现在vs默认的对齐数是1,由结构体对接规则第二条,我们可以知道,现在所有元素的对齐数都为1了。此时结构体成员变量就只需要在偏移量为1的倍数的地方进行存储等价于内存不对齐的方式也就是6个字节。


三、结构体传参

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()
{
	print2(&s); //传地址
    return 0;
}

上⾯的 print1 和 print2 函数哪个好些?
答案是:首选print2函数

原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递⼀个结构体对象的时候,结构体过⼤,参数压栈的的系统开销⽐较⼤,所以会导致性能的下
降。

这句话的意思也就是说向上面直接进行结构体的传参,结构体中又存在大型的数据如:int data[1000];这种需要开辟大量的内存空间,而在函数传参的时候直接传递结构体那么则需要开辟两份这么大型的空间,空间会造成浪费,然后使用数据进行赋值的时候又需要额外进行一一对应值进行传回来,这就浪费了大量的时间。而使用结构体地址进行传参,将结构体的地址传递过去,只需要占用四个字节,内存占用少,而且在实现函数内部对原结构体变量进行修改,可以直接通过地址进行修改,而不需要一个对着一个进行传值,减少了代码的运行时间。

结论:
结构体传参的时候,要传结构体的地址。


四、结构体实现位段

在学习完结构体之后,我们可以学习结构体位段,结构体位段是建立在结构体之上的

1、什么是位段

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

  1. 位段的位代表二进制位,所谓的位段就是在结构体成员变量之后加上一个冒号和一个数字,代表该数据要存多少个二进制位
  2. 位段的成员必须是 int、unsigned int 或signed int
struct s
{
	int i : 3;
	int x : 4;
};

这里就是结构体位段,结构体位段成员i表示存进内存为3个二进制位,而结构体位段成员x表示存进内存为4个二进制位。位段的空间是按照需要以四个字节(int)或者一个字节(char)的方式进行开辟空间的,所以这里sizeof(struct s)为四个字节,因为四个字节就可以把所有的位包括进去,具体怎么存放,还得向下进行学习。

2、位段的内存分配

  1. 位段的成员可以是int,unsigned int,signed int,或者char类型的数据
  2. 位段的空间是按照需要以四个字节(int)或者一个字节(char)的方式进行开辟空间的
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段

例如上面案例

struct s
{
	int i : 3;
	int x : 4;
};
int main()
{
	struct s s = { 0 };
	s.i = 1;
	s.x = 10;
	return 0;
}

首先给这块内存开辟四个字节的空间也就是8个二进位。在这里我先画一个字节方便理解

在这里插入图片描述

根据结构体位段成员变量需要存进去多少二进制位,假设上面内存最左边是结构体位段地址,在vs中,结构体位段从一个字节的右边向左边进行存储

在这里插入图片描述
此时内存分配只需要一个字节的空间就可以将所有的元素存入。在这里我们也可以打开调试近距离观察一下:

首先代码会开辟四个字节的空间

在这里插入图片描述
然后存入结构体位段的成员,因为vs是小端存放,所以先存放1然后存放10的四位也就是1010,所以总的二进制位位01010001转为十六进制位为51

在这里插入图片描述

在这里插入图片描述

3、位段的跨平台问题

  1. int位段的有符号和无符号的不确定性

在位段中,int类型的成员被当作有符号数还是无符号数是不确定的。这取决于具体的编译器实现和平台规范。因此,在编写跨平台代码时,如果使用了int类型的位段,就需要特别注意这一点,以避免因符号位解释不同而导致的错误。

  1. 位段中最大位数的不确定性

位段中成员所能占用的最大位数也是不确定的。这同样取决于编译器的位数和平台规范。例如,在16位机器上,位段中的最大位数可能是16,而在32位机器上则可能是32。如果编写的代码在不同位数的机器上运行,就需要特别注意这一点,以避免因位数限制而导致的错误。

  1. 内存分配顺序的不确定性

位段中的成员在内存中的分配顺序也是未定义的。有的平台可能从左向右分配,而有的平台则可能从右向左分配。这种不确定性会导致在不同的平台上运行相同的代码时,位段成员在内存中的布局可能不同,从而引发错误。

  1. 位段剩余位的处理不确定性

当一个结构体包含两个位段成员,且第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用这些位是不确定的。这种不确定性同样会导致在不同的平台上运行相同的代码时,位段成员的值可能不同,从而引发错误。

4、位段的使用注意事项

  1. 无名位段:无名位段不能被访问,但是会占空间。在编写代码时需要特别注意这一点,以避免因误用无名位段而导致的错误。
//无名位段
struct 
{
	int i : 3;
	int x : 4;
};
  1. 取地址操作:不能对位段进行取地址操作。这是因为位段并不占用独立的内存空间,而是与其他位段共享同一个整型空间。因此,对位段进行取地址操作是没有意义的,也是不被允许的。所以不能对位段的成员使⽤&操作符,这样就不能使⽤scanf直接给位段的成员输⼊值,只能是先输入放在⼀个变量中,然后赋值给位段的成员
struct s
{
	int i : 3;
	int x : 4;
};
int main()
{
	//错误使用
	struct s s = { 0 };
	//printf("%d",&s.i);

	//正确的⽰范
	int b = 0;
	scanf("%d", &b);
	s.i = b;
	return 0;
}
  1. 赋值范围:对位段赋值时,不要超过位段所能表示的最大范围。否则,会导致位段的值溢出或发生其他不可预测的错误。
  2. 数组形式:位段不能出现数组形式。这是因为位段的大小是固定的,而数组的大小是可变的。因此,将位段定义为数组形式是没有意义的,也是不被允许的。

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

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

相关文章

通过异地组网工具+RustDesk实现虚拟局域网使用远程桌面RDP

通过异地组网工具RustDesk实现虚拟局域网使用远程桌面RDP 预期效果 常见的远程桌面工具就不多说,麻烦而且不好用 QQ 使用普及率高 卡顿、延迟高 TeamViewer 功能强大、兼容性好 官方查询商业用途频繁 向日葵 安全性高、支持多种设备 强制登录、免费用户限速、限…

详解varint,zigzag编码, 以及在Go标准库中的实现

文章目录 为啥需要varint编码为啥需要zigzag编码varint编码解码 zigzag编码解码 局限性 为啥需要varint编码 当我们用定长数字类型int32来表示整数时,为了传输一个整数1,我们需要传输00000000 00000000 00000000 00000001 32 个 bits,而有价…

Oracle CONNECT BY、PRIOR和START WITH关键字详解

Oracle CONNECT BY、PRIOR和START WITH关键字详解 1. 基本概念2. 数据示例3. SQL示例3.1. 查询所有员工及其上级3.2. 显示层次结构3.3. 查询特定员工的子级 4. 结论 在Oracle数据库中,CONNECT BY、PRIOR和START WITH关键字主要用于处理层次结构数据,例如…

PostgreSQL的学习心得和知识总结(一百五十六)|auto_explain — log execution plans of slow queries

目录结构 注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下: 1、参考书籍:《PostgreSQL数据库内核分析》 2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》 3、PostgreSQL数据库仓库…

基于 Python 的机器学习模型部署到 Flask Web 应用:从训练到部署的完整指南

目录 引言 技术栈 步骤一:数据预处理 步骤二:训练机器学习模型 步骤三:创建 Flask Web 应用 步骤四:测试 Web 应用 步骤五:模型的保存与加载 保存模型 加载模型并在 Flask 中使用 步骤六:Web 应用…

在xml 中 不等式 做转义处理的问题

对于这种要做转义处理&#xff0c;<![CDATA[ < ]]>

图文详解ChatGPT-o1完成论文写作的全流程

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 本月中旬OpenAI发布了OpenAI o1系列新的AI模型。 据OpenAI介绍&#xff0c;这些模型旨在花更多时间思考后再做出反应&#xff0c;就像人一样。通过训练&#xff0c;它们学会改进思维过…

如何制定有效的学习计划

文章目录 第一章&#xff1a;目标设定1.1 目标的重要性1.2 SMART原则1.3 目标设定公式 第二章&#xff1a;时间管理2.1 时间的重要性2.2 制定时间表2.3 时间管理公式2.4 番茄工作法2.5 时间分配公式 第三章&#xff1a;学习策略3.1 学习方法3.2 学习材料的选择3.3 学习效果公式…

Kaggle竞赛——灾难推文分类(Disaster Tweets)

目录 1. 准备工作2. 资源导入3. 数据处理4. 绘制词云图5. 数据可视化5.1 词数和字符数可视化5.2 元特征可视化5.3 类别可视化 6. 词元分析6.1 一元语法统计6.2 多元语法统计 7. 命名实体识别8. 推文主题提取9. 构建模型9.1 数据划分与封装9.2 模型训练与验证 10. 模型评估11. 测…

【Linux】文件IO深度解析:文件描述符与重定向的奥秘

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Linux 目录 一&#xff1a;&#x1f525; C语言中文件IO操作 &#x1f95d; 1.C语言中的开关读写文件&#x1f98b; 1.1 fopen()&#x1f98b; 1.2 fclose()&#x1f98b; 1.3 fwrite()&#x1f98…

内容安全与系统构建加速,助力解决生成式AI时代的双重挑战

内容安全与系统构建加速&#xff0c;助力解决生成式AI时代的双重挑战 0. 前言1. PRCV 20241.1 大会简介1.2 生成式 Al 时代的内容安全与系统构建加速 2. 生成式 AI2.1 生成模型2.2 生成模型与判别模型的区别2.3 生成模型的发展 3. GAI 内容安全3.1 GAI 时代内容安全挑战3.2 图像…

面试宝典(五):用三个线程按顺序循环打印123三个数字,比如123123123

要使用三个线程按顺序循环打印123三个数字&#xff0c;势必要控制线程的执行顺序&#xff0c;可以使用java.util.concurrent包中的Semaphore类来控制线程的执行顺序。 代码示例 import java.util.concurrent.Semaphore;public class SequentialPrinting123 {private static Se…

第T8周:猫狗识别

>- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营](https://mp.weixin.qq.com/s/0dvHCaOoFnW8SCp3JpzKxg) 中的学习记录博客** >- **&#x1f356; 原作者&#xff1a;[K同学啊](https://mtyjkh.blog.csdn.net/)** &#x1f37a; 要求&#xff1a; 了解mode…

离线电脑 Visual Studio Community 2017:您的许可证已过期

VS 2017社区版&#xff0c;打开后提示&#xff1a; “您的许可证已过期&#xff0c;必须进行更新。请确保已连接Internet&#xff0c;然后检查更新的许可证以继续使用本产品” 解决办法&#xff1a; &#xff08;1&#xff09;在另一台可以联网的电脑上&#xff0c;更新VS20…

8.Linux按键驱动-中断下半部

1.编程思路 1.1在gpio结构体中添加tasklet_struct结构体 1.2在probe函数中初始化tasklet结构体 1.3在中断服务程序中调度tasklet 1.4在这个函数中执行其它任务 2.代码&#xff1a; 应用程序和Makefile和上节一致 https://blog.csdn.net/weixin_40933496/article/details/1…

通过call指令来学习指令摘要表的细节

E8 cw cw 表示E8后面跟随2 字节 (什么数不知道) rel16 指在与指令同一代码段内的相对地址偏移 D ,指向Instruction Operand Encoding 表中的D列, 他告诉我们 操作数1 是一个0FFSET N.S. 在64位模式下&#xff0c;某些指令需要使用“地址覆盖前缀”&#xff08;address over…

RL学习笔记-马尔可夫过程

参考资料&#xff1a;蘑菇书、周博磊老师课程 在强化学习中&#xff0c;智能体与环境交互是通过马尔可夫决策过程来表示的&#xff0c;因此马尔可夫决策过程是强化学习的基本框架。 马尔可夫性质 指一个随机过程在给定现在状态及所有过去状态情况下&#xff0c;其未来状态的条件…

Golang | Leetcode Golang题解之第506题相对名次

题目&#xff1a; 题解&#xff1a; var desc [3]string{"Gold Medal", "Silver Medal", "Bronze Medal"}func findRelativeRanks(score []int) []string {n : len(score)type pair struct{ score, idx int }arr : make([]pair, n)for i, s : …

BERT语言模型详解【Encoder-Only】

NLP-大语言模型学习系列目录 一、注意力机制基础——RNN,Seq2Seq等基础知识 二、注意力机制【Self-Attention,自注意力模型】 三、Transformer图文详解【Attention is all you need】 四、大语言模型的Scaling Law【Power Low】 五、大语言模型微调方法详解【全量微调、PEFT、…

Android Studio 导入/删除/新建库的模块(第三方项目) - Module

文章目录 一、导入module项目 Module空项目如何导入Project工程项目二、删除module项目三、新建module项目(不常用) 一、导入module项目 首先&#xff0c;你必须要有一个工程(Project),才可以打开项目(Module) 第一步骤&#xff1a;右键项目依次点击 New -> Module 1、工…