Flow深入浅出系列之更聪明的分享 Kotlin Flows

  • Flow深入浅出系列之在ViewModels中使用Kotlin Flows
  • Flow深入浅出系列之更聪明的分享 Kotlin Flows
  • Flow深入浅出系列之使用Kotlin Flow自动刷新Android数据的策略

Flow深入浅出系列之更聪明的分享 Kotlin Flows

使生命周期对上游流有效,以跳过不必要的工作。这是一系列有关在Android中使用Kotlin Flow的文章的第二部分。

在第一部分中,我们描述了当在ViewModel类中使用SharedFlow或StateFlow时,Kotlin Flow的主要限制:

当用户导航回Activity或Fragment后重新收集使用SharingStarted.WhileSubscribed()策略的SharedFlow或StateFlow时,其源上游流将始终从头重新启动,有时会导致执行不必要的工作,即先前缓存的数据仍然有效。

在上面的示例中,即使queryFlow的最新值在此期间未更改,仍将再次执行repository.search()。这意味着可能会进行不必要的网络请求或数据库查询。

LiveData不会遇到这个问题,因为它的观察者不需要在变得不活动时取消订阅:LiveData是生命周期感知的,并将推迟新结果的传递,直到它变得活动为止,同时还确保相同的结果永远不会被传递给相同的观察者两次(即使它再次变为活动状态)。有关更多详细信息,请参阅完整文章。

在第一部分的结尾处,我们得出结论,在仅依赖于标准的shareIn()stateIn()操作时,没有简单和正确的方法可避免执行此不必要的工作。

在第二部分中,我们将通过设计一个新的Flow运算符来解决这个效率问题,以使SharedFlows更好地与生命周期集成。

与Lifecycle同步

LiveData

LiveData比Flow更好地处理生命周期的核心原因是,生命周期状态会自动传播到所有LiveData实例的上游,从而它们可以在保留其状态的同时协作地暂停。确切地说,传播的信息是一个简化和聚合的生命周期状态,称为活动状态:

当LiveData实例的观察者中至少有一个进入STARTED生命周期状态时,LiveData实例变为活动状态。当订阅的观察者进入STARTED状态或新的已启动观察者订阅时,可能会发生这种情况;
当LiveData实例的没有观察者处于STARTED生命周期状态时,LiveData实例变为非活动状态。可能会发生这种情况,当已订阅的观察者从STARTED状态回退到CREATED状态或已启动的观察者取消订阅时。
当应用LiveData变换(如map()或switchMap())时,上游LiveData实例会自动继承下游LiveData实例的活动状态。

当LiveData实例处于非活动状态时,它们会自动推迟将新结果传递给下游观察者。此外,LiveData实现可能会通知以停止主动工作。例如,LiveData协程构建器将在其变为非活动状态时取消其协程块。

SharedFlow

SharedFlow(及其子类StateFlow)是一种特殊类型的Flow,它能够将单个源发出的值广播到多个观察者(称为收集器)并对新观察者进行最新值重播,就像LiveData一样。 SharedFlow实例既是值的收集器又是发射器,而上游值由SharedFlow独立于下游收集器收集。

Flow和SharedFlow不直接支持Android Lifecycle类,但在UI层必须使用AndroidX Lifecycle API(如Lifecycle.repeatOnLifecycle())来同步Flow收集与Android Lifecycle:Flow必须仅在UI处于STARTED生命周期状态或更高级别时收集,并且在UI不可见时应尽快取消收集协程。

像MutableSharedFlow这样的SharedFlow实现也能够公开当前已订阅收集器的数量,当与上述Lifecycle同步API结合使用时,可以用于推断与LiveData等效的活动状态:

  • 当第一个收集器订阅时,SharedFlow实例变为活动状态;
  • 当最后一个收集器取消订阅时,SharedFlow实例变为非活动状态。

shareIn()stateIn()扩展功能是从源上游Flow创建SharedFlow实例的最简单方法。它们接受一个SharingStarted参数,该参数是响应“活动状态”变化时用于启动和停止收集上游Flow(从单独的协程)的策略。

总之,SharedFlow可以使生命周期感知,但是当使用这些有限API时,对于活动状态更改所能做的唯一事情是启动或取消收集整个上游Flow的协程。上游Flow不能暂停并在离开时恢复:取消协程意味着失去当前收集的整个内部状态,并且必须在下一次收集期间重新启动上游Flow。

