C++ | 对比inline内联函数和宏的不同点

在这里插入图片描述

在这里插入图片描述

文章目录

  • 一、前言
  • 二、宏的优缺点分析
    • 1、概念回顾
    • 2、宏的缺点
    • 3、宏的优点
  • 三、inline内联函数
    • 1、概念
    • 2、特性①:空间换时间
    • 🎁趣味杂谈:庞大的游戏更新包
    • 3、特性②:inline实现机制
    • 4、特性③:inline的声明与定义
      • 反汇编观察函数调用的流程
      • 内联函数的生成机制【⭐】
  • 四、总结与提炼

一、前言

本文我们聊一聊一下宏和内联函数之间的关系。

  • 在预处理章节,我们重点介绍了什么是宏,以及宏和函数之间的区别,还记得下面这张图吗,我们对宏和函数之间做了一个很细致的分析🔍
  • 那在本文中,我们来说的就不是普通的函数了,它叫做【内联函数】,是C++里面独有的,由inline关键字进行修饰,具体怎样,敬请看下去吧👇

在这里插入图片描述

二、宏的优缺点分析

在将内联函数之间前,我们再来聊聊有关宏的一些内容

1、概念回顾

下面是宏的申明方式:

#define name( parament-list ) stuff
//其中的 parament-list 是一个由逗号隔开的符号表,它们可能出现在stuff中

注:① 参数列表的左括号必须与name紧邻,如果两者之间有任何空白存在,参数列表就会被解释为stuff的一部分。

  • 在学习了C语言后,如果我不带你去回顾这一些,那你是否能立马写出一个宏来呢,例如:写一个用于求和的宏函数
  • 那我相信对于很多同学来说都会措手不及,答案会像是下面这样千奇百怪🤣
//1.
#define Add(int x, int y)	return x + y;
//2.
#define ADD(x, y) x + y
//3.
#define ADD(x, y) (x + y)
//4.
#define ADD(x, y) ((x) + (y));
//5.
#define ADD(x, y) ((x) + (y))
  • 那究竟哪一个对的呢?首先第一个肯定是错误的,因为我说了这是【宏】,而不是函数,这样子是函数的写法。

后面四个的主要区别就在于后面的stuff,我们一一来分析一下

  • 首先若是写成下面这样,传入1给到x,传入2给到y,此时去我们去Linux环境下看看预处理之后会发生什么
int ret = ADD(1, 2);
  • 可以看到很明显进行了一个【宏替换】

在这里插入图片描述

  • 若此时我将调用的函数后面再乘上一个3呢,这会发生什么?
int ret = ADD(1, 2) * 3;
  • 可以观察到此时在预处理阶段也是直接进行了一个替换,不过仔细观察就可以发现,由于*的优先级来得高,所以2会和后面的3先进行一个运算,这也就造成了最后结果的不同

在这里插入图片描述


所以我们应该要像下面这样,在外层加上一个大括号,防止出现优先级的问题

#define ADD(x, y) (x + y)
  • 可是这样真的就可以了吗?若此时我向ADD宏函数传入下面这样的参数呢?会发生什么?
int a = 10;  
int b = 20;
int ret = ADD(a | b, a & b);
  • 编译器还是一样会去做傻傻的替换,但是这个时候我们又得注意优先级的问题了,对于+号来说,它的优先级高于&按位与和|按位或的,如果不清楚优先的话可以看看操作符汇总大全
  • 所以中间的【b】和【a】会先进行结合,然后再去算&|

在这里插入图片描述

  • 如果要防止这种表达式的传入而造成的优先级问题,可以对内部的形参也加上一个括号(),这样就不会出现问题了

#define ADD(x, y) ((x) + (y));
  • 可是呢,有的同学虽然想到了这一点,但是却在最后面加了一个;号,对于分号来说是我们在写代码的时候作为一条语句结束的标志,但是对于宏来说也可以写分号吗?
  • 将原先的传参继续替换成下面这样来试试
int ret = ADD(1, 2) * 3;
  • 继续通过预处理之后的结果去进行观察,就可以发现在进行宏替换之后原先的语句中出现了;号,但是分号后面还有3要乘,这个时候其实就不对了,所以宏定义后是不可以加分号的

