冲呀!Kotlin Jetpack 实战之Kotlin 高阶函数 !

标签: android  移动开发  kotlin  jetpack  高阶函数

前言

1. 高阶函数有多重要?

高阶函数,在 Kotlin 里有着举足轻重的地位。它是 Kotlin 函数式编程的基石,它是各种框架的关键元素,比如:协程Jetpack ComposeGradle Kotlin DSL。高阶函数掌握好了,会让我们在读源码的时候“如虎添翼”。

本文将以尽可能简单的方式讲解 Kotlin 高阶函数Lambda 表达式,以及函数类型。在本文的最后,我们将自己动手编写一个 HTML Kotlin DSL

前期准备

  • 将 Android Studio 版本升级到最新
  • 将我们的 Demo 工程 clone 到本地,用 Android Studio 打开:

github.com/chaxiu/Kotl…

  • 切换到分支:chapter_04_lambda
  • 强烈建议各位小伙伴小伙伴跟着本文一起实战,实战才是本文的精髓

正文

1. 函数类型,高阶函数,Lambda,它们分别是什么?

1-1 函数类型(Function Type)是什么?

顾名思义:函数类型,就是函数的类型。

//         (Int,  Int) ->Float 
//           ↑      ↑      ↑
fun add(a: Int, b: Int): Float { return (a+b).toFloat() }
复制代码

将函数的参数类型返回值类型抽象出来后,就得到了函数类型(Int, Int) -> Float 就代表了参数类型是 两个 Int 返回值类型为 Float 的函数类型。

1-2 高阶函数是什么?

高阶函数是将函数用作参数或返回值的函数。

上面的话有点绕,直接看例子吧。如果将 Android 里点击事件的监听用 Kotlin 来实现,它就是一个典型的高阶函数

//                      函数作为参数的高阶函数
//                              ↓
fun setOnClickListener(l: (View) -> Unit) { ... }
复制代码

1-3 Lambda 是什么?

Lambda 可以理解为函数的简写

fun onClick(v: View): Unit { ... }
setOnClickListener(::onClick)

// 用 Lambda 表达式来替代函数引用
setOnClickListener({v: View -> ...})
复制代码

看到这,如果你没有疑惑,那恭喜你,这说明你的悟性很高,或者说你基础很好;如果你感觉有点懵,那也很正常,请看后面详细的解释。


2. 为什么要引入 Lambda 和 高阶函数?

刚接触到高阶函数和 Lambda 的时候,我就一直有个疑问:为什么要引入 Lambda 和 高阶函数?这个问题,官方文档里没有解答,因此我只能自己去寻找。

2-1 Lambda 和 高阶函数解决了什么问题?

这个问题站在语言的设计者角度会更明了,让我们看个实际的例子,这是 Android 中的 View 定义,我省略了大部分代码:

// View.java
private OnClickListener mOnClickListener;
private OnContextClickListener mOnContextClickListener;

// 监听手指点击事件
public void setOnClickListener(OnClickListener l) {
    mOnClickListener = l;
}

// 为传递这个点击事件,专门定义了一个接口
public interface OnClickListener {
    void onClick(View v);
}

// 监听鼠标点击事件
public void setOnContextClickListener(OnContextClickListener l) {
    getListenerInfo().mOnContextClickListener = l;
}

// 为传递这个鼠标点击事件,专门定义了一个接口
public interface OnContextClickListener {
    boolean onContextClick(View v);
}
复制代码

Android 中设置点击事件和鼠标点击事件,分别是这样写的:

// 设置手指点击事件
image.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        gotoPreview();
    }
});

// 设置鼠标点击事件
image.setOnContextClickListener(new View.OnContextClickListener() {
    @Override
    public void onContextClick(View v) {
        gotoPreview();
    }
});
复制代码

请问各位小伙伴有没有觉得这样的代码很啰嗦?

现在我们假装自己是语言设计者,让我们先看看上面的代码存在哪些问题:

  • 定义方:每增加一个方法,就要新增一个接口:OnClickListenerOnContextClickListener
  • 调用方:需要写一堆的匿名内部类,啰嗦,繁琐,毫无重点

[图片上传中…(image-76348e-1605078255000-3)]

仔细看上面的代码,开发者关心的其实只有一行代码:

gotoPreview();
复制代码

如果将其中的核心逻辑抽出来,这样子才是最简明的:

image.setOnClickListener { gotoPreview() }
image.setOnContextClickListener { gotoPreview() }
复制代码