这使得在上游Flow中使用像distinctUntilChanged()这样的运算符变得不切实际,因为一旦SharedFlow策略取消收集Flow的协程,最后发出的值就会被遗忘。Flow操作符不适用于跨多个收集周期的工作。

另一种方法
我们需要找到一个权衡。我们应该始终保持收集整个上游Flow的协程处于活动状态,而不是取消它,但将上游Flow分成两部分:

  • 上游流的上部分应与生命周期同步,并且在生命周期无效时应停止被收集;
  • 上游流的下部分应始终被收集,但在生命周期无效时将暂时不接收来自上部分的新值。

这样,就可以在下部分中使用像distinctUntilChanged()这样的运算符,在重新开始上部分时成功过滤掉重新发出的值,并避免在链下面进行不必要的工作。


val results: StateFlow<Result> =someTriggerFlow()                           // 上部分.someOperator(someFormOfLifecycle).distinctUntilChanged()                 // 下部分.map { someExpensiveLoadingOperation(it) }.stateIn(scope = viewModelScope,started = SharingStarted.Eagerly,initialValue = Result.empty())

这种方法是由 @hicham.boushaba 在他的文章“使冷流具备生命周期感知能力”中提出的,该文章为本文提供了起点。

要使Flow的上部具备生命周期感知能力,我们需要使用一个操作符,以某种形式的生命周期作为输入,当生命周期处于活动状态时,启动一个子协程来收集它,并在非活动状态下取消协程,同时将值传递给下部。

在ViewModel中注入Android Lifecycle?

Boushaba先生建议使用一个名为whenAtLeast()的新操作符,与自定义的Android Lifecycle-aware ViewModel结合使用。另一种选择是在标准ViewModel中使用由AndroidX Lifecycle库提供的flowWithLifecycle()操作符。这两个解决方案都要求在ViewModel类中保留对Android Lifecycle的引用。我认为出于以下不同的原因应该避免这样做:

  • 它使ViewModel更难测试,因为它增加了对Android特定Lifecycle类的额外依赖;
  • 它需要额外的复杂代码来在LifecycleOwner销毁后清除ViewModel中的Lifecycle引用,以防止内存泄漏;
  • 它没有考虑到一个单独的ViewModel可能同时被多个具有不同Lifecycle的组件观察。例如,常常使用ViewModel来实现Fragment与Activity或另一个Fragment之间的通信。支持这些用例需要重新实现LiveData已经支持的很多功能;

有一个更简单的解决方案。

引入flowWhileShared()操作符

事实证明,我们在ViewModel中根本不需要处理Android的Lifecycle,因为MutableSharedFlow已经提供了类似的功能。在前面的部分中,我们提到它通过subscriptionCount属性跟踪和公开当前订阅收集器的数量,并且这可以用来推断所需的活动状态。它在shareIn()和stateIn()内部使用,但也可以作为一个称为flowWhileShared()的自定义Flow操作符的输入值使用:

@OptIn(ExperimentalCoroutinesApi::class)
fun <T> Flow<T>.flowWhileShared(subscriptionCount: StateFlow<Int>,started: SharingStarted
): Flow<T> {return started.command(subscriptionCount).distinctUntilChanged().flatMapLatest {when (it) {SharingCommand.START -> thisSharingCommand.STOP,SharingCommand.STOP_AND_RESET_REPLAY_CACHE -> emptyFlow()}}
}

这个实现很简单,依赖于两个现有的Flow构建块:

使用SharingStarted来发射START和STOP命令的Flow,以响应subscriptionCount的变化。在99%的情况下,我们将使用的实现是SharingStarted.WhileSubscribed(),这样当subscriptionCount > 0时,会发射START命令,当subscriptionCount == 0时,会发射STOP命令,以匹配LiveData的行为;
使用flatMapLatest()操作符在上游Flow(this)和空Flow之间切换。在内部,此操作符创建一个子协程来收集这些Flow的值,并在每个新的命令上取消它。
注意:为了简单起见,特殊的“停止并重置重放缓存”命令(作用于父SharedFlow的缓存)被忽略,并被视为常规的“停止”命令。

与SharedFlow或StateFlow的使用