在这里插入图片描述

  • 最后这一种才是最正确的写法
#define ADD(x, y) ((x) + (y))		//✔

2、宏的缺点

看了上面有关【宏】概念的一些回顾,我们来聊聊它的缺点所在

① 宏可能会带来运算符优先级的问题,导致程容易出现错。【加括号太麻烦了!!!】

  • 这一点相信你在看了上一小节的叙述之后一定是感同身受,只是完成一个两数相加的功能,就需要加上这么多括号了,若是再复杂一些的功能,那岂不是要加很多了🙄

② 宏是不能调试的【这点比较致命👈】

  • 可以观察到,无论是在哪个平台下,对于宏来说都是无法调试的

Windows环境下VS

在这里插入图片描述

Linux环境下gdb

在这里插入图片描述

③ 没有类型安全的检查【直接替换】

  • 可以看到,无论我传入何种类型的参数,都不会出现问题。这点其实说明了宏对于类型的检查是不严谨的

在这里插入图片描述

④ 有些场景下非常复杂,容易出错,不容易掌握

  • 之前我有写过一篇文章叫做使用宏将一个整数的二进制位的奇数位和偶数位交换。为了实现这个功能我写了一个这样的宏函数
#define SWAP(n) num = (((n & 0xaaaaaaaa) >> 1) | ((n & 0x55555555) << 1))
  • 乍眼一看,就感觉非常复杂,都是一些二进制的位运算,若是没有对这一块很熟悉的话,就很容易写错,因此说对于一个宏来说其实要实现一些功能的时候会非常繁琐

3、宏的优点

了解了宏的缺点之后,我们再来瞧瞧它的优点,既然在一些场景被广泛使用,那一定也具有它的优点💡

宏常量提高复用性和可维护性

  • 这一点很好理解,也就是我们平常用得最多的,一般会将频繁使用到的一些数字定义成一个宏,这样就不需要每次写数字了,直接写这个宏所定义的变量即可
  • 而且在修改的时候也会很方便,只需要修改一下宏定义处即可,实现一改多改【这一点在项目里面还是比较常用的
#define n 500

宏函数类型不固定,int、double、float都可以用

  • 这一点其实我们在讲缺点的时候也有提及,虽然类型检查得不严谨,但真正用起来还是很香的,这很像是我们之前说到过的函数重载,虽然函数名相同,但是可以传入不同的参数实现不同的运算

宏函数不用建立栈帧,因此不会产生消耗

  • 刚才我们有观察过,对于【宏】来说是不能调试的,因为它根本就不是一个真正意义上的函数,只是具备函数的功能罢了。既然不是函数的话也就不会在栈上建立函数栈帧,那也就不会有过多的消耗

好,以上就是本文所要讲的有关【宏】相关的所有内容,希望能唤起读者对这个知识点的回忆

三、inline内联函数

接下去我们就正式来讲讲C++中的独有的【内联函数】

1、概念

inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率

  • C++的祖师爷呢,认为【宏】存在一定的缺陷,但是呢也有它的好处,也就是直接做替换,不需要开辟函数栈帧,所以在C++中就出现了内敛函数这么一个东西
inline int Add(int x, int y)
{
	int z = x + y;
	return z;
}
  • 但是它真不会去进行函数调用吗?我们到VS中来看看

在这里插入图片描述

  • 那此时就又同学疑惑了🤨这不还是会有call指令吗,哪来的不调用一说呢?
  • 因为我们还需要去做一些配置💻

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 然后我们再去观察一下汇编就可以发现不存在call指令了,编译器直接将内敛函数Add做了展开

在这里插入图片描述

  • 所以C++中我们是不推荐用宏的,因为有内联函数这个特性,即保留了宏的优点,无需调用函数建立栈帧,而且还修复了宏的缺陷,不再需要将内容写得那么复杂,写成日常的函数形式即可,只需要在前面加上一个inline关键字,就可以起到这种效果。非但如此,它还可以调试💻

接下去我们再来介绍一下有关内敛函数的一些特性

2、特性①:空间换时间

