当前位置: 首页 > news >正文

【28】Kotlin语法进阶——使用协程编写高效的并发程序

提示:此文章仅作为本人记录日常学习使用,若有存在错误或者不严谨得地方欢迎指正。

文章目录

  • 一、Kotlin中的协程
    • 1.1 协程的基本用法
      • 1.1.1协程与协程作用域
      • 1.1.2 使用launch函数创建子协程
      • 1.1.3 通过suspend关键声明挂起函数
      • 1.1.4 coroutineScope函数
    • 1.2 更多的作用域构建器
      • 1.2.1 项目中创建协程的常用方法
      • 1.2.2 获取协程的返回值
      • 1.2.3 withContext函数

一、Kotlin中的协程

协程是Kotlin语言中很有代表性的一种并发设计模式,用于简化异步执行的代码。协程和线程有点类似,可以简单地将它理解成一种轻量级的线程。我们前面学习的线程是属于重量级的,这是因为线程需要依靠操作系统的调度来实现不同线程之间的切换。而协程仅在编程语言的层面就能实现不同协程之间的切换,无需操作系统的介入,从而极大提高了并发编程的运行效率。
举一个具体的例子,例如我们有foo()和bar()这两个方法:

fun foo(){a()b()c()
}
fun bar(){x()y()z()
}

在没有开启线程的情况下,先调用foo()方法后调用bar()方法,理论上结果一定是a()、b()、c()执行完了以后,x()、y()、z()才能够得到执行。而如果在协程A中调用foo()方法,在协程B中调用bar()方法。虽然它们仍运行在同一个线程中,但在执行foo()方法时随时都有可能被挂起而去执行bar()方法;同理在执行bar()方法时也随时都有可能被挂起转而继续执行foo()方法,这就使得最终输出的结果变得不确定了。
可以看出,协程允许我们在单线程模式下模拟多线程编程的效果,代码执行时的挂起与恢复完全是由编程语言来控制的,和操作系统无关。

1.1 协程的基本用法

如果我们需要在项目中使用协程功能,需要在build.gradle.kts(:app)中添加以下依赖:

dependencies {· · ·implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") // 适用于Android项目implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9") // 适用于非Android项目
}    

创建一个CoroutinesTest.kt文件并在其中定义一个main()函数,然后在main()函数中使用GlobalScope.launch函数:

fun main() {GlobalScope.launch {println("codes run in coroutine scope")}
}

GlobalScope.launch函数可以创建一个协程的作用域,这样代码块中的代码就是在协程中运行的了。按照我们的理解,现在运行main()函数,应该会打印一句话才对。可是当你运行main()函数后却发现控制台中没有任何日志输出:
在这里插入图片描述

这是因为GlobalScope.launch函数每次创建的都是一个顶层协程,当应用程序运行结束时顶层协程也会跟着一起结束。刚才的日志之所以无法打印出来,正是因为代码块中的代码还没来得及运行,应用程序就结束了。为了解决这个问题,我们让程序延迟一段时间再结束就行了:

fun main() {GlobalScope.launch {println("codes run in coroutine scope")}// 让主线程休眠1s(1s后再关闭应用程序)Thread.sleep(1000)
}

可以看到,在使用Thread.sleep(1000)让主线程休眠1s后,日志可以正常打印出来了。
在这里插入图片描述
可是这种写法还是存在一些问题的,如果代码块中的代码在1秒钟内不能运行结束,那么就会被强制中断:

fun main() {GlobalScope.launch {println("codes run in coroutine scope")delay(1500)println("codes run in coroutine finished")}Thread.sleep(1000)
}

我们在代码块中加入了一个delay()函数,并在它后面又追加了一条打印。delay()函数可以让当前协程延迟一段时间后再运行,但它和Thread.sleep()方法不同。delay()函数只会挂起当前协程,并不会影响其他协程的运行。而Thread.sleep()方法会阻塞当前的线程,这样所有运行在该线程下的协程都会被阻塞

注意:delay()函数只能在协程的作用域或其他挂起函数中调用。