不幸的是,这个新操作符不能与shareIn()stateIn()结合使用,因为它们没有公开MutableSharedFlow的subscriptionCount字段。相反,我们需要创建和管理自己的MutableSharedFlow或MutableStateFlow的实例。

为了减少样板代码并避免重复,我们可以创建一个自定义的StateFlow工厂函数:

fun <T> stateFlow(scope: CoroutineScope,initialValue: T,producer: (subscriptionCount: StateFlow<Int>) -> Flow<T>
): StateFlow<T> {val state = MutableStateFlow(initialValue)scope.launch {producer(state.subscriptionCount).collect(state)}return state.asStateFlow()
}

这个函数立即从提供的作用域启动一个协程,并将subscriptionCount作为参数传递给上游Flow的生产者lambda表达式。然后,MutableStateFlow(实现了FlowCollector<T>)收集生成的Flow,最后将MutableStateFlow作为只读StateFlow返回。

在本文的末尾,您将找到一个优化版本的此函数的源代码(最小化编译器生成的类的数量)和SharedFlow的一个变体。

当然,只有在所有SharedFlow和StateFlow都是通过使用上面提到的AndroidX Lifecycle API从UI层收集时,整个系统才能按预期工作。例如:

viewLifecycleOwner.lifecycleScope.launch {viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {viewModel.results.collect { data ->displayResult(data)}}
}

用例

让我们看一些如何结合使用这些新函数的实际示例。

响应位置更新加载数据

假设我们有一个类似于本文中描述的 locationFlow() 函数,允许以冷的 Flow 形式接收位置更新,同时主动使用设备的 GPS。我们想要从 API 加载当前位置周围的商店列表,并在设备每次移动至少 30 米时更新该列表,以避免不必要的 API 调用。同时,我们希望确保在屏幕不可见时释放 GPS 资源。

ViewModel 将包含与此类似的 StateFlow builder:

@OptIn(ExperimentalCoroutinesApi::class)
val location: StateFlow<List<Shop>> =stateFlow(viewModelScope, emptyList()) { subscriptionCount ->locationClient.locationFlow(interval = 20000L,smallestDisplacement = 30f).flowWhileShared(subscriptionCount,SharingStarted.WhileSubscribed(1000L)).distinctUntilChanged { old, new ->old.distanceTo(new) < 30f}.mapLatest { location ->shopsRepository.getShopsAtLocation(location)}.catch { emit(emptyList()) }}

(为了演示简化了错误处理)

stateFlow() 工厂函数以返回上游 Flow 的生产者 lambda 作为最后一个参数。该 lambda 允许在上游 Flow 中使用 subscriptionCount

上游 Flow 的主要部分将在 ViewModel 保持在内存中时无中断地被收集。但是,在 flowWhileShared() 之前的部分(在本例中为 locationFlow())仅在 StateFlow 处于活动状态(至少有一个订阅)时才会被收集,因此当它变为非活动时,GPS 将停止。由于 SharingStarted.WhileSubscribed(1000L) 策略,一旦 UI 隐藏且订阅数达到 0,集合就会在一秒钟后取消。这个额外的延迟可以避免在配置更改期间重新启动位置更新的成本(当新的 UI 实例几乎立即恢复 Flow 集合时)。

当用户离开屏幕并稍后回到屏幕时,locationFlow() 的收集将重新启动,并且仍将被无中断地收集的 distinctUntilChanged() 将处理过滤掉新的位置,这些位置与先前发布的位置太接近。这样,如果设备自上次 UI 可见以来移动的距离不足 30 米,我们就可以避免再次调用 API 进行不必要的工作。

最新结果仍将由 StateFlow 缓存,并在开始收集它时自动重放到当前 UI。

我们成功创建了最有效的实现,解决了该问题,而无需依赖 LiveData。

在无效时重新加载数据

尽可能应用程序应向用户展示最新版本的数据,而不需要手动刷新。为了实现这一点,存储库可以在每次完成写操作(创建/更新/删除)后向数据的观察者发送无效信号,最终触发刷新。

为了实现最大效率,只有在以下情况下才应从存储或网络重新加载数据:

  • 屏幕当前可见(在后台没有早期刷新);
  • 自上次加载以来数据确实发生了变化。