有同学可能不是很理解空间换时间是什么意思,可以看看时空复杂度章节。不过要说明的一点是这里的空间不是内存,是编译后的程序,而【空间换时间】就会使得编译出来的可执行程序会变大

  • 对于这种空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用
    • 【缺陷】:可能会使目标文件变大;
    • 【优势】:少了调用开销,提高程序运行效率;
  • 可以通过下面这幅图来看看:此时程序中有一个swap()函数,内部有10行代码。在程序的1000个地方都会对其进行调用,那请问此时去使用内联函数和不使用内联函数的区别是什么?
    • 当不使用内联函数时,就是将这个swap()函数当做普通的函数的话,我们知道在底层会进行一个call指令再通过jump指令跳转到这个函数所在地址然后执行这个函数,那每一个调用的地方都要跳转的话,就会跳转1000次,所以swap + 调用swap指令,合计是1000 + 10条指令
    • 当使用内联函数时,通过上面的学习可以知道,它不会去进行一个函数的调用,而是直接将相关的指令拷贝到程序中调用这个swap()函数的每一块地方,所以swap + 调用swap指令,合计是1000 * 10条指令

在这里插入图片描述

  • 可以看出,在使用内联函数减少函数调用的同时也会增加程序的负担,使得目标文件会变得很大

🎁趣味杂谈:庞大的游戏更新包

在理解了一个函数设不设置内联函数的区别的时候,顺带来谈一谈这个目标文件体积变大的缘故

  • 要知道,干我们这一行基本一年到头都是在公司,也有逢年过节才能回家一趟,而且平常工作忙得也没有时间玩游戏,那此刻当你过年回家的时候,就看到家里的一些弟弟妹妹拿着手机📱在打《王者荣耀》,这相信是最正常不过了
  • 那此时呢,你也心里痒痒,打开了你尘封已久的王者荣耀,然后看到要更新,而且一看就是4、5个G,突然之间就不想玩了😅更新的过程中只能眼巴巴地看着别人玩,
  • 不过呢,你有个同事,也是很久都没有玩过游戏了,也是一样进去更新,但是呢她却3、5分钟更新好了,因为更新包只有3、500M。这是为什么呢?原来她是苹果手机,系统是IOS系统,其实对于更新的这些内容都是指令,只是你们的指令数目不同而已。那我们都知道【苹果】这家公司系统是自己设计的,而且对于像芯片、硬件、软件各方面都领先于同行
  • 所以说这个性能不一样就会手机系统上使用的软件也存在差异。别人这个性能好,在优化各种方面都齐全,所以就不会导致这个更新包越变越大,相反对比我们平常使用的几千元的安卓手机,却越用越卡,这也是很多人选择使用苹果手机的原因👈

在这里插入图片描述

3、特性②:inline实现机制

好,小插曲,我们回归正题,继续来讲讲内联函数的第二大特性 —— 【inline实现机制】

  • inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同
  • 一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性

《C++prime》第五版关于inline的建议:

在这里插入图片描述
我们可以到VS中来观察一下

  • 可以观察到当内联函数的内容增多时,程序中去调用这个内联函数依旧会执行call指令,而不是将其函数中的内容直接拷贝到程序调用处,即使是加了这个inline关键字,似乎还是起不到内联函数的作用,这是为什么呢?
  • 因为编译器在对于这个内联请求的时候发觉不对劲,所以选择忽略了这个请求,而不是像宏那样无论怎样都会傻傻地进行替换

在这里插入图片描述

  • 这其实就是内联函数在替代宏之后很优秀的一个特性,假设说现在你这个设置的内联函数有1000多行代码,在一个大项目中又有1000个地方调用了这个内联函数。
    • 如果不采用将其展开去调用的话消耗的顶多也就是1000 + 1000条指令
    • 如果采用内联将其展开的话消耗的就是1000 * 1000条指令,这就很恐怖了😱
  • 那上面这个还是个普通的大一点的函数,但你再想如果是**··**呢?层层地往下调用再一层层地返回来,那需要调用的指令就更多了,如果全部站开的话,就会造成一个灾难性的后果⚠

在这里插入图片描述

所以呢,加不加内敛是你的事,最终要不要把它真的展开变成内敛编译器说了算,所以我们在使用的时候一般是比较短小、调用频繁的函数(10几行以内)加上内联,其他就不加了