这里我们让协程挂起1.5s(打印第一行日志1.5s后再打印第二行日志),让主线程休眠1s(应用程序1s后结束)。重新运行程序,你会发现代码块中新增加的一条日志并没有打印出来,因为它还没来得及运行,应用程序就已经结束了。为了解决这个问题,我们可以借助runBlocking函数让应用程序在协程中所有代码都运行完了之后再结束

fun main() {runBlocking {println("codes run in coroutine scope")delay(1500)println("codes run in coroutine finished")}
}

runBlocking函数同样会创建一个协程的作用域,但它可以保证所有该协程作用域内的代码和子协程,在执行完毕之前会一直阻塞当前线程。需要注意的是runBlocking函数通常只应该在测试环境下使用,在正式环境中使用容易产生一些性能问题。重新运行程序,可以看到两条日志都能够正常打印出来了:
在这里插入图片描述

1.1.1协程与协程作用域

  • 协程(Coroutine)是一种轻量级的并发单元,可以在其执行过程中挂起并返回到其父协程或顶层协程。协程的主要特点是它们能够被挂起并恢复,这使得它们可以用来实现并发和异步编程。
  • 协程作用域(Coroutine Scope)则是一种定义协程生命周期范围的对象。每个协程都必须在某个作用域内运行,当作用域被销毁时,它内部的所有协程都会被自动取消。协程作用域可以用来管理协程的启动、取消和结构化并发

简单来说,协程是轻量级的并发单元,而协程作用域则是定义协程生命周期范围的对象,它可以用来管理和控制协程的执行。

1.1.2 使用launch函数创建子协程

接下来我们学习一下如何使用launch函数创建多个协程

fun main() {runBlocking {launch {println("launch1")delay(1000)println("launch1 finished")}launch {println("launch2")delay(1000)println("launch2 finished")}}
}

这里的launch函数和我们前面所使用的GlobalScope.launch函数不同。首先①launch函数必须在协程的作用域中才能调用,其次②launch函数会在当前协程的作用域下创建子协程子协程的特点是如果外层作用域的协程结束了,该作用域下的所有子协程也会一同结束,而GlobalScope.launch函数创建的永远是顶层协程
我们在runBlocking函数结构中调用了两次launch函数,也就相当于创建了两个子协程。重新运行程序,结果如下:
在这里插入图片描述
可以看到两个子协程中的日志是交替打印的,说明它们确实是像多线程一样并发运行的。然而这两个协程实际却运行在同一个线程当中,只是由编程语言来决定如何在多个协程之间进行调度。调度的过程完全不需要操作系统参与,这也就使得协程的并发效率会更高。
为了直观的体验到协程在处理并发事件时的效率,我们进行以下实验:

fun main() {// 协程开始执行的时间val start = System.currentTimeMillis()runBlocking {repeat(100000) {launch {println(".")}}}// 协程执行完毕的时间val end = System.currentTimeMillis()println("协程执行时间:${end - start}ms")
}

我们使用repeat()函数创建了10万个协程,并让每个协程打印一行日志,然后记录下整个操作的耗时。重新运行程序,可以看到我们仅耗时348毫秒,这足以看出协程是多么的高效。试想一下,如果我们开启的是10万个线程,程序或许早都因为内存泄露而崩溃了。
在这里插入图片描述

1.1.3 通过suspend关键声明挂起函数

随着launch函数中的逻辑越来越复杂,可能你需要将部分launch函数中的代码提取到一个单独的函数中。这个时候就产生了另一个问题:我们在launch函数中编写的代码是拥有协程作用域的,但是如果将其提取到一个单独的函数中就没有协程作用域了,那么我们该如何调用像delay()这样的挂起函数呢?
为此Kotlin提供了一个suspend关键字,使用suspend关键字可以将任意函数声明成挂起函数,而挂起函数之间都是可以相互调用的:

fun main() {// 协程开始执行的时间val start = System.currentTimeMillis()runBlocking {repeat(100000) {launch {printDot()}}}// 协程执行完毕的时间val end = System.currentTimeMillis()println("协程执行时间:${end - start}ms")
}// 通过suspend关键字将printDot()声明成挂起函数
suspend fun printDot() {println(".")delay(1000)
}