Kotlin 语言的设计者是怎么做的?是这样:

  • 用函数类型替代接口定义
  • 用 Lambda 表达式作为函数参数

与上面 View.java 的等价 Kotlin 代码如下:

//View.kt
var mOnClickListener: ((View) -> Unit)? = null
var mOnContextClickListener: ((View) -> Unit)? = null

fun setOnClickListener(l: (View) -> Unit) {
    mOnClickListener = l;
}

fun setOnContextClickListener(l: (View) -> Unit) {
    mOnContextClickListener = l;
}
复制代码

以上做法有以下的好处:

  • 定义方:减少了两个接口类的定义
  • 调用方:代码更加简明

细心的小伙伴可能已经发现了一个问题:Android 并没有提供 View.java 的 Kotlin 实现,为什么我们 Demo 里面可以用 Lambda 来简化事件监听?

// 在实际开发中,我们经常使用这种简化方式
setOnClickListener { gotoPreview() }
复制代码

原因是这样的:由于 OnClickListener 符合 SAM 转换的要求,因此编译器自动帮我们做了一层转换,让我们可以用 Lambda 表达式来简化我们的函数调用。

那么,SAM 又是个什么鬼?

2-2 SAM 转换(Single Abstract Method Conversions)

SAM(Single Abstract Method),顾名思义,就是:只有一个抽象方法的类或者接口,但在 Kotlin 和 Java8 里,SAM 代表着:只有一个抽象方法的接口。符合 SAM 要求的接口,编译器就能进行 SAM 转换:让我们可以用 Lambda 表达式来简写接口类的参数。

注:Java8 中的 SAM 有明确的名称叫做:函数式接口(FunctionalInterface)。

FunctionalInterface 的限制如下,缺一不可:

  • 必须是接口,抽象类不行
  • 该接口有且仅有一个抽象的方法,抽象方法个数必须是1,默认实现的方法可以有多个。

也就是说,对于 View.java 来说,它虽然是 Java 代码,但 Kotlin 编译器知道它的参数 OnClickListener 符合 SAM 转换的条件,所以会自动做以下转换:

转换前:

public void setOnClickListener(OnClickListener l)
复制代码

转换后:

fun setOnClickListener(l: (View) -> Unit)
// 实际上是这样:
fun setOnClickListener(l: ((View!) -> Unit)?)
复制代码

((View!) -> Unit)?代表,这个参数可能为空。

2-3 Lambda 表达式引发的8种写法

当 Lambda 表达式作为函数参数的时候,有些情形下是可以简写的,这时候可以让我们的代码看起来更简洁。然而,大部分初学者对此也比较头疼,同样的代码,能有 8 种不同的写法,确实也挺懵的。

要理解 Lambda 表达式的简写逻辑,其实很简单,那就是:多写

各位小伙伴可以跟着我接下来的流程来一起写一写:

2-3-1 第1种写法

这是原始代码,它的本质是用 object 关键字定义了一个匿名内部类

image.setOnClickListener(object: View.OnClickListener {
    override fun onClick(v: View?) {
        gotoPreview(v)
    }
})
复制代码
2-3-2 第2种写法

如果我们删掉 object 关键字,它就是 Lambda 表达式了,因此它里面 override 的方法也要跟着删掉:

image.setOnClickListener(View.OnClickListener { view: View? ->
    gotoPreview(view)
})
复制代码

上面的 View.OnClickListener 被称为: SAM Constructor—— SAM 构造器,它是编译器为我们生成的。Kotlin 允许我们通过这种方式来定义 Lambda 表达式。

思考题:

这时候,View.OnClickListener {} 在语义上是 Lambda 表达式,但在语法层面还是匿名内部类。这句话对不对?

2-3-3 第3种写法

由于 Kotlin 的 Lambda 表达式是不需要 SAM Constructor的,所以它也可以被删掉。

image.setOnClickListener({ view: View? ->
    gotoPreview(view)
})
复制代码
2-3-4 第4种写法

由于 Kotlin 支持类型推导,所以 View? 可以被删掉:

image.setOnClickListener({ view ->
    gotoPreview(view)
})
复制代码
2-3-5 第5种写法

当 Kotlin Lambda 表达式只有一个参数的时候,它可以被写成 it

image.setOnClickListener({ it ->
    gotoPreview(it)
})
复制代码
2-3-6 第6种写法

Kotlin Lambda 的 it 是可以被省略的:

image.setOnClickListener({
    gotoPreview(it)
})
复制代码
2-3-7 第7种写法

当 Kotlin Lambda 作为函数的最后一个参数时,Lambda 可以被挪到外面:

image.setOnClickListener() {
    gotoPreview(it)
}
复制代码
2-3-8 第8种写法

当 Kotlin 只有一个 Lambda 作为函数参数时,() 可以被省略:

image.setOnClickListener {
    gotoPreview(it)
}
复制代码

按照这个流程,在 IDE 里多写几遍,你自然就会理解了。一定要写,看文章是记不住的。

2-4 函数类型,高阶函数,Lambda表达式三者之间的关系

  • 将函数的参数类型返回值类型抽象出来后,就得到了函数类型(View) -> Unit 就代表了参数类型是 View 返回值类型为 Unit 的函数类型。
  • 如果一个函数的参数或者返回值的类型是函数类型,那这个函数就是高阶函数。很明显,我们刚刚就写了一个高阶函数,只是它比较简单而已。
  • Lambda 就是函数的一种简写

一张图看懂:函数类型高阶函数Lambda表达式三者之间的关系:

[图片上传中…(image-35e85a-1605078254997-2)]

回过头再看官方文档提供的例子:

fun <T, R> Collection<T>.fold(
    initial: R, 
    combine: (acc: R, nextElement: T) -> R
): R {
    var accumulator: R = initial
    for (element: T in this) {
        accumulator = combine(accumulator, element)
    }
    return accumulator
}
复制代码

看看这个函数类型:(acc: R, nextElement: T) -> R,是不是瞬间就懂了呢?这个函数接收两个参数,第一个参数类型是R,第二个参数是T,函数的返回类型是R


3. 带接收者(Receiver)的函数类型:A.(B,C) -> D

说实话,这个名字也对初学者不太友好:带接收者的函数类型(Function Types With Receiver),这里面的每一个字(单词)我都认识,但单凭这么点信息,初学者真的很难理解它的本质。

还是绕不开一个问题:为什么?

3-1 为什么要引入:带接收者的函数类型?

我们在上一章节中提到过,用 apply 来简化逻辑,我们是这样写的:

修改前:

if (user != null) {
    ...
    username.text = user.name
    website.text = user.blog
    image.setOnClickListener { gotoImagePreviewActivity(user) }
}
复制代码

修改后:

user?.apply {
    ...
    username.text = name
    website.text = blog
    image.setOnClickListener { gotoImagePreviewActivity(this) }
}
复制代码

请问:这个 apply 方法应该怎么实现?

上面的写法其实是简化后的 Lambda 表达式,让我们来反推,看看它简化前是什么样的:

// apply 肯定是个函数,所以有 (),只是被省略了
user?.apply() {
    ...
}

// Lambda 肯定是在 () 里面
user?.apply({ ... })

// 由于 gotoImagePreviewActivity(this) 里的 this 代表了 user
// 所以 user 应该是 apply 函数的一个参数,而且参数名为:this
user?.apply({ this: User -> ... })
复制代码

所以,现在问题非常明确了,apply 其实接收一个 Lambda 表达式:{ this: User -> ... }。让我们尝试来实现这个 apply 方法:

fun User.apply(block: (self: User) -> Unit): User{
    block(self)
    return this
}

user?.apply { self: User ->
    ...
    username.text = self.name
    website.text = self.blog
    image.setOnClickListener { gotoImagePreviewActivity(this) }
}
复制代码

由于 Kotlin 里面的函数形参是不允许被命名为 this 的,因此我这里用的 self,我们自己写出来的 apply 仍然还要通过 self.name 这样的方式来访问成员变量,但 Kotlin 的语言设计者能做到这样:

//                   改为 this
//                      ↓ 
fun User.apply(block: (this: User) -> Unit): User{
//    这里还要传参数
//         ↓ 
    block(this)
    return this
}

user?.apply { this: User ->
    ...
//               this 可以省略
//                   ↓ 
    username.text = this.name
    website.text = blog
    image.setOnClickListener { gotoImagePreviewActivity(this) }
}
复制代码

从上面的例子能看到,我们反推的 apply 实现比较繁琐,需要我们自己调用:block(this),因此 Kotlin 引入了带接收者的函数类型,可以简化 apply 的定义:

//              带接收者的函数类型
//                     ↓  
fun User.apply(block: User.() -> Unit): User{
//  不用再传this
//       ↓ 
    block()
    return this
}