4、特性③:inline的声明与定义

inline内联函数的第三个特性,就是我们要注意内联函数的定义和声明不可以分开,导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到

// F.h
#include <iostream>
using namespace std;

inline void f(int i);
//--------------------------
// F.cpp
#include "F.h"

void f(int i)
{
	cout << i << endl;
}
//--------------------------
// main.cpp
#include "F.h"

int main()
{
	f(10);
	return 0;
}
  • 通过去VS中执行这段分文件代码,可以发现它不是一个【编译时错误】,而是一个【运行时错误

在这里插入图片描述
👉 这种错误一般都是最后链接目标文件的时候除了问题,不是很清楚的可以看看程序的编译和链接
👉 对于这些很复杂的符号func@@YAXH@Z,是C++中的函数名修饰,可以看看探究函数重载的原理:函数名修饰

在这里插入图片描述
那我们现在就要去思考,为什么会出现这种【运行时错误】呢?

  • 最主要的一点首先你要知道:内联函数的不进符号表的,因为对于内联函数来说它在其调用的地方都展开了,所以不需要产生一串指令把它放到符号表中,再通过一条一条指令去调用

反汇编观察函数调用的流程

对于内联函数来说是这样,但是普通函数来说是怎么一步步地进行调用的呢?我们可以通过汇编来看看💻

  • 对于函数的地址来说,在指针章节我们有说过,它值得就是【函数指针】,但是从汇编角度来看,它call的到底是谁的地址呢?

在这里插入图片描述

  • 通过按下F11跳转到了一条叫做【jmp】的指令,此时你去观察这个地址,其实就是这条【jmp】的地址,在英语中jump是跳的意思,那你可以将这条指令理解为一个中转站,call指令会暂时跳到这条指令所在的地址,然后再通过这条指令去找需要调用函数的地址

在这里插入图片描述

  • 此刻,再按下F10的话就会通过这个函数的地址找到这个函数,此时就开始执行函数内部的逻辑,

在这里插入图片描述

  • 然后,当执行完函数内部的逻辑后,便回到了call指令的下一条指令,这就算是一次函数调用的过程

在这里插入图片描述

可以将它们放到一起来观察,就可以发现它们之间的逻辑非常紧密

在这里插入图片描述
如果对上面过程还是不太理解的可以看看反汇编深挖【函数栈帧】的创建和销毁

内联函数的生成机制【⭐】

上面是对于普通函数的调用机制,那对于内联函数呢?也是这么去跳转吗?

  • 一定要注意!内联内联,那么这个函数的内容就直接放到调用的地方
  • 它便说:也就是我不需要你再通过这么繁琐的步骤一步步地跳转过来了,我会将内部的东西做一些优化,直接放到你那里,你执行这些指令即可

可是呢,为什么会出现链接错误❌

  • 因为在【预编译】的时候就要展开func.h这个头文件,但是在主调用接口中包含的头文件中只有函数的声明没有实现,此时只能在【链接】的时候展开了,但是在链接的时候因为只有声明所以只得到了函数名修饰后的地址。编译器便需要通过这个地址找到函数所在的位置,对于普通函数而言在这个时候就可以通过call找过去了,但是对于内联函数而言,却无法做到,因为它并没有【call】和【jmp】这些指令,因此就造成了链接错误的现象

那要如何去解决呢?

  • 若是这个函数要定义成内联函数的话,就不要将定义和声明分开了,在头文件中定义出来后就直接对其进行声明,便不会造成这样的问题了
// F.h
#include <iostream>
using namespace std;

inline void f(int i)
{
	cout << i << endl;
}
//--------------------------
// main.cpp
#include "F.h"

int main()
{
	f(10);
	return 0;
}

四、总结与提炼