表示可以无效的数据的自然方法是从存储库返回结果的 Flow,而不是单个结果。

但是,由于我们希望在屏幕不可见时避免刷新数据,因此我们需要取消该 Flow 的收集。由于每个 Flow 集合都是自包含的,因此它始终会从相同屏幕的先前收集开始加载数据,而不管其是否已更改。

例如,Room Jetpack 库创建这种类型的 Flow,以提供对 DAO 中可观测数据集的支持。 Room 实现在 Flow 收集开始时执行数据库查询,然后每次更新相关表时执行。这意味着每次屏幕再次变为可见并且数据集在此期间没有更改时,都会执行不必要的数据库查询。

相比之下,Room 生成的 LiveData 实现更高效,因为只有在表自上次活动的 LiveData 以来发生更改时才会执行数据库查询。

为了使 Kotlin Flow 代码更高效,我们需要使用版本控制。

数据集的版本应该以 StateFlow<Int> 的形式在存储库中公开,同时还应提供获取当前数据快照的函数。例如:

interface CustomerRepository {val customersVersion: StateFlow<Int>suspend fun getAllCustomers(): List<Customer>suspend fun getActiveCustomers(): List<Customer>
}

存储库可以由远程 API 或本地存储(如数据库)支持;这并不重要。

每当成功更新数据集时,版本号必须以原子方式递增:

private val _customersVersion = MutableStateFlow(0)
suspend fun updateCustomerName(customerId: Long, newName: String) {// 在此处插入一些代码来更新数据_customersVersion.update { it + 1 }
}

提示:如果存储库由 Room 数据库支持,您不需要手动跟踪更改,可以使用 Room 的 InvalidationTracker 监视一组表:

fun RoomDatabase.createVersionFlow(vararg tables: String): StateFlow<Int> {val stateFlow = MutableStateFlow(0)invalidationTracker.addObserver(object : InvalidationTracker.Observer(tables) {override fun onInvalidated(tables: Set<String>) {stateFlow.update { it + 1 }}})return stateFlow.asStateFlow()
}

最后,在 ViewModel 中将所有拼图放在一起:

@OptIn(ExperimentalCoroutinesApi::class)
val activeCustomers: Flow<List<Customer>> =stateFlow(viewModelScope, emptyList()) { subscriptionCount ->customersRepository.customersVersion.flowWhileShared(subscriptionCount, SharingStarted.WhileSubscribed()).distinctUntilChanged().mapLatest {customersRepository.getActiveCustomers()}}

再次感谢 distinctUntilChanged() operator,一旦 StateFlow 变为活动状态,当前版本号就会与先前版本号(如果有)进行比较,只有在版本号更改时才会加载数据。问题解决。

最后的建议

对于许多情况,标准的 shareIn() stateIn() operator 仍然应优先于flowWhileShared()的复杂性。特别是:

  • 用于一次性数据加载;
  • 当上游 Flow 的源是屏幕上的 UI 元素(例如刷新按钮)时,因此在屏幕不可见时永远不会发出新值,并且无需取消上游 Flow 集合;
  • 当从存储库重新加载相同数据的成本很低时,例如因为实现支持共享缓存。在屏幕可见时每次重新加载数据是可以接受的。

结论

使用新的 flowWhileShared() operator,我们成功将上游 Flow 的一部分与下游 SharedFlow 或 StateFlow 的生命周期同步,借助 distinctUntilChanged() 等 operator 跳过不必要的工作。所有这些都不需要在 Android 应用程序的 ViewModel 中包含任何 LiveData 或 Android 特定的 Lifecycle 代码。

完整源代码