这样printDot()函数就是一个挂起函数了,我们也就可以在printDot()函数中调用delay()函数。但suspend关键字只能将一个函数声明成挂起函数,却无法给他提供协程作用域的。例如你现在尝试在printDot()函数中调用launch函数,一定是无法调用成功的,因为launch函数要求必须在协程作用域当中才能调用

1.1.4 coroutineScope函数

为了解决suspend关键字作用域的问题,我们可以借助coroutineScope函数来解决。由于coroutineScope函数也是一个挂起函数,因此可以在其他挂起函数中调用它。coroutineScope函数的特点是会继承外部协程的作用域并创建一个子协程,借助这个特性,我们可以给任意挂起函数提供协程作用域

suspend fun printDot() = coroutineScope {launch {println(".")delay(1000)}
}

通过在printDot()内部使用coroutineScope函数,使得coroutineScope函数内部启动的协程(由launch创建)继承了printDot()函数的父作用域(也就是调用printDot()函数的外部作用域)。
可以看到,现在我们可以在printDot()这个挂起函数中调用launch函数了。coroutineScope函数和runBlocking函数有点类似,它可以保证其作用域内的所有代码和子协程在全部执行完之前,外部的协程会一直被挂起。例如下面的示例代码:

fun main() {// runBlocking确保其作用域内所有代码和子协程都执行完毕前会阻塞当前线程runBlocking {// coroutineScope确保其作用域内所有代码和子协程都执行完毕前会阻塞当前线程coroutineScope {launch {for (i in 1..10) {println(".")delay(1000)}}}println("coroutineScope finished")}println("runBlocking finished")
}

我们先使用runBlocking函数创建了一个协程作用域,然后这个主协程作用域内又调用了coroutineScope函数创建了另一个新的协程作用域,并等待该作用域内的所有子协程执行完毕。运行结果如下:
在这里插入图片描述
由此可见coroutineScope函数确实是将外部runBlocking协程挂起了。只有当coroutineScope作用域内所有的代码和子协程都执行完毕后,才会执行它之后的代码。需要注意,虽然coroutineScope函数和runBlocking函数很类似,但coroutineScope函数只会阻塞当前协程,既不会影响其他协程,也不会影响任何线程,因此是不会造成任何性能上的问题的。而runBlocking函数则会挂起外部线程,如果你恰好又在主线程中调用runBlocking函数的话,很可能会造成界面卡死,所以不推荐在实际项目中体验。

1.2 更多的作用域构建器