好,最后来总结一下本文所学习的内容📖

  • 首先我们回顾来一下C语言中所学习的【宏】,经过了对宏的优缺点分析以及一些同学的错误案例对照,在面试的时候让你写一个宏,可不要写错了哦!
  • 接下去,我们就正式地开始介绍内联函数,对于内联函数来说,它不仅保留了宏的优点,没有函数调用建立栈帧的开销,而且还修复了宏的缺点,将其做成一个函数的形式,简洁直观,而且便于调试观察,提升程序运行的效率
  • 但是对于内联函数来说,也是存在要注意的地方,因为它也是会和宏一样在调用的地方展开,不过会进行一定程度的优化,可这种空间换时间的思想只适用于小型的函数,对于大型的函数不建议定义成【内联函数】,会造成程序的过多臃肿
  • 但是在看了内联函数的生成机制后,其实我们也不用担心在误用内联函数后使得程序变大,它会有一个自动判断的机制,若是你程序的行数过多的话,编译器就会忽略你的这请求,对于我们要将一个函数声明为内联函数其实是在向编译器发起一个申请,它可以选择接收也可以选择拒绝🙅‍

以上就是本文要介绍的所有内容,感谢您的阅读🌹

在这里插入图片描述

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

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

相关文章

【链表OJ题(八)】相交链表

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;数据结构 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录链表OJ题(八)8. 相交…

【C++笔试强训】第三十二天

&#x1f387;C笔试强训 博客主页&#xff1a;一起去看日落吗分享博主的C刷题日常&#xff0c;大家一起学习博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a;夜色难免微凉&#xff0c;前方必有曙光 &#x1f31e;。 &#x1f4a6;&a…

Spring Bean实例化和初始化的过程

承接上文Spring Bean生命周期应用程序在运行过程中能否去读取当前系统的环境变量或系统属性?这里涉及到一个非常重要的接口Environment&#xff0c;System.getenv&#xff0c;System.getProperties都是获取当前系统环境变量&#xff0c;Environment接口的实现类AbstractEnviro…

浏览器前进与后退的秘密——栈 (栈的理解与实现)

文章目录前言&#xff1a;浏览器与栈的纠缠如何理解“栈”&#xff1f;如何实现一个“栈”&#xff1f;基于数组的顺序栈基于链表的链式栈解答开篇&#x1f431;‍&#x1f409;作者简介&#xff1a;大家好&#xff0c;我是黑洞晓威&#xff0c;一名大二学生&#xff0c;希望和…

每日算法题

题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 作为篮球队教练&#xff0c;你需要从以下名单中选出 11 号位至 55 号位各一名球员&#xff0c;组成球队的首发阵容。 每位球员担任 11 号位至 55 号位时的评分如下…

CCF-CSP真题《202212-3 JPEG 解码》思路+python,c++满分题解

想查看其他题的真题及题解的同学可以前往查看&#xff1a;CCF-CSP真题附题解大全 试题编号&#xff1a;202212-3试题名称&#xff1a;JPEG 解码时间限制&#xff1a;1.0s内存限制&#xff1a;512.0MB问题描述&#xff1a; 问题背景 四年一度的世界杯即将画上尾声。在本…

STM32 OTA应用开发——通过串口/RS485实现OTA升级(方式2)

STM32 OTA应用开发——通过串口/RS485实现OTA升级&#xff08;方式2&#xff09; 目录STM32 OTA应用开发——通过串口/RS485实现OTA升级&#xff08;方式2&#xff09;前言1 环境搭建2 功能描述3 程序编写3.1 BootLoader部分3.2 APP的制作4 修改工程中的内存配置4.1 Bootloader…

面试阿里测开岗失败后,被面试官在朋友圈吐槽了......

前一阵子有个徒弟向我诉苦&#xff0c;说自己在参加某大厂测试面试的时候被面试官怼得哑口无言&#xff0c;场面让他一度十分尴尬印象最深的就是下面几个问题&#xff1a;根据你以前的工作经验和学习到的测试技术&#xff0c;说说你对质量保证的理解&#xff1f;非关系型数据库…

HashMap扩容为什么每次都是之前的2倍

一. 背景介绍HashMap的底层是通过数组链表红黑树的数据结构来存放数据的。我们知道&#xff0c;当新添加元素的key值出现了hash碰撞&#xff0c;就会在同一个bucket中形成链表或者红黑树。当键值对的数量超过阈值时就会扩容&#xff0c;将以前处于同一个链表或者红黑树上的元素…

持续集成 在 Linux 上搭建 Jenkins,自动构建接口测试