//FlowExt.kt
package be.digitalia.flowimport kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingCommand
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.launchinline fun <T> stateFlow(scope: CoroutineScope,initialValue: T,producer: (subscriptionCount: StateFlow<Int>) -> Flow<T>
): StateFlow<T> {val state = MutableStateFlow(initialValue)producer(state.subscriptionCount).launchIn(scope, state)return state.asStateFlow()
}inline fun <T> sharedFlow(scope: CoroutineScope,replay: Int = 0,extraBufferCapacity: Int = 0,onBufferOverflow: BufferOverflow = BufferOverflow.SUSPEND,producer: (subscriptionCount: StateFlow<Int>) -> Flow<T>
): SharedFlow<T> {val shared = MutableSharedFlow<T>(replay, extraBufferCapacity, onBufferOverflow)producer(shared.subscriptionCount).launchIn(scope, shared)return shared.asSharedFlow()
}fun <T> Flow<T>.launchIn(scope: CoroutineScope, collector: FlowCollector<T>): Job = scope.launch {collect(collector)
}@OptIn(ExperimentalCoroutinesApi::class)
fun <T> Flow<T>.flowWhileShared(subscriptionCount: StateFlow<Int>,started: SharingStarted
): Flow<T> {return started.command(subscriptionCount).distinctUntilChanged().flatMapLatest {when (it) {SharingCommand.START -> thisSharingCommand.STOP,SharingCommand.STOP_AND_RESET_REPLAY_CACHE -> emptyFlow()}}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用kaliber与imu_utils进行IMU、相机+IMU联合标定

目录 1 标定工具编译 1.1 IMU标定工具 imu_utils 1.2 相机标定工具 kaliber 2 标定数据录制 3 开始标定 3.1 IMU标定 3.2 相机标定 3.3 相机IMU联合标定 4 将参数填入ORBSLAM的文件中 1 标定工具编译 1.1 IMU标定工具 imu_utils 标定IMU我们使用imu_utils软件进行标定…

Variations-of-SFANet-for-Crowd-Counting记录

论文&#xff1a;Encoder-Decoder Based Convolutional Neural Networks with Multi-Scale-Aware Modules for Crowd Counting 论文链接&#xff1a;https://arxiv.org/abs/2003.05586 源码链接&#xff1a;GitHub - Pongpisit-Thanasutives/Variations-of-SFANet-for-Crowd-C…

yarn : 无法加载文件 C:\Program Files\nodejs\yarn.ps1

问题描述&#xff1a; 问题分析&#xff1a; 这个错误提示说明在电脑系统上禁止运行 PowerShell 脚本&#xff0c;因此导致无法加载 Yarn 的安装脚本。这是由于系统的执行策略&#xff08;Execution Policies&#xff09;设置所导致的。 解决方法&#xff1a; 1. 以管理员身…

Ubuntu中不能使用ifconfig命令

​ 问题 打开终端使用如下命令不能运行&#xff1a; ifconfig显示如下错误: 解决方法 在VMware中的虚拟机下面打开“编辑虚拟机设置”&#xff0c;或者在已经打开的虚拟机面板上面打开“虚拟机—设置” 选择网络适配器&#xff0c;选择“NAT模式”&#xff0c;没开机的就…

Learning Sample Relationship for Exposure Correction 论文阅读笔记

这是中科大发表在CVPR2023的一篇论文&#xff0c;提出了一个module和一个损失项&#xff0c;能够提高现有exposure correction网络的性能。这已经是最近第三次看到这种论文了&#xff0c;前两篇分别是CVPR2022的ENC&#xff08;和这篇文章是同一个一作作者&#xff09;和CVPR20…

ardupilot开发 --- 起飞前后 篇

起飞前检查 电机响应是否正确&#xff08;转向&#xff09;姿态响应是否正常&#xff08;roll pitch yaw&#xff09;GPS数据是否正常&#xff08;星数&#xff0c;RTK信号&#xff09;电源电压安全开关安全检测&#xff08;armed pre check&#xff09; 起飞前的必调参数 机…

mmlab 做实验

首先 下载项目完整代码&#xff0c;在pycharm中打开 1. comfig 中有各种网络模型&#xff0c;可以直接使用训练好的预训练模型&#xff0c;尽量不要改动网络模型的结构 2. 18表示网络机构18层&#xff0c;8是每个卡的batch&#xff0c;cifar10 是数据集 3.配置文件解析 4. …

工业机器视觉系统构成及功能

工业机器视觉系统构成及功能 工业机器视觉系统由光源、光学传感器、图像采集设备、图像处理设备、机器视觉软件、辅助传感器、控制单元和执行机构等组件构成。 光源提供光线以辅助图像获取。 光学传感器将外部场景转换为电信号。 图像采集设备将信号转换为图像数据&#xf…

VSCode连接代理

VSCode连接代理 首先有代理 然后在设置里搜代理 然后再在windows的设置–>网络–>代理 拼接上就行 最后重启

【小尘送书-第八期】《小团队管理:如何轻松带出1+1>2的团队》

大家好&#xff0c;我是小尘&#xff0c;欢迎你的关注&#xff01;大家可以一起交流学习&#xff01;欢迎大家在CSDN后台私信我&#xff01;一起讨论学习&#xff0c;讨论如何找到满意的工作&#xff01; &#x1f468;‍&#x1f4bb;博主主页&#xff1a;小尘要自信 &#x1…

macos 12 支持机型 macOS Monterey 更新中新增的功能

macOS Monterey 能让你以全然一新的方式与他人沟通联络、共享内容和挥洒创意。尽享 FaceTime 通话新增的音频和视频增强功能&#xff0c;包括空间音频和人像模式。通过功能强大的效率类工具&#xff08;例如专注模式、快速备忘录和 Safari 浏览器中的标签页组&#xff09;完成更…

【unity】【VR】白马VR课堂系列-VR开发核心基础04-主体设置-XR Rig的引入和设置

接下来我们开始引入并构建XR Rig。 你可以将XR Rig理解为玩家在VR世界中的替身。 我们先删除Main Camera&#xff0c;在Hierarchy右键点击删除。 然后再在场景层右键选择XR下的XR Origin。这时一个XR Origin对象就被添加到了Hierarchy。 重设XR Origin的Position和Rotation…

数据结构 - 4(栈和队列6000字详解)

一&#xff1a;栈 1.1 栈的概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原…

JVM三色标记

三色标记 什么是三色标记法 三色标记法&#xff0c;也被称为Tri-color Marking Algorithm&#xff0c;是一种用于追踪对象存活状态的垃圾回收算法。它基于William D. Hana和Mark S. McCulleghan在1976年提出的两色标记法的基础上进行了改进。 与两色标记法只能将对象标记为“…

[GSEP202306 一级] C++ 时间规划

题目描述 小明在为自己规划学习时间。现在他想知道两个时刻之间有多少分钟&#xff0c;你通过编程帮他做到吗? 输入格式 输入4行&#xff0c;第一行为开始时刻的小时&#xff0c;第二行为开始时刻的分钟&#xff0c;第三行为结束时刻的小时&#xff0c;第四行为结束时刻的分…

笔记本Win10系统一键重装操作方法

笔记本电脑已经成为大家日常生活和工作中必不可少的工具之一&#xff0c;如果笔记本电脑系统出现问题了&#xff0c;那么就会影响到大家的正常操作。这时候就可以考虑给笔记本电脑重装系统了。接下来小编给大家介绍关于一键重装Win10笔记本电脑系统的详细步骤方法。 推荐下载 系…

【Linux学习笔记】代码编译工具gcc/g++

1. 程序的翻译1.1. 预处理1.2. 编译1.3. 汇编1.4. 链接 2. 动态链接和静态链接2.1. 动态链接2.2. 静态链接2.3. 动态链接和静态链接的区别2.4. 动态链接和静态链接的验证 3. make/makefile3.1. 依赖方法和依赖关系3.2. 写一个makefile文件3.3. PHONY的意义3.4. makefile的推导规…

无人机电力巡检:国网安徽实际案例解析

在科技快速发展的今天&#xff0c;传统行业正在经历前所未有的转型。电力巡检&#xff0c;这一看似传统且乏味的任务&#xff0c;却因为无人机技术的介入而焕发新生。今天&#xff0c;让我们深入了解一个具体的案例&#xff0c;探索无人机如何革新电力巡检。 案例背景&#xff…

【Machine Learning】03-Unsupervised learning

03-Unsupervised learning 3. Unsupervised Learning3.1 无监督学习&#xff08;Unsupervised Learning&#xff09;3.1.1 聚类&#xff08;Clustering&#xff09;3.1.2 K-均值聚类算法&#xff08;K-means Clustering&#xff09;3.1.3 高斯分布&#xff08;Gaussian distrib…

深度学习入门(一)之感知机

文章目录 前言什么是感知机简单的逻辑电路与门与非门和或门 感知机的实现简单的实现导入权重和偏置 感知机的局限性异或门线性和非线性 多层感知机已有门电路的组合异或门的实现 代码合集 前言 感知机是由美国学者1957年提出来&#xff0c;作为神经网络的起源算法。因此学习感…