user?.apply { this: User ->
    ...
    username.text = this.name
    website.text = this.blog
    image.setOnClickListener { gotoImagePreviewActivity(this) }
}
复制代码

现在,关键来了。上面的 apply 方法是不是看起来就像是在 User 里增加了一个成员方法 apply()?

class User() {
    val name: String = ""
    val blog: String = ""

    fun apply() {
        // 成员方法可以通过 this 访问成员变量
        username.text = this.name
        website.text = this.blog
        image.setOnClickListener { gotoImagePreviewActivity(this) }
    }
}
复制代码

所以,从外表上看,带接收者的函数类型,就等价于成员方法。但从本质上讲,它仍是通过编译器注入 this 来实现的。

一张图总结:

[图片上传中…(image-b7c5e-1605078254996-1)]

思考题2:

带接收者的函数类型,是否也能代表扩展函数?

思考题3:

请问:A.(B,C) -> D 代表了一个什么样的函数?

4. HTML Kotlin DSL 实战

官方文档在高阶函数的章节里提到了:用高阶函数来实现 类型安全的 HTML 构建器。官方文档的例子比较复杂,让我们来写一个简化版的练练手吧。

4-1 效果展示:

val htmlContent = html {
    head {
        title { "Kotlin Jetpack In Action" }
    }
    body {
        h1 { "Kotlin Jetpack In Action"}
        p { "-----------------------------------------" }
        p { "A super-simple project demonstrating how to use Kotlin and Jetpack step by step." }
        p { "-----------------------------------------" }
        p { "I made this project as simple as possible," +
                " so that we can focus on how to use Kotlin and Jetpack" +
                " rather than understanding business logic." }
        p {"We will rewrite it from \"Java + MVC\" to" +
                " \"Kotlin + Coroutines + Jetpack + Clean MVVM\"," +
                " line by line, commit by commit."}
        p { "-----------------------------------------" }
        p { "ScreenShot:" }
        img(src = "https://user-gold-cdn.xitu.io/2020/6/15/172b55ce7bf25419?imageslim",
         alt = "Kotlin Jetpack In Action")
    }
}.toString()

println(htmlContent)
复制代码

以上代码输出的内容是这样的:

<html>
  <head>
    <title>
      Kotlin Jetpack In Action
    </title>
  </head>
  <body>
    <h1>
      Kotlin Jetpack In Action
    </h1>
    <p>
      -----------------------------------------
    </p>
    <p>
      A super-simple project demonstrating how to use Kotlin and Jetpack step by step.
    </p>
    <p>
      -----------------------------------------
    </p>
    <p>
      I made this project as simple as possible, so that we can focus on how to use Kotlin and Jetpack rather than understanding business logic.
    </p>
    <p>
      We will rewrite it from "Java + MVC" to "Kotlin + Coroutines + Jetpack + Clean MVVM", line by line, commit by commit.
    </p>
    <p>
      -----------------------------------------
    </p>
    <p>
      ScreenShot:
    </p>
    <img src="https://user-gold-cdn.xitu.io/2020/6/15/172b55ce7bf25419?imageslim" alt="Kotlin Jetpack In Action" /img>
  </body>
</html>
复制代码

4-2 HTML Kotlin DSL 实现

4-2-1 定义节点元素的接口
interface Element {
    // 每个节点都需要实现 render 方法
    fun render(builder: StringBuilder, indent: String): String
}
复制代码

所有的 HTML 节点都要实现 Element 接口,并且在 render 方法里实现 HTML 代码的拼接:<title> Kotlin Jetpack In Action </title>

4-2-2 定义基础类
/**
 * 每个节点都有 name,content: <title> Kotlin Jetpack In Action </title>
 */
open class BaseElement(val name: String, val content: String = "") : Element {
    // 每个节点,都会有很多子节点
    val children = ArrayList<Element>()
    // 存放节点参数:<img src= "" alt=""/>,里面的 src,alt
    val hashMap = HashMap<String, String>()

    /**
     * 拼接 Html: <title> Kotlin Jetpack In Action </title>
     */
    override fun render(builder: StringBuilder, indent: String): String {
        builder.append("$indent<$name>\n")
        if (content.isNotBlank()) {
            builder.append("  $indent$content\n")
        }
        children.forEach {
            it.render(builder, "$indent  ")
        }
        builder.append("$indent</$name>\n")
        return builder.toString()
    }
}
复制代码
4-2-3 定义各个子节点:
// 这是 HTML 最外层的标签: <html>
class HTML : BaseElement("html") {
    fun head(block: Head.() -> Unit): Head {
        val head = Head()
        head.block()
        this.children += head
        return head
    }