在上一小节的内容中,我们学习了GlobalScope.launch、runBlocking、launch、coroutineScope这几种作用域构建器。它们之间的调用方法还是有些许不同的:

  • GlobalScope.launch{ }函数、runBlocking{ }函数可以在任意地方调用。(实际项目中不推荐使用
  • coroutineScope{ }函数可以在协程作用域或者挂起函数中调用。
  • launch{ }函数只能在协程作用域中调用。

注意:
①由于runBlocking{ }函数会阻塞当前线程,因此只建议在测试环境下使用,不建议在实际项目中使用。
②而GlobalScope.launch{ }函数每次创建的都是顶层协程(当应用程序结束时顶层协程也会结束),除非明确需要创建顶层协程,否则也不建议在实际项目中使用


这里将讲一下为什么不建议在实际项目中使用顶层协程:
不建议使用的原因是它的管理成本太高了。例如我们在某个Activity中使用协程发起了一条网络请求,由于网络请求是耗时的,当用户在服务器还没来得及响应的情况下就关闭了当前Activity,按理说此时应该取消这条网络请求或者不应该进行回调操作。因为Activity已经被用户关闭了,此时就算服务器返回了数据也没有任何意义。
然而为了取消顶层协程,不管是GlobalScope.launch{ }函数还是launch{ }函数,它们都会返回一个Job对象,我们需要调用Job对象的cancel()方法来取消协程:

val job = GlobalScope.launch {· · ·
}
job.cancel()

如果我们每次创建的都是顶层协程,那么当Activity关闭时,我们就需要逐个调用所有已创建协程的cancel()方法,这简直是一种灾难!因此GlobalScope.launch{ }这种协程作用域构建器在实际项目中也不太常用。


1.2.1 项目中创建协程的常用方法

下面是实际项目中比较常见的写法:

fun main() {// 创建Job对象val job = Job()// 通过Job对象创建CoroutineScope对象val scope = CoroutineScope(job)// 通过CoroutineScope对象的launch{}函数创建协程scope.launch {//处理具体逻辑}// 关闭协程job.cancel()
}

我们先是创建了一个Job对象,然后通过这个Job对象来创建一个CoroutineScope对象。之后我们就可以通过调用这个CoroutineScope对象的launch{ }函数来创建一个协程了。现在所有调用CoroutineScope的launch{ }函数所创建协程,都会被关联在Job对象的作用域下面。现在我们只需要调用一次Job对象中的cancel()方法,就可以将同一作用域内的所有协程全部取消,从而很大程度上降低了协程管理的成本。
总而言之,CoroutineScope( )函数更适合实际项目当中使用。如果你只是在Main()函数中编写一些学习测试用的代码,还是使用runBlocking{ }函数最为方便。

1.2.2 获取协程的返回值

通过前面的学习你已经知道了调用launch{ }函数可以创建一个新的协程,但是launch{ }函数只能用于执行一段逻辑,却不能获取执行的结果,因为他的返回值永远是一个Job对象。那么如何能够创建一个协程并获取它的执行结果呢?
其实我们通过async函数就可以实现,async函数必须在协程作用域当中才能调用。async函数会创建一个新的协程并返回一个Deferred对象,如果我们想要获取async函数代码块的执行结果,只需要调用Deferred对象的await()方法即可。下面是一段示例代码:

fun main() {runBlocking {val result = async {5 + 5}.await()println(result)}
}

我们通过async()函数将运算结果保存了下来,然后打印到日志中。
在这里插入图片描述
在调用了async函数之后,代码块中的代码就会立刻开始执行。当调用await()方法时,如果async函数体中的代码还没有执行完毕,那么当前协程(即 runBlocking 函数内的协程)会被挂起,直到可以获得async函数的执行结果。我们接下来编写一段代码进行验证:

fun main() {runBlocking {// 开始时间val start = System.currentTimeMillis()// result1延时1sval result1 = async {delay(1000)5 + 5}.await()// result2延时1sval result2 = async {delay(1000)4 + 6}.await()println("result is : ${result1 + result2}")// 结束时间val end = System.currentTimeMillis()println("一共耗时 : ${end - start}ms")}
}

这里我们使用了两个async函数来执行任务,在每个async代码块中调用delay()方法进行1秒的延时,然后在async代码块执行完毕后都调用了await()方法。按照我们之前的学习,await()方法使得async代码块中的代码在执行完毕之前会一直阻塞当前协程。运行程序后可以看到如下打印,足以说明协程是顺序执行的,即等待result1执行完毕后再执行result2的。
在这里插入图片描述
但是上面这种写法效率是很低的,因为每个async块执行完后都调用了await()方法。这意味着,第一个async块中的代码在执行完毕后必须等待1秒后才去执行第二个async块。而第二个async块同样会等待1秒后才去执行主线程中的其他代码,所以总的执行时间约为2秒。其实两个async函数完全可以同时执行,从而提高运行效率。现在对上述代码进行优化:

fun main() {runBlocking {val start = System.currentTimeMillis()val deferred1 = async {delay(1000)5 + 5}val deferred2 = async {delay(1000)4 + 6}// 在使用返回值的地方再调用await()方法println("result is : ${deferred1.await() + deferred2.await()}")val end = System.currentTimeMillis()println("一共耗时 : ${end - start}ms")}
}

可以看到在这段代码中,我们改变了调用await()函数的时机,仅在需要用到async代码块的执行结果时才调用await()方法去获取。这样两个async函数就可以并行执行了,第二个async再也不用等待第一个async完成之后才能执行了。这次重新运行程序:
在这里插入图片描述
可以看到我们的代码耗时从2015ms缩短为了1015ms,运行效率显著提升。这也就说明我们两个async中的代码确实是并行执行的,并且成功将他们的结果输出到日志中了。

1.2.3 withContext函数

最后我们再来学习一个比较特殊的作用域构建器——withContext( )函数。withContext()函数也是一个挂起函数,可以将其理解成async函数的一种简化版写法。示例代码如下:

fun main() {runBlocking {val result = withContext(Dispatchers.Default) {5 + 5}println(result)}
}

当我们调用withContext()函数后,会立即执行代码块中的代码,同时会将外部协程(runBlocking{ })挂起。当代码块中的代码全部执行完毕后,会将最后一行的执行结果作为withContext()函数的返回值进行返回。因此基本相当于val result = async{ 5 + 5 }.await()的写法,唯一不同的是withContext()函数会强制要求我们指定一个线程参数。
你已经知道协程是一种轻量级的线程,因此很多传统编程情况下需要开启多线程执行并发任务。然而借助协程,我们只需要在一个线程中开启多个协程来执行就可以了。这并不意味着我们就永远不需要开启线程了,比如说Android中的网络请求必须要在子线程中进行,即使你开启了协程去执行网络请求,假如它是主线程当中的协程,那么程序依然会报错。这个时候我们就应该通过线程参数给协程指定一个具体运行的线程。
线程参数主要有以下3种值可以选择:Dispatchers.Default、Dispatchers.IO、Dispatchers.Main。

  • Dispatchers.Default:使用一种默认低并发的线程策略,当你要执行的代码属于计算密集型任务时,开启过高的并发反而可能会影响任务的运行效率,因此就可以使用Dispatchers.Default。
  • Dispatchers.IO:使用一种较高并发的线程策略,当你要执行的代码大多数时间是在阻塞和等待中,比如说要执行网络请求时,为了能够支持更高的并发数量,此时就可以使用Dispatchers.IO。
  • Dispatchers.Main:表示不会开启子线程,而是在Android主线程中执行代码,但是这个值只能在Android项目中使用,纯Kotlin程序使用这种类型的线程参数会报错。

事实上除了coroutineScope{ }函数之外,其他所有的函数都是可以指定这样一个线程参数的,只不过withContext()函数是强制要求指定的,而其他函数则是可选的。

相关文章:

【28】Kotlin语法进阶——使用协程编写高效的并发程序

提示:此文章仅作为本人记录日常学习使用,若有存在错误或者不严谨得地方欢迎指正。 文章目录 一、Kotlin中的协程1.1 协程的基本用法1.1.1协程与协程作用域1.1.2 使用launch函数创建子协程1.1.3 通过suspend关键声明挂起函数1.1.4 coroutineScope函数 1.2…...

【大数据面试知识点】Spark的DAGScheduler

Spark数据本地化是在哪个阶段计算首选位置的? 先看一下DAGScheduler的注释,可以看到DAGScheduler除了Stage和Task的划分外,还做了缓存的跟踪和首选运行位置的计算。 DAGScheduler注释: The high-level scheduling layer that i…...

Pycharm引用其他文件夹的py

Pycharm引用其他文件夹的py 方式1:包名设置为Sources ROOT 起包名的时候,需要在该文件夹上:右键 --> Mark Directory as --> Sources ROOT 标记目录为源码目录,就可以了。 再引用就可以了 import common from aoeweb impo…...

目标检测-One Stage-YOLOv1

文章目录 前言一、YOLOv1的网络结构和流程二、YOLOv1的损失函数三、YOLOv1的创新点总结 前言 前文目标检测-Two Stage-Mask RCNN提到了Two Stage算法的局限性: 速度上并不能满足实时的要求 因此出现了新的One Stage算法簇,YOLOv1是目标检测中One Stag…...

PHP序列化总结3--反序列化的简单利用及案例分析

反序列化中生成对象里面的值,是由反序列化里面的值决定,与原类中预定义的值的值无关,穷反序列化的对象可以使用类中的变量和方法 案例分析 反序列化中的值可以覆盖原类中的值 我们创建一个对象,对象创建的时候触发了construct方…...

大一C语言程序细节复盘2

7-4 学生成绩排序 分数 27 全屏浏览题目 切换布局 作者 张泳 单位 浙大城市学院 假设学生的基本信息包括学号、姓名、三门课程成绩以及个人平均成绩&#xff0c;定义一个能够表示学生信息的结构类型。输入n&#xff08;n<50&#xff09;个学生的成绩信息&#xff0c;按照学生…...

【QT】跨平台区分32位和64位的宏

目录 0.背景 1.详细 0.背景 项目用到&#xff0c;原用的是 “WIN32”和“WIN64”,但是发现在64位下的时候&#xff0c;进了表示32位的代码&#xff0c;上网查找&#xff0c;原来是宏写错了&#xff0c;特此记录&#xff0c;适用windows和linux 1.详细 修改前&#xff1a; #…...

对抗AUTOMIXUP

文章目录 摘要1、简介2、相关工作3、ADAUTOMIX3.1、深度学习分类器3.2、生成器3.3 对抗增强3.3.1 对抗损失 3.4 对抗优化 4、实验4.1、分类结果4.1.1、数据集分类4.1.2、精细分类 4.2、校准4.3、鲁棒性4.4、遮挡鲁棒性4.5、迁移学习4.6、消融实验 5、结论附录AA.1 数据集信息A.…...

AMEYA360:什么是热敏电阻 热敏电阻基础知识详解

热敏电阻(thermistor)是对温度敏感的一种电子器件&#xff0c;其电阻值会随着温度的变化而发生改变。 热敏电阻按照温度系数不同分为正温度系数热敏电阻(PTC thermistor&#xff0c;即 Positive Temperature Coefficient thermistor)和负温度系数热敏电阻(NTC thermistor&#…...

RedisTemplate自增时保证原子性的lua脚本限制接口请求频率

场景&#xff1a;限制请求后端接口的频率&#xff0c;例如1秒钟只能请求次数不能超过10次&#xff0c;通常的写法是&#xff1a; 1.先去从redis里面拿到当前请求次数 2.判断当前次数是否大于或等于限制次数 3.当前请求次数小于限制次数时进行自增 这三步在请求不是很密集的时…...

《通信基站绿色低碳服务评价技术要求》团体标准顺利通过技术审查

2023年12月14日团体标准《通信基站绿色低碳服务评价技术要求》召开了技术审查视频会议。来自节能权威机构、科研院校、通信行业企业的专家以及标准编制组代表参加了本次会议。 技术审查专家组由郑州大学能动学院教授赵金辉、国家节能中心节能技术推广处处长辛升、中国标准化研…...

堆排序(C语言版)

一.堆排序 堆排序即利用堆的思想来进行排序&#xff0c;总共分为两个步骤&#xff1a; 1. 建堆 升序&#xff1a;建大堆 降序&#xff1a;建小堆 2. 利用堆删除思想来进行排序 1.1.利用上下调整法实现堆排序 第一步&#xff1a;建堆 好了&#xff0c;每次建堆都要问自己…...

实现区域地图散点图效果,vue+echart地图+散点图

需求&#xff1a;根据后端返回的定位坐标数据实现定位渲染 1.效果图 2.准备工作,在main.js和index.js文件中添加以下内容 main.js app.use(BaiduMap, {// ak 是在百度地图开发者平台申请的密钥 详见 http://lbsyun.baidu.com/apiconsole/key */ak: sRDDfAKpCSG5iF1rvwph4Q95M…...

Kubernetes 学习总结(41)—— 云原生容器网络详解

背景 随着网络技术的发展&#xff0c;网络的虚拟化程度越来越高&#xff0c;特别是云原生网络&#xff0c;叠加了物理网络、虚机网络和容器网络&#xff0c;数据包在网络 OSI 七层网络模型、TCP/IP 五层网络模型的不同网络层进行封包、转发和解包。网络数据包跨主机网络、容器…...

多人协同开发git flow,创建初始化项目版本

文章目录 多人协同开发git flow&#xff0c;创建初始化项目版本1.gitee创建组织模拟多人协同开发2.git tag 打标签3.git push origin --tags 多人协同开发git flow&#xff0c;创建初始化项目版本 1.gitee创建组织模拟多人协同开发 组织中新建仓库 推送代码到我们组织的仓库 2…...

「Kafka」入门篇

「Kafka」入门篇 基础架构 Kafka 快速入门 集群规划 集群部署 官方下载地址&#xff1a;http://kafka.apache.org/downloads.html 解压安装包&#xff1a; [atguiguhadoop102 software]$ tar -zxvf kafka_2.12-3.0.0.tgz -C /opt/module/修改解压后的文件名称&#xff1a; [a…...

PHP8的JIT(Just-In-Time)编译器是什么?

PHP8的JIT&#xff08;Just-In-Time&#xff09;编译器是什么&#xff1f; PHP8是最新的PHP版本&#xff0c;引入了JIT&#xff08;Just-In-Time&#xff09;编译器&#xff0c;以进一步提高性能和执行速度。 JIT编译器是一种在运行时将解释性语言转化为机器码的技术。在过去…...

【C++对于C语言的扩充】C++与C语言的联系,命名空间、C++中的输入输出以及缺省参数

文章目录 &#x1f680;前言&#x1f680;C有何过C之处&#xff1f;&#x1f680;C中的关键字&#x1f680;命名空间✈️为什么要引入命名空间&#xff1f;✈️命名空间的定义✈️如何使用命名空间中的内容呢&#xff1f; &#x1f680;C中的输入和输出✈️C标准库的命名空间✈…...

Excel中部分sheet页隐藏并设置访问密码

1、新建sheet1 2、新建sheet2 3、隐藏sheet2 4、保护工作簿、输密码 5、密码二次确认 6、隐藏的sheet2已经查看不了 7、想要查看时&#xff0c;按图示输入原密码即可 8、查看sheet2内容...

从零开始配置pwn环境:CTF PWN 做题环境

前期在kali2023环境安装的pwndocker使用发现不好用&#xff0c;so找了网上配置好pwn环境的虚拟机。 GitHub - giantbranch/pwn-env-init: CTF PWN 做题环境一键搭建脚本 可以直接下载我配置好的Ubuntu 16.04&#xff0c;为VMware导出的ovf格式 链接&#xff1a;百度网盘 请输…...

Vue3复习笔记

目录 挂载全局属性和方法 v-bind一次绑定多个值 v-bind用在样式中 Vue指令绑定值 Vue指令绑定属性 动态属性的约束 Dom更新时机 ”可写的“计算属性 v-if与v-for不建议同时使用 v-for遍历对象 数组变化检测 事件修饰符 v-model用在表单类标签上 v-model还可以绑定…...

【OpenCV】OpenCV:计算机视觉的强大工具库

摘要   OpenCV是一个广泛应用于计算机视觉领域的开源工具库&#xff0c;为开发者提供了丰富的图像处理和计算机视觉算法。本文将介绍OpenCV的功能和应用领域&#xff0c;并探讨它在实践中的重要性和前景。 计算机视觉的强大工具库 一、什么是OpenCV&#xff1f;二、OpenCV的功…...

spring-boot-autoconfigure误引入spring-boot-starter-data-jpa而导致数据源初始化异常

一、现状描述 某个Grade类引入了jpa的注解&#xff1a; import javax.persistence.Column; import javax.persistence.Embeddable;/*** 年级*/ Embeddable public class Grade {Column(name "code")private int code; }并且pom.xml中引入该jar包&#xff1a;sprin…...

工程(十六)——自己数据集跑Fast_livo

一、基础环境 Ubuntu20.04 ROS noetic PCL 1.8 Eigen 3.3.4 Sophus git clone https://github.com/strasdat/Sophus.git cd Sophus git checkout a621ff mkdir build && cd build && cmake .. make sudo make install 下面两个直接把包下载下来一起编译…...

PostgreSQL数据库的json操作

1.操作符 select json字段::json->key值 from order -- 对象域 select json字段::json->>key值 from order -- 文本 select json字段::json#>{key值} from order -- 对象域 select json字段::json#>>{key值} from order -- 文本对象域表示还能继续操作&#…...

gradio-osprey-demo

创建需要的dockerfle ################### # 使用 Ubuntu 作为基础镜像 FROM nvcr.io/nvidia/cuda:11.8.0-devel-ubuntu22.04 # 更新软件包列表并安装依赖项 RUN apt update && \ apt install -y python3 python3-pip git ffmpeg libsm6 libxext6 curl wget …...

从仿写持久层框架到MyBatis核心源码阅读

接上篇手写持久层框架&#xff1a;https://blog.csdn.net/liwenyang1992/article/details/134884703 MyBatis源码 MyBatis架构原理&主要组件 MyBatis架构设计 MyBatis架构四层作用是什么呢&#xff1f; API接口层&#xff1a;提供API&#xff0c;增加、删除、修改、查询…...

浏览器常用基本操作之python3+selenium4自动化测试

1、打开指定的网页地址 我们使用selenium进行自动化测试时&#xff0c;打开浏览器之后&#xff0c;第一步就是让浏览器访问我们指定的地址&#xff0c;可使用get方法实现 1 2 3 from selenium import webdriver driver webdriver.Edge() driver.get(https://www.baidu.com/)…...

在MySQL中使用VARCHAR字段进行日期筛选

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…...

微信小程序自定义步骤条效果

微信小程序自定义一个步骤条组件&#xff0c;自定义文字在下面&#xff0c;已完成和未完成和当前进度都不一样的样式&#xff0c;可点击上一步和下一步切换流程状态&#xff0c;效果如下。 这是视频效果&#xff1a; 前端实现步骤条效果 下面我们一步步实现编码&#xff0c;自定…...

贵阳做网站的大公司有哪些/引流黑科技app

ls /etc/sysconfig/network-scripts/ifcfg-* 列出所有的设备配置文件 vim /etc/sysconfig/network-scripts/ifcfg-ens33 打开配置文件进行编辑&#xff0c;BOOTPROTO默认情况是dhcp动态获取的&#xff0c;改成static静态&#xff0c;再添加网关信息 BROADCAST192.168.1.255IPAD…...

企业网站源码哪个好/nba最新交易

2019独角兽企业重金招聘Python工程师标准>>> 在遍历数组的时候swift的forin给我们提供了更方便的模式,直接上代码解释 let btnName ["aaa", "bbb", "ccc", "ddd", "eee"] for (index,name) in btnName.enumerat…...

网站开发是否属于技术合同/搜索引擎app

文章目录hello程序信息就是位 上下文程序被其他程序翻译成不同的格式预处理阶段编译阶段汇编阶段链接阶段了解编译系统如何工作是大有益处的优化程序性能理解链接时出现的错误避免安全漏洞处理器读并解释储存在内存中的指令系统的硬件组成总线I/O设备主存处理器运行hello程序高…...

四博互联做的网站/刷推广链接

文章目录1. 误差与拟合1.1 误差1.2 训练误差与测试误差1.3 过拟合&#xff08;overfitting&#xff09;和欠拟合&#xff08;underfitting&#xff09;2.模型选择2.1 正则化&#xff08;regularization&#xff09;2.2 留出法&#xff08;hold-out&#xff09;2.3 交叉验证法&a…...

郑州做网站好的公/抖音流量推广神器软件

什么叫消息队列消息(Message)是指在应用间传送的数据。消息可以非常简单&#xff0c;比如只包含文本字符串&#xff0c;也可以更复杂&#xff0c;可能包含嵌入对象。消息队列(Message Queue)是一种应用间的通信方式&#xff0c;消息发送后可以立即返回&#xff0c;由消息系统来…...

h5企业网站定制排名/今日新闻最新10条

1. hadoop环境搭建 1.1 伪分布式环境搭建 1.1.1 伪分布式环境搭建 1.1.2 伪分布式搭建结果 hdfs可视化界面&#xff1a; http://od001:50070/dfshealth.html#tab-overview yarn可视化界面&#xff1a; http://od001:8088/cluster 历史服务器可视化界面&#xff1a;http://od001…...