本篇把从 0 开始搭建 Jenkins 的过程分享给大家&#xff0c;希望对小伙伴们有所帮助。 文章目录 在 Linux 上安装 Jenkins在 Linux 上安装 Git在 Linux 上安装 Python在 Linux 上安装 Allure配置 Jenkinsjenkins 赋能 - 使用邮箱发送测试报告jenkins 赋能 - 优化测试报告内容…

C语言刷题(6)(猜名次)——“C”

各位CSDN的uu们你们好呀&#xff0c;今天&#xff0c;小雅兰还是在复习噢&#xff0c;今天来给大家介绍一个有意思的题目 题目名称&#xff1a; 猜名次 题目内容&#xff1a; 5位运动员参加了10米台跳水比赛&#xff0c;有人让他们预测比赛结果&#xff1a; A选…

测试管理之路 —— 如何优化测试计划以提高测试覆盖率

&#x1f60f;作者简介&#xff1a;博主是一位测试管理者&#xff0c;同时也是一名对外企业兼职讲师。 &#x1f4e1;主页地址&#xff1a;&#x1f30e;【Austin_zhai】&#x1f30f; &#x1f646;目的与景愿&#xff1a;旨在于能帮助更多的测试行业人员提升软硬技能&#xf…

Github的使用

Github Date: March 8, 2023 Sum: Github的使用 Github 了解开源相关的概念 1. 什么是开源 2. 什么是开源许可协议 开源并不意味着完全没有限制&#xff0c;为了限制使用者的使用范围和保护作者的权利&#xff0c;每个开源项目都应该遵守开源许可协议&#xff08; Open Sou…

node多版本控制

前言 最近在折腾Python&#xff0c;并将node升级至v18.14.2。突然发现一个旧项目无法运行&#xff0c;也无法打包&#xff0c;里面的node-sass报错&#xff0c;显然这是因为node版本过高导致的。 将node版本降低至以前的v14.16.0&#xff0c;果然立马就能正常运行。 存在不同…

mysql-日志备份

1.binlog 用于数据恢复&#xff0c; 用于数据复制。 mysql> show variables like %log_bin%; -------------------------------------------------------------- | Variable_name | Value | ----------------------------------…

前端代码复用学习笔记:整洁架构与清晰架构

基础代码的复用往往比较简单&#xff0c;但是业务代码的复用通常是困难的&#xff0c;如果没有特殊的手段去治理项目会逐渐发展为难以维护的巨石应用&#xff0c;按照维基百科记载&#xff0c;代码的复用形式主要有三种&#xff0c;程序库&#xff0c;应用框架&#xff0c;设计…

【无标题】 6UVPX 总线架构的高性能实时信号处理

VPX630 是一款基于 6U VPX 总线架构的高速信号处理平台&#xff0c;该平台采用一片 Xilinx 的 Kintex UltraScale 系列 FPGA&#xff08;XCKU115&#xff09;作为主处理器&#xff0c;完成复杂的数据采集、回放以及实时信号处理算法。 采用一片带有 ARM内核的高性能嵌入式处理…

GeoServer发布一张纯图片作为地图教程

从事GIS行业的小伙伴们可能会有这样的需求,就是客户给了一张纯图片。可能是一张手工绘图,也可能是一张影像图片,总归来说就是png,jpeg格式的纯图片,现在需要把这张图片加载到我们的地图上,该如何做呢?本文带你从0开始操作一遍。 首先我先准备好测试数据,是一张jpg格式…

LeetCode刷题——贪心法(C/C++)

这里写目录标题[中等]买卖股票的最佳时机 II[中等]移掉k位数字[中等]跳跃游戏[中等]跳跃游戏 II[中等]加油站[中等]划分字母区间[中等]无重叠区间[中等]用最少数量的箭引爆气球[中等]买卖股票的最佳时机 II 原题链接题解 最简单的思路&#xff0c;效率不高&#xff0c;只要明天…

基于 pytorch 的手写 transformer + tokenizer

先放出 transformer 的整体结构图,以便复习,接下来就一个模块一个模块的实现它。 1. Embedding Embedding 部分主要由两部分组成,即 Input Embedding 和 Positional Encoding,位置编码记录了每一个词出现的位置。通过加入位置编码可以提高模型的准确率,因为同一个词出现在…