    fun body(block: Body.() -> Unit): Body {
        val body = Body()
        body.block()
        this.children += body
        return body
    }
}

// 接着是 <head> 标签
class Head : BaseElement("head") {
    fun title(block: () -> String): Title {
        val content = block()
        val title = Title(content)
        this.children += title
        return title
    }
}

// 这是 Head 里面的 title 标签 <title>
class Title(content: String) : BaseElement("title", content)

// 然后是 <body> 标签
class Body : BaseElement("body") {
    fun h1(block: () -> String): H1 {
        val content = block()
        val h1 = H1(content)
        this.children += h1
        return h1
    }

    fun p(block: () -> String): P {
        val content = block()
        val p = P(content)
        this.children += p
        return p
    }

    fun img(src: String, alt: String): IMG {
        val img = IMG().apply {
            this.src = src
            this.alt = alt
        }

        this.children += img
        return img
    }
}

// 剩下的都是 body 里面的标签
class P(content: String) : BaseElement("p", content)
class H1(content: String) : BaseElement("h1", content)

class IMG : BaseElement("img") {
    var src: String
        get() = hashMap["src"]!!
        set(value) {
            hashMap["src"] = value
        }

    var alt: String
        get() = hashMap["alt"]!!
        set(value) {
            hashMap["alt"] = value
        }

    // 拼接 <img> 标签
    override fun render(builder: StringBuilder, indent: String): String {
        builder.append("$indent<$name")
        builder.append(renderAttributes())
        builder.append(" /$name>\n")
        return builder.toString()
    }

    private fun renderAttributes(): String {
        val builder = StringBuilder()
        for ((attr, value) in hashMap) {
            builder.append(" $attr=\"$value\"")
        }
        return builder.toString()
    }
}

复制代码
4-2-4 定义输出 HTML 代码的方法
fun html(block: HTML.() -> Unit): HTML {
    val html = HTML()
    html.block()
    return html
}
复制代码

4-3 HTML 代码展示

class WebActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_web)

        val myWebView: WebView = findViewById(R.id.webview)
        myWebView.loadDataWithBaseURL(null, getHtmlStr(), "text/html", "UTF-8", null);
    }

    private fun getHtmlStr(): String {
        return html {
            head {
                title { "Kotlin Jetpack In Action" }
            }
            body {
                h1 { "Kotlin Jetpack In Action"}
                p { "-----------------------------------------" }
                p { "A super-simple project demonstrating how to use Kotlin and Jetpack step by step." }
                p { "-----------------------------------------" }
                p { "I made this project as simple as possible," +
                        " so that we can focus on how to use Kotlin and Jetpack" +
                        " rather than understanding business logic." }
                p {"We will rewrite it from \"Java + MVC\" to" +
                        " \"Kotlin + Coroutines + Jetpack + Clean MVVM\"," +
                        " line by line, commit by commit."}
                p { "-----------------------------------------" }
                p { "ScreenShot:" }
                img(src = "https://user-gold-cdn.xitu.io/2020/6/15/172b55ce7bf25419?imageslim", alt = "Kotlin Jetpack In Action")
            }
        }.toString()
    }
}
复制代码

4-4 展示效果:

以上修改的具体细节可以看我这个 GitHub Commit

4-5 HTML DSL 的优势

  • 类型安全
  • 支持自动补全
  • 支持错误提示
  • 节省代码量,提高开发效率

4-6 小结

  • 以上 DSL 代码单纯的看是很难看懂的,一定要下载下来一步步调试
  • 这个案例里充斥着高阶函数的运用,对理解高阶函数很有帮助
  • 这个案例并不完整,还有很多 HTML 特性没有实现,感兴趣的小伙伴可以完善试试
  • 在这个案例里,我尽量在克制其他 Kotlin 特性的使用,比如泛型,如果用上泛型,代码会更简洁,感兴趣的小伙伴也可以试试,下一章我们也会讲泛型,到时候再来优化

5 总结

  • 本文并未覆盖高阶函数所有内容,但最关键,最难懂的部分已经讲解清楚了。小伙伴们看完本文后再去看官方文档会轻松不少
  • 再啰嗦一遍:看再多的教程,都不如亲自写几行代码

都看到这了,点个赞呗!

