Kotlin 进阶函数式编程技巧
Kotlin 进阶函数式编程技巧
Kotlin 简介
软件开发环境不断变化,要求开发人员不仅适应,更要进化。Kotlin 以其简洁的语法和强大的功能迅速成为许多人进化过程中的信赖伙伴。虽然 Kotlin 的初始吸引力可能是它的简洁语法和与 Java 的互操作性,但 Kotlin 的真正优势在于其更深层次的函数式编程能力。一旦掌握这些技术,就有可能改变我们处理问题、设计解决方案甚至理解代码的方式。
本文深入探讨 Kotlin 中的高级函数式编程,提供见解和现实世界的示例,旨在提高您的编码技能。无论你是在提高自己的技能还是初步接触这个领域,这里都是一个旨在与现代开发人员的挑战和愿景共鸣的指南。
Kotlin 的函数式基础
Kotlin 函数式编程的核心在于不可变性的概念和将函数视为一等公民。
1. 不可变数据结构
基本语法
在 Kotlin 中,“val”关键字表示只读(不可变)变量。虽然变量本身是不可变的,但它所指向的数据不一定是不可变的。这就是为什么 Kotlin 还提供了不可变集合。
val readOnlyList = listOf("a", "b", "c")
真实示例
考虑一个典型的电子商务应用程序。当用户查看他们的个人资料时,他们会看到他们过去的订单列表。为了在显示这些订单时防止意外修改,最好确保订单列表保持不可变。
data class Order(val orderId: Int, val product: String, val price: Double)// 假设我们从数据库或 API 中获取该列表
val userOrders: List<Order> = fetchOrdersFromDatabase()// 如果我们想要打折,我们可以通过创建具有更新价格的新列表来避免修改原始列表。
val discountedOrders = userOrders.map { order ->if (order.price > 100.0) {order.copy(price = order.price * 0.9) // 10% 折扣} else {order}
}
2. 一等公民函数
基本语法
Kotlin 支持将函数分配给变量、将它们作为参数传递或从其他函数中返回,这意味着它们可以作为一等公民。
fun greet(name: String) = "Hello, $name!"
val greetingFunction: (String) -> String = ::greet
println(greetingFunction("Bob")) // 输出:Hello, Bob!
真实示例
在图形渲染软件中,可以将各种效果(如模糊、锐化或颜色反转)应用于图像。通过将函数视为一等公民,可以将这些效果表示为函数并以各种方式组合。
fun blur(image: Image): Image = ...
fun sharpen(image: Image): Image = ...
fun invertColors(image: Image): Image = ...val effects = listOf(::blur, ::sharpen, ::invertColors)// 顺序地在图像上应用所有效果
val processedImage = effects.fold(originalImage) { img, effect -> effect(img) }
高级集合函数
Kotlin 提供了丰富的集合操作函数。除了基础知识,理解这些函数的复杂性可以极大提高代码的清晰度和效率。
1. 使用 map 和 flatMap 进行转换
基本语法
“map” 函数使用提供的转换函数转换集合中的每个元素。“flatMap” 可以转换和扁平化集合。
val numbers = listOf(1, 2, 3)
val squared = numbers.map { it * it } // [1, 4, 9]
真实示例
假设您有一个字符串列表,表示潜在 URL,并想要提取域名。不是每个字符串都是有效的 URL,因此这就是 “flatMap” 起作用的地方。
val potentialUrls = listOf("https://example.com/page", "invalid-url", "https://another-example.com/resource")val domains = potentialUrls.flatMap { url ->runCatching { URL(url).host }.getOrNull()?.let { listOf(it) } ?: emptyList()
}
// Result: ["example.com", "another-example.com"]
2. 使用 filter 和 filterNot 进行过滤
基本语法
“filter” 返回满足给定谓词的元素列表。“filterNot” 则相反。
val numbers = listOf(1, 2, 3, 4, 5)
val evens = numbers.filterNot { it % 2 == 0 } // [1, 3, 5]
真实示例
想象一下,基于多个动态条件(如价格范围、评分和可用性)而不仅仅是一个条件来筛选产品。
data class Product(val id: Int, val price: Double, val rating: Int, val isAvailable: Boolean)val products = fetchProducts() // 假设这会获取产品列表val filteredProducts = products.filter { product ->product.price in 10.0..50.0 && product.rating >= 4 && product.isAvailable
}
Sure! Here is the content organized using Markdown:
使用 fold 和 reduce 进行累积操作
fold 和 reduce 的概述
fold
和 reduce
都用于累积操作,但它们在使用场景和语法上有一些不同。
fold
用途:对集合的元素执行操作,需要一个初始的累加器值和一个组合操作。可以处理任何类型的集合。
基本语法:
val numbers = listOf(1, 2, 3, 4)
val sumStartingFrom10 = numbers.fold(10) { acc, number -> acc + number } // 结果: 20
例子:例如,将字符串连接起来
val words = listOf("apple", "banana", "cherry")
val concatenated = words.fold("Fruits:") { acc, word -> "$acc $word" }
// 结果: "Fruits: apple banana cherry"
reduce
用途:与 fold
类似,但不需要一个初始的累加器值。它使用集合的第一个元素作为初始的累加器。
基本语法:
val numbers = listOf(1, 2, 3, 4)
val product = numbers.reduce { acc, number -> acc * number } // 结果: 24
例子:结合自定义数据结构。假设我们有一个范围的列表,我们想要合并范围:
val ranges = listOf(1..5, 3..8, 6..10)
val combinedRange = ranges.reduce { acc, range -> acc.union(range) }
// 结果: 1..10
关键区别
- 初始值:
fold
需要一个显式的初始累加器值。reduce
使用集合的第一个元素作为初始值。
- 适用性:
fold
可以处理任何大小的集合,包括空集合(因为有初始累加器值)。reduce
在空集合上会抛出异常,因为没有初始值来开始操作。
- 灵活性:
fold
更灵活,允许定义与集合元素类型不同的初始值。reduce
有类型约束,要求累加器和集合的元素必须是相同的类型。
使用 groupBy 和 associateBy 进行分区
groupBy 和 associateBy 的概述
groupBy
根据键选择器函数的结果返回一个将元素分组的 Map。associateBy
根据提供的键选择器将每个元素作为键返回一个 Map。
基本语法:
val words = listOf("apple", "banana", "cherry")
val byLength = words.groupBy { it.length } // {5=[apple], 6=[banana, cherry]}
示例
假设我们有一个学生对象的列表,我们想要根据学生的 ID 对其进行分组。
data class Student(val id: String, val name: String, val course: String)val students = fetchStudents()// 假设 students 包含:
// Student("101", "Alice", "Math"), Student("101", "Eve", "History"), Student("102", "Bob", "Science")val studentsById = students.associateBy { it.id }
// 结果的 Map 将是:
// {"101"=Student("101", "Eve", "History"), "102"=Student("102", "Bob", "Science")}
在上面的例子中,Eve 覆盖了 Alice,因为它们都有相同的 ID “101”。结果的 Map 只保留了具有该 ID 的最后一个学生的详细信息。
关键区别
groupBy
创建一个 Map,其中每个键指向原始集合中的项目列表。associateBy
创建一个 Map,其中每个键指向原始集合中的单个项目。如果存在重复项,最后一个元素将覆盖其他元素。
在选择使用 groupBy
还是 associateBy
时,主要考虑是否需要保留具有相同键的所有元素(使用 groupBy
),还是只保留最后一个元素(使用 associateBy
)。
在 Kotlin 中的函数组合
想象一下你有一个玩具工厂的装配线,在这条线上的每个工位上,玩具都要经历特定的变化。玩具从一个工位移动到下一个工位,每个步骤都会进行修改。
在编程中,尤其是在 Kotlin 中,当你将两个函数链接在一起,使得第一个函数的结果成为下一个函数的输入时,就像玩具从一个工位流畅地移动到终点一样。
想象一下我们的玩具工厂有三个工位:
- A工位:给玩具上色。
- B工位:将轮子安装到已上色的玩具上。
- C工位:在已装有轮子的玩具上贴上贴纸。
这些工位就像函数一样,每个函数按照顺序执行自己的任务。
在 Kotlin 中,让我们将这些工位表示为函数:
fun paint(toy: Toy): Toy { /*上色并返回玩具*/ }
fun attachWheels(toy: Toy): Toy { /*安装轮子并返回玩具*/ }
fun placeSticker(toy: Toy): Toy { /*贴上贴纸并返回玩具*/ }
我们不想手动地将玩具从一个工位移动到下一个工位,我们希望有一个自动化的过程,使得玩具可以顺利地从开始到结束。这就是函数组合发挥作用的地方。
为了使其在 Kotlin 中生效,我们将定义一个 compose
函数:
infix fun <A, B, C> ((B) -> C).compose(g: (A) -> B): (A) -> C {return { x -> this(g(x)) }
}
这个 compose
函数是我们链接两个工位(函数)的工具。它确保一个工位的输出成为下一个工位的输入。
现在,使用 compose
函数,我们可以定义我们的自动化玩具装配线:
val completeToyProcess = ::placeSticker compose ::attachWheels compose ::paint
当你将原始玩具放入 completeToyProcess
中时,它会自动被上色、安装轮子,然后贴上贴纸。
实际示例
val rawToy = Toy()
val finishedToy = completeToyProcess(rawToy)
在这个例子中,原始玩具经过整个过程,变成了完成的玩具——上色、安装轮子和贴上贴纸,全部在一个流畅的操作中完成。
为什么这很有用?
清晰明了:就像我们的玩具工厂类比一样,您可以一次性看到整个装配线过程。您可以快速了解玩具经历的变化顺序。
灵活性:如果您需要不同的结果,您可以轻松地更改顺序或添加/删除工位(或函数)。
效率:您无需在每次修改后存储玩具;它只需通过装配线不断移动。
需要注意的事项:
顺序很重要:就像不能在玩具上涂贴标签之前连续涂色一样,链接函数的顺序至关重要。
保持简单:如果您的装配线(或函数链)太长,就会变得难以理解或管理。这就像我们的玩具工厂中有太多工位一样。因此,平衡是关键!
科里化 - 增量决策的力量
想象一下您正在一家多功能咖啡店。他们不提供现成的饮料,而是给您一系列的选择。首先,您选择咖啡豆的类型,然后决定使用牛奶(或替代品),最后选择任何额外的口味或配料。
现在,假设您是一位常客,总是选择阿拉比卡咖啡豆,但会根据心情变化其他选择。咖啡店不会让您每次都从头开始选择,而是记住您的豆子偏好。这种方法节省时间,减少决策疲劳,并让您可以专注于当下最重要的事情。
这就像科里化在编程中所实现的功能。
分解问题
简化复杂的决策:就像选择一杯咖啡需要几个步骤一样,一些函数有很多参数。科里化将这些多参数函数简化为一系列更简单的函数链。该链中的每个函数都接受一个参数并返回下一个要使用另一个参数调用的函数。
记住偏好:通过科里化函数,您可以“记住”特定的决策(或函数参数)。在我们的咖啡示例中,您对阿拉比卡咖啡豆的喜好被记住了,让您可以进行其他选择。
专注于重要事项:有时,您并没有所有的信息。科里化允许您在信息可用时进行决策。就像当您来到柜台时,即使几天前选择了咖啡豆类型,您也可以稍后再决定使用牛奶和口味。
代码示例
假设有一个订购咖啡的函数。
当您使用科里化时,可以在函数调用过程中使用标记来指定特定的参数。这样做可以提供更灵活和可读性更高的代码。
在函数签名中使用标记:
fun orderCoffee(bean: String): (milk: String) -> (flavor: String) -> Coffee { ... }
在这个示例中,我们在函数签名中为 milk
和 flavor
参数添加了标记。这使得在函数调用时可以明确地指定每个参数的值。
使用标记进行函数调用:
val arabicaOrder = orderCoffee("Arabica")
val myCoffee = arabicaOrder(milk = "Almond Milk")(flavor = "Vanilla")
通过在函数调用中使用标记,我们可以清楚地表达每个参数的含义,并且不需要按照特定顺序传递参数。这提高了代码的可读性,并且对于具有多个可选参数的函数尤其有用。
对于具有多个标记参数的函数,您可以根据自己的需求选择要使用的参数,并省略其他参数。例如:
val arabicaWithFlavor = arabicaOrder(flavor = "Caramel")
val myCoffee = arabicaWithFlavor(milk = "Whole Milk")
在这个示例中,我们只指定了 milk
和 flavor
参数,并忽略了 bean
参数。这样,我们可以通过只提供所需的标记参数来创建定制的函数,而无需重复指定其他参数。
使用标记进行函数调用可以提高代码的可读性和灵活性,并使函数调用更具表达力和可维护性。标记参数允许您以更直观的方式指定参数,并且不需要依赖于参数的顺序。
单子——编程的安全网
想象一下组装一个DIY家具套件。说明书中的每个步骤都依赖于前一个步骤。然而,并不是所有的步骤都那么简单,有时你可能会发现缺少一个部件或者意识到你在之前的步骤中犯了个错误。
如果说明书带有内置的安全网岂不是太好了?例如,如果你准备在错误的地方固定螺丝钉,说明书会立即提醒你。或者,如果有一块零件缺失,它会提供一个权宜之计或告诉你如何在没有它的情况下继续进行。
这种“安全网”概念在DIY世界中就是单子给编程带来的。
理解单子
- 依赖步骤——就像家具组装涉及一系列依赖步骤一样,编程中的操作通常是一个链条,其中每个链接都依赖前面的成功。
- 安全机制——单子充当了一个安全机制,确保如果一个步骤失败或没有产生有效值,后续的步骤能够意识到并做出相应反应。
- 封装挑战——单子将值与产生这些值的上下文捆绑在一起,无论是通过成功、错误还是某些副作用产生的。
实际应用
Kotlin的Optional是一种单子形式。想象一下从数据库查询用户资料的情况 -
fun findUserProfile(id: Int): Optional<UserProfile> {// 一些获取资料的逻辑
}
假设我们想获取用户的电子邮件 -
val emailOpt = findUserProfile(123).flatMap { profile -> profile.email }
如果findUserProfile找不到资料,它可能会返回一个空的Optional。flatMap操作不会崩溃或抛出错误;它只会产生另一个空的Optional。
这就像我们的DIY说明书的安全网。如果一个步骤不能完成,它不会停止整个过程,而是给你一个安全继续进行的方式。
单子引起注意
- 优雅的失败:单子允许函数以优雅的方式失败。它们确保进程继续前行,即使是为了传达一个错误。
- 直观的流程:有了单子,代码的流程变得更直观,更能反映现实生活中的决策过程。
- 增强的可组合性:由于它们可链式使用的特性,单子导致更模块化和适应性更强的代码。
惰性求值和序列——提供高效的操作
曾经去过自助餐厅,决定只拿你确定会吃的菜,而不是一次性把盘子填满,可能浪费食物吗?这种策略让你在需要时消耗所需,确保最大限度地享受,最少的浪费。
编程中的惰性求值采用了类似的策略。它不是预先计算所有东西,而是在需要时计算所需的内容。在Kotlin中,序列是实现这一目标的主要方式。让我们深入了解!
理解惰性求值
- 惰性求值是一种计算策略,其中表达式仅在实际需要其结果时进行评估。这可以提高内存使用效率和执行速度,尤其是在处理大型集合时。
Kotlin Sequences
在 Kotlin 中,sequences(Sequence)表示一种惰性计算的集合。与列表不同,sequences 不保存数据;相反,它们描述在请求时生成数据元素的计算过程。
实际应用——Sequences vs. Lists
考虑一个数字列表,我们想要在平方后找到第一个可被 5 整除的数。
使用列表:
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
val result = numbers.map { it * it } // 平方所有数字.filter { it % 5 == 0 } // 过滤所有可被 5 整除的平方数.first() // 获取第一个项目
println(result) // 25
在这种方法中,我们对所有数字进行平方和过滤,只使用一个值。那太低效了!
使用 sequence:
val numbersSeq = numbers.asSequence()val resultSeq = numbersSeq.map { it * it }.filter { it % 5 == 0 }.first()println(resultSeq) // 25
使用 sequences,每个数字都会被平方,检查是否可被 5 整除,然后当找到第一个这样的数字时,该过程停止。因此,在这种情况下,sequence 只会平方和过滤,直到它找到数字 5。那是高效的!
通过 Sequences 的惰性评估带来的好处:
- 效率——只计算必要的部分。
- 灵活性——可以表示无限数据结构。
- 内存节省——在处理大型数据集时尤为重要。
在 Kotlin 中采用 sequences 和惰性评估,就像采用“边走边消费”的方法一样。它使开发人员能够编写高效和可扩展的代码,特别是在涉及大量数据操作的场景下。
尾递归——利用 Kotlin 编写高效的递归
这样想——你站在高楼的底层,向上凝视着永无止境的楼梯。如果你一个接一个地爬每个台阶,你可能很快就会累垮,或者感到不知所措。但是,如果你可以一次跳过多个楼层,并使用爬一个台阶所需的相同的能量呢?这就是 Kotlin 中尾递归的魔力!
解析递归
递归是一种编程技术,其中函数调用自身以将复杂问题分解为简单的子问题。但是,标准递归可以很快占用大量内存,特别是对于大型输入。每个函数调用都会添加到调用堆栈中,而对于深度递归,这可能会导致堆栈溢出错误。
引入尾递归
尾递归是递归的一种特殊形式,其中递归调用是函数中执行的最后一件事。Kotlin 的编译器优化尾递归函数以使用恒定的堆栈空间,防止堆栈溢出错误。
简单示例——阶乘
没有尾递归:
fun factorial(n: Int): Int {if (n == 1) return 1return n * factorial(n - 1)
}
使用尾递归:
fun factorial(n: Int, accumulator: Int = 1): Int {if (n == 1) return accumulatorreturn factorial(n - 1, n * accumulator)
}
在尾递归版本中,递归调用的结果(与当前操作相结合)作为累加器传递。它确保在递归调用后没有额外的操作待处理,使其成为有效的尾调用。
为什么使用尾递归?
- 效率——它使用恒定的堆栈空间,防止堆栈溢出。
- 清晰度——对于某些问题,递归解决方案可能更直观。
- Kotlin 的支持——只需添加 tailrec 修饰符,Kotlin 即可处理优化!
重要说明
必须确保递归真正处于尾部位置。如果递归调用后有任何操作待处理,该函数将不是尾递归,并且 Kotlin 的编译器将无法优化它。
尾递归背后发生的事情是什么?
在传统递归中,每个函数调用都会被堆叠,等待下一个函数完成其自身计算之前。随着函数调用的堆叠,内存使用量将增加,特别是对于大型输入数字。
在我们的尾递归版本中,发生了以下情况:
- 每个递归调用都被优化以重用当前函数的堆栈帧,因为在递归调用后没有剩余的计算(例如阶乘中的乘法)。
- 累加器充当运行总数,保存中间结果。这意味着,到达基本情况(n == 1)时,我们已经在累加器中得到了答案,无需“往回走”。
- Kotlin 编译器看到 tailrec 修饰符,并识别出函数是尾递归的。然后,它在幕后优化字节码,以确保函数使用恒定的堆栈内存,无论输入大小如何。
实质上,我们的阶乘函数,当调用 factorial(5)
时,从计算:
5 * 4 * 3 * 2 * 1
转换为:
(((5 * 1) * 4) * 3) * 2
这种转换确保在到达基本情况时即可得到答案,同时使用恒定的堆栈空间。
另一个说明
尽管尾递归优化是 Kotlin 中的一个强大功能,但值得注意的是,这个概念并不是该语言所专有的。许多其他编程语言,包括函数式语言(如 Haskell)和更通用的语言(如 Scala),都支持尾递归。但是,它们实现和优化的方式可能不同。在过渡各种语言或与来自不同背景的开发人员讨论该主题时,请始终考虑这一点。
结论
在我们探讨 Kotlin 中的高级函数式编程时,我们已经看到了 Kotlin 提供的深度和多功能性。从集合函数的复杂性、函数组合的优雅性到尾递归的效率,Kotlin 为开发人员提供了强大的工具。虽然这些概念在 Kotlin 中得到了强调,但它们是更广泛的函数式编程世界中的支柱。通过掌握它们,您不仅可以优化 Kotlin 技能,而且还可以使用永恒的编程原则。在您继续前进时,请让这些工具和技巧指导您的 Kotlin 之旅,以生成更有效、更干净、更易于维护的代码。
相关文章:

Kotlin 进阶函数式编程技巧
Kotlin 进阶函数式编程技巧 Kotlin 简介 软件开发环境不断变化,要求开发人员不仅适应,更要进化。Kotlin 以其简洁的语法和强大的功能迅速成为许多人进化过程中的信赖伙伴。虽然 Kotlin 的初始吸引力可能是它的简洁语法和与 Java 的互操作性,…...

操作系统——内存映射文件(王道视频p57)
1.总体概述: 2.传统文件访问方式: 我认为,这种方式最大的劣势在于,如果要对整个文件的不同部分进行多次操作的话,这样确实开销可能会大一些,而且程序员还要指定对应的“分块”载入到内存中 3.内存映射文件…...

王道p18 07.将两个有序顺序表合并为一个新的有序顺序表,并由函数返回结果顺序表。(c语言代码实现)
视频讲解在这:👇 p18 第7题 c语言代码实现王道数据结构课后代码题_哔哩哔哩_bilibili 本题代码如下 int merge(struct sqlist* A, struct sqlist* B, struct sqlist* C) {if (A->length B->length > C->length)//大于顺序表的最大长度r…...

2024最新mac电脑清理垃圾的软件有哪些?
mac电脑是许多人喜爱的电子产品,它拥有优美的设计、流畅的操作系统和强大的性能。但是,随着使用时间的增长,mac电脑也会积累一些不必要的垃圾文件,这些文件会占用宝贵的存储空间,影响电脑的运行速度和稳定性。因此&…...

2023年【山东省安全员C证】考试技巧及山东省安全员C证模拟试题
题库来源:安全生产模拟考试一点通公众号小程序 山东省安全员C证考试技巧考前必练!安全生产模拟考试一点通每个月更新山东省安全员C证模拟试题题目及答案!多做几遍,其实通过山东省安全员C证模拟考试题很简单。 1、【多选题】《环境…...

2024最新免费的mac电脑清理垃圾的软件有哪些?
mac电脑是许多人喜爱的电子产品,它拥有优美的设计、流畅的操作系统和强大的性能。但是,随着使用时间的增长,mac电脑也会积累一些不必要的垃圾文件,这些文件会占用宝贵的存储空间,影响电脑的运行速度和稳定性。因此&…...

linux下sqlplus登录oracle显示问号处理办法
问题描述 昨天紧急通过rpm按安装方式给客户装了一台linux的19c数据库,操作系统是CentOs Stream release 9,过程不再回忆了… 今天应用发现sqlplus登入后部分显示问号?,需要处理下 原因分析: 很明显,这就是…...

Git 删除本地和远程分支
目录 删除本地和远程分支分支删除验证验证本地分支验证远程分支 开源项目微服务商城项目前后端分离项目 删除本地和远程分支 删除 youlai-mall 的 dev 本地和远程分支 # 删除本地 dev 分支(注:一定要切换到dev之外的分支才能删除,否则报错&…...

Selenium元素定位之页面检测技巧
在进行web自动化测试的时候进行XPath或者CSS定位,需要检测页面元素定位是否正确,如果用脚本去检测,那么效率是极低的。 一般网上推选装额外的插件来实现页面元素定位检测 如:firebug。 其实F12开发者工具就能直接在页面上检测元…...

C# 文件 文件夹 解除占用
文件/文件夹 解除占用或直接删除。 编程语言:C# 这个就不用过多功能描述了。 注册windows 文件/文件夹 右键菜单。 文件夹解除占用:遍历文件夹所有文件,判断是否被占用,先解除文件占用,后解除文件夹占用࿰…...

数据库 存储引擎
存储引擎概念 在mysql当中数据库用不同的技术存储在文件中,每一种技术都是使用不同的存储引擎机制,索引技巧,锁定水平,以及最终提供的不同的功能和能力,这些就是我们说的存储引擎 主要功能 1mysql将数据存储在文件系…...

操作系统复习(2)进程管理
一、概述 1.1程序的顺序执行 一个具有独立功能的程序独占CPU运行,直至得到最终结果的过程称为程序的顺序执行。 程序的并发执行所表现出的特性说明两个问题 ⑴ 程序和计算机执行程序的活动不再一一对应 ⑵ 并发程序间存在相互制约关系(要求共享信息&…...

通过51单片机控制28byj48步进电机按角度正反转旋转
一、前言 本项目基于STC89C52单片机,通过控制28BYJ-48步进电机实现按角度正反转旋转的功能。28BYJ-48步进电机是一种常用的电机,精准定位和高扭矩输出,适用于许多小型的自动化系统和机械装置。 在这个项目中,使用STC89C52单片机…...

二十三种设计模式全面解析-装饰器模式的高级应用:打造灵活可扩展的通知系统
在现代软件开发中,通知系统是一个广泛应用的功能,用于实时向用户发送各种类型的通知,如短信、微信、邮件以及系统通知。然而,通知系统的需求通常是多变且动态的,因此需要一种灵活可扩展的设计模式来满足不同类型的通知…...

使用脚本整合指定文件/文件夹,执行定制化 ESLint 命令
背景 最近面对一个庞大的项目,但是只需要修改某个模块,每次都手搓命令太麻烦了,于是就想着能不能写个脚本来辅助处理这些事情。 解决方案 定制化一键 ESLint,执行文件下载地址: https://github.com/mazeyqian/go-g…...

C++ static与类
C static与类 1. 不和对象直接相关的数据,声明为static2. static成员函数没有this指针3.在类的外部定义static成员变量4.static与类的一些小应用 1. 不和对象直接相关的数据,声明为static 想象有一个银行账户的类,每个人都可以开银行账户。存…...

数据结构之堆的实现(图解➕源代码)
一、堆的定义 首先明确堆是一种特殊的完全二叉树,分为大根堆和小根堆,接下来我们就分别介绍一下这两种不同的堆。 1.1 大根堆(简称:大堆) 在大堆里面:父节点的值 ≥ 孩子节点的值 我们的兄弟节点没有限制&…...

持续集成部署-k8s-配置与存储-配置管理:ConfigMap
持续集成部署-k8s-配置与存储-配置管理:ConfigMap 1. ConfigMap 简介2. 创建 ConfigMap3. ConfigMap 环境变量与配置文件加载3.1 环境变量的使用3.2 配置文件加载1. ConfigMap 简介 在Kubernetes (K8s) 中,ConfigMap是一种用于存储配置数据的API对象。它用于将应用程序的配置…...

【漏洞复现】Apache_HTTP_2.4.50_路径穿越漏洞(CVE-2021-42013)
感谢互联网提供分享知识与智慧,在法治的社会里,请遵守有关法律法规 文章目录 1.1、漏洞描述1.2、漏洞等级1.3、影响版本1.4、漏洞复现1、基础环境2、漏洞扫描3、漏洞验证方式一 curl方式二 bp抓捕 1.5、修复建议 说明内容漏洞编号CVE-2021-42013漏洞名称…...

【KVM】软件虚拟化和硬件虚拟化类型
前言 大家好,我是秋意零。 今天介绍的内容是虚拟化技术以及软件虚拟化和硬件虚拟化。 👿 简介 🏠 个人主页: 秋意零🔥 账号:全平台同名, 秋意零 账号创作者、 云社区 创建者🧑 个…...

ES-初识ES
文章目录 介绍ElasticSearchElasticSearch的主要功能ElasticSearch的主要特性ElasticSearch的家族成员LogStashKibanaBeats ELK(ElasticSearch LogStash Kibana)的应用场景与数据库集成指标采集/日志分析 安装和配置ElasticSearch一、安装1、下载ES安装…...

foreach、for in和for of的区别?
foreach,for...in和for...of是三种不同的循环结构,它们在JavaScript中用来遍历数组或对象的属性。它们有一些重要的区别,以及各自的优点和适用情况。 1.foreach:这是最普通的循环结构,它遍历数组或对象的每一个元素或属…...

CVE-2023-21839 weblogic rce漏洞复现
文章目录 一、漏洞影响版本二、漏洞验证三、启动反弹shell监听切换路径到jdk1.8 四、启动ldap服务五、使用CVE-2023-21839工具来进行攻击测试六、反弹shell 一、漏洞影响版本 CVE-2023-21839是Weblogic产品中的远程代码执行漏洞,由于Weblogic IIOP/T3协议存在缺陷&…...

MQTT java代码演示
以下是使用Eclipse Paho客户端库的Java代码示例,用于连接到MQTT代理并发布和订阅消息。 首先,需要添加Maven依赖项到项目中: <dependency> <groupId>org.eclipse.paho</groupId> <artifactId>org.eclipse.paho.client.mqttv3</artifactId>…...

Windows环境下使用VLC获取到大疆无人机的RTMP直播推流
1.环境准备 1.安装nginx 1.7.11.3 Gryphon 下载地址:http://nginx-win.ecsds.eu/download/ 下载nginx 1.7.11.3 Gryphon.zip,解压后修改文件夹名称为nginx-1.7.11.3-Gryphon; 2.安装nginx-rtmp-module 下载地址:GitHub - arut…...

【SpringBoot笔记42】SpringBoot集成knife4j生成接口文档
这篇文章,主要介绍SpringBoot如何集成knife4j及生成接口文档。 目录 一、knife4j接口文档生成器 1.1、接口文档工具介绍 1.2、引入依赖...

Go类型嵌入介绍和使用类型嵌入模拟实现“继承”
Go类型嵌入介绍和使用类型嵌入模拟实现“继承” 文章目录 Go类型嵌入介绍和使用类型嵌入模拟实现“继承”一、独立的自定义类型二、继承三、类型嵌入3.1 什么是类型嵌入 四、接口类型的类型嵌入4.1 接口类型的类型嵌入介绍4.2 一个小案例 五、结构体类型的类型嵌入5.1 结构体类…...

【深度学习】pytorch——实现CIFAR-10数据集的分类
笔记为自我总结整理的学习笔记,若有错误欢迎指出哟~ 往期文章: 【深度学习】pytorch——快速入门 CIFAR-10分类 CIFAR-10简介CIFAR-10数据集分类实现步骤一、数据加载及预处理实现数据加载及预处理归一化的理解访问数据集Dataset对象Dataloader对象 二、…...

Datawhale-AIGC实践
Datawhale-AIGC实践 部署ChatGLM3-6B平台 clone 项目,配置环境 git clone https://github.com/THUDM/ChatGLM3.git cd ChatGLM3 pip install -r requirement.txt修改web_demo.py, web_demo2.py 设置加载模型的路径修改启动代码: demo.queue().launch(shareFalse…...

C++对象模型
思考:对于实现平面一个点的参数化。C的class封装看起来比C的struct更加的复杂,是否意味着产生更多的开销呢? 实际上并没有,类的封装不会产生额外的开销,其实,C中在布局以及存取上的额外开销是virtual引起的…...