Android学习是一条漫长的道路,我们要学习的东西不仅仅只有表面的 技术,还要深入底层,弄明白下面的 原理,只有这样,我们才能够提高自己的竞争力,在当今这个竞争激烈的世界里立足。

千里之行始于足下,愿你我共勉。

我把自己这段时间整理的Android最重要最热门的学习方向资料放在了我的GitHub:https://github.com/xieyuliang/Android,里面还有不同方向的自学编程路线、面试题集合/面经、及系列技术文章等。

资源持续更新中,欢迎大家一起学习和探讨。

版权声明:本文为m0_46962786原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_46962786/article/details/109623083

智能推荐

CodeForce Tic-Tac-Toe

Two bears are playing tic-tac-toe via mail. It's boring for them to play usual tic-tac-toe game, so they are a playing modified version of this game. Here are its rules. The game is played on the foll...

Python雾里看花-抽象类ABC (abstract base class)

首先认识模块 abc,python中没有提供抽象类与抽象方法,然而提供了内置模块abc来模拟实现抽象类,例如提供泛映射类型的抽象类 abc.MutableMapping 继承abc.MutableMapping构造一个泛映射类型(类似python中的dict) 当然继承abc.Mapping 也可以,毕竟MutableMapping是其子类 dict是python中典型的映射类型数据结构,其接口的...

python 文件操作

2, with open (‘xx.txt’,‘w’,encoding=‘utf-8’) as f: f.write(‘文件内容或对象’)...

【Python基础】使用统计函数绘制简单图形

机器学习算法与自然语言处理出品 @公众号原创专栏作者 冯夏冲 学校 | 哈工大SCIR实验室在读博士生 2.1 函数bar 用于绘制柱状图 2.2 函数barh 用于绘制条形图 2.3 函数hist 用于绘制直方图 直方图与柱状图的区别 函数pie 用于绘制饼图 2.5 函数polor 用于绘制极线图 极线图是在极坐标系上绘出的一种图。在极坐标系中,要确定一个点,需要指明这个点距原点的角...

css:顶部按钮固定,上面内容滑动

这种需求我们平时见到很多的,实现方法也多的参差不齐,下面我说一种简单的。如图: 可以看到只有红线部分滚动,底下按钮是固定的。 代码...

猜你喜欢

环形公路堵车概率模型(含详细解析)

文章目录 基础理论 代码实现 图形分析 基础理论 路面上有n辆车,以不同的速度向前行驶, 模拟堵车问题。 有以下假设: 假设某辆车的当前速度是v。 若前方可见范围内没车,则它在下一秒的车速提高到v+1,直到达到规定的最高限速。 若前方有车,前车的距离为d,且d < v,则它下 一秒的车速降低到d-1 。 每辆车会以概率p随机减速v-1。、 代码实现 图形分析 图形中颜色越重的地方,说明很多车...

JavaScript事件处理的例题

知道的越多,所不知道的越多。如果带给你帮助,点赞支持一下。 JavaScript事件处理的例题 1、表单验证 2、验证数字输入 3、利用document对象的bgColor属性改变背景色,添加鼠标悬停事件 4.附加题(选做) 1、表单验证 要求:用户名不少于2位,并且用户名第一个字符需为字母! 密码长度必须在6~15之间。 2、验证数字输入 如果输入的值 x 不是数字或者小于 1 或者大于 10,...

arduino操作光照传感器BH1750(数字型,I2C接口)

BH1750传感器,用于检测环境光光照强度。 BH1750FVI是日本罗姆(ROHM)半导体生产的数字式环境光传感IC。其主要特性有: I2C数字接口,支持速率最大400Kbps 输出量为光照度(Illuminance) 测量范围1~65535 lux,分辨率最小到1lux 低功耗(Power down)功能 屏蔽50/60Hz市电频率引起的光照变化干扰 支持两个I2C地址,通过ADDR引脚选择 ...

10.31 数组算法学习-冒泡、选择、二分法

  冒泡排序:   冒泡排序 https://blog.csdn.net/kelinfeng16/article/details/84034386 原文地址:https://github.com/hustcc/JS-Sorting-Algorithm/blob/master/2.selectionSort.md 冒泡排序(Bubble Sort)也是一种简单直观的排序算法。它重...

排序算法5--交换排序2--快速排序(C++完整代码实现)

以上图片来自天勤数据结构,以下代码***非天勤数据结构提供的代码;*** 平均时间复杂度O(nlog2n)...