Kotlin语法基础,函数与闭包

Posted 北漂周

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin语法基础,函数与闭包相关的知识,希望对你有一定的参考价值。

函数

为了是程序简洁明了,更具有逻辑性,我们通常的做法就是把相似的功能模块整合到一起,并设计成函数。函数是执行特定任务模块的代码,每个函数都有一个类型,你可以像使用Kotlin语言中其他类型一样使用函数类型,将函数作为参数传递给其他函数,或者将函数类型当做返回类型。你可以通过给定一个函数名称来标识它是什么,并在需要的时候使用该名称来调用函数以执行任务。

在Kotlin语言中,函数可以分为两类:一种是库和框架中的函数,要调用库函数或者使用框架中的函数,需要在单元顶部引用库或者框架的接口单元;另一种则是自定义函数,这类函数通常都是为了解决某一特别的问题而编写的,编写好函数之后,我们可以在程序的任意位置引用并调用它。学会编写和使用函数会使你的代码复杂度、可读性、可维护性都提高。

函数的定义和调用

要使用函数,必须先定义后调用。每一个函数都有一个函数名,用来表述函数执行的任务。要使用函数是,可以使用它的名称进行“调用”,并通过函数名来传递参数。函数形参是指函数定义时设定的参数名,函数实参是指调用函数时实际传递的参数,一个函数的实参必须始终和函数的形参顺序一致。函数定义需要使用关键字fun,其一般格式为:

fun <函数名>(<参数名1> : <参数1类型>, <参数名2> : <参数2类型>...) : <函数返回类型> 
    函数体...
    return <返回值>

例如下面的例子,我们定义了一个函数getPercent,从函数的名字我们可以看得出来,它的功能是获取一个数字的百分比显示内容。即,我给它一个浮点数number,它返回给我一个百分比显示的字符串,0.98 ——> 98%。我们需要一个参数来支出这个数字具体是什么,即设计形参number。参数通过函数名传进函数体李,通过函数处理之后需要一个返回处理结果,以表示百分显示的效果。所以有了一个返回值,这里是String类型。函数定义如下:

/**
 * 获取百分比显示的数字
 */
fun getPercent(number: Double): String 
    val percentNumber = number * 100
    return "$percentNumber %"

我们可以直接通过使用函数名来调用这个函数,并且在调用的时候把参数传进去:

println(getPercent(0.98))

函数getPercent将数字0.98传入到函数体内部,函数体内部的程序语句对数字进行处理判断,然后将处理后的的显示效果赋值给percentNumber变量,在通过return关键字将percentNumber的值返回给函数主体。

函数的形参

在Kotlin语言中,函数的形参和返回值是非常具有灵活性的,在需要的时候,你可以定义一个或者多个甚至选择性的省略。

在定义的时候可以忽略返回值,但一个定义了返回值的函数必须在函数体中设定返回值。对于一个定义了返回类型的函数来说,如果没有返回值,相当于没有指定函数的出口,这种情况下,编译器会报错的。事实上,在定义的时候忽略返回值等于是隐式声明函数的返回值是空。

如之前的查找“X”字符的例子:

/**
 * 查找x字符串
 */
fun findX(xArray : Array<String>) 
    for (word in xArray) 
        if (word == "x") 
            println("find the 'x' word!")
            return // 中断整个循环执行,退出函数
        
    
    println("Can not find 'x' !")

我们在查找到“X”之后,return一个空值,这里的return并没有给函数返回任何值。它的作用仅仅是告诉这个函数,在这个情况下return函数就该结束了。

参数默认值

当然,对于任何形参的定义来说,我们可以给其设定一个默认值。如果已经定义了默认值,那么调用函数时就可以省略该形参。和其他高级语言一样,为了避免遗漏参数或者参数传递二义性,请在函数形参列表的末尾放置带默认值的形参,不要在默认值的形参前放置。

/**
 * 给一个字符串拼接前缀和后缀
 * 前缀默认值:***
 * 后缀默认值:###
 */
fun catString(myString: String, prefix: String = "***", suffix: String = "###"): String
    return prefix + myString + suffix


// 使用的时候,可以忽略带有默认值的参数不传
catString("hello")

在有定义默认值得情况下,当你没有指定外部名称时,Kotlin语言将为你定义的任何默认形参提供一个自动外部名,这个自动外部名和本地名类似。

可变个数参数(vararg)

可变个数形参是指可接受零个或者多个指定类型值得形参,你可以用它来传递任意数量的输入参数。声明可变个数形参需要用到vararg关键字,当参数传递进入函数体之后,参数在函数体内可以通过集合的形式访问。还有一点需要注意的是,函数最多可以有一个可变个数的形参,而且它必须出现在参数列表的最后。如:

/**
 * 求多个数字的和
 */
fun sumNumbers(vararg numbers : Double) : Double
    var result : Double = 0.0
    for (number in numbers) 
        result += number
    
    return result


// 使用的时候,则可以传任意多个参数
sumNumbers(1.2,2.56,3.14)

命名参数

如果别人第一次阅读你的代码,可能会不知道你使用的这个函数的形参的目的,那么使用外部形参名称就可以是你要表达的亿图更加的明确,上下文更加清晰。当然,如果每个形参名 的目的已经足够的简单清晰明确的话,那就无需在制定外部形参名。命名参数的方式,是在调用函数的时候带上这个形参的名称,并做赋值语句传入。

如,我们定义了一个格式化字符串的函数

fun reformat(str: String,
             normalizeCase: Boolean = true,
             upperCaseFirstLetter: Boolean = true,
             divideByCamelHumps: Boolean = false,
             wordSeparator: Char = ' ') 
    //...

然而,当使用非默认参数调用它时,该调用看起来就像

reformat(str, true, true, false, '_')

阅读起来相当的费劲,而且对于多个设置有默认值类型相同形参的函数,在调用使用上还很容易将参数传错。如果我们使用外部形参命名的方式,我们就可以写为:

reformat(str,
    normalizeCase = true,
    upperCaseFirstLetter = true,
    divideByCamelHumps = false,
    wordSeparator = '_'
)

使用上直观,阅读上清晰易懂,避免了传错参数导致的低级Bug。

返回 Unit 的函数

在函数中,所有定义了返回值的函数,我们都称之为显示函数。没有返回类型的函数,我们称之为隐式函数。

上文中我们又向大家介绍,在定义的时候忽略返回值等于是隐式声明函数的返回值是空。在Kotlin中,这种隐式返回的类型称之为:Unit。这个Unit类型的作用类似Java语言中的void类型。Unit是一种只有一个值——Unit的类型。这个值不需要显式返回。

即定义函数printHello具有Unit返回类型:

fun printHello(name: String): Unit 
   // ...

和不写Unit返回类型的作用是一样的。

fun printHello(name: String) 
   // ...

当然,在返回值return上,写不写Unit效果都一样。

fun printHello(name: String): Unit 
    return Unit// 和不写return
            // 和return Unit作用一样

单表达式函数

单表达式函数,即只有一个表单式的函数。当函数返回单个表达式时,可以省略花括号并且在 = 符号之后指定代码体即可

fun doubleValue(x: Int): Int = x * 2

当返回值类型可由编译器推断时,显式声明返回类型是可选的

fun doubleValue(x: Int) = x * 2

嵌套函数

在结构化编程盛行的年代,嵌套函数被广泛使用,在一个函数体中定义另外一个函数体就为嵌套函数。嵌套函数默认对外界是隐藏的,但仍然可以通过它们包裹的函数调用和使用它,举个例子:

/**
 * 嵌套函数demo
 *
 * 比较数字numberA和数字numberB的二次幂谁大
 */
fun compare(numberA: Int, numberB: Int) : Int
    var powerB = 0

    // 嵌套函数,求一个数字的二次幂
    fun power(num : Int) : Int
        return num * num
    
    powerB = power(numberB)

    if (numberA > powerB) 
        return numberA
     else 
        return powerB
    



fun main(args: Array<String>) 
    // 报错!!! 
    // 无法直接调用内部嵌套的函数
    power()

闭包

闭包是Kotlin语言的众多特性之一,对多数习惯使用了Java语言的开发者来说是一个很难理解的东西(实际上Java8也开始支持闭包特性),Kotlin中的闭包是一个功能性自包含模块,可以再代码中被当做参数传递或者直接使用。这个描述可能不太直观,你可能还是想问:“那么到底什么是闭包呢?闭包在函数中是以什么形式出现和使用的呢?”下面向大家介绍。

什么是闭包

一段程序代码通常由常量、变量和表达式组成,然后使用一对花括号“”来表示闭合,并包裹着这些代码,由这对花括号包裹着的代码块就是一个闭包。其实在签名我们也介绍了全局和嵌套函数就是一种特殊的闭包。这里,我们总结了一下,Kotlin语言中有三种闭包形式:全局函数、自嵌套函数、匿名函数体。

听着名词解释是挺让人费解,下面我们举个例子:

fun main(args: Array<String>) 
    // 执行test闭包的内容
    test


// 定义一个比较测试闭包
val test = if (5 > 3) 
    println("yes")
 else 
    println("no")

先不说闭包的结构,从代码层面来看,上述逻辑我们都知道这段代码永远都只会输出“yes”。那么你可能会问:
- 为什么能够将一个if逻辑语句赋值给test呢?
- 为什么在main函数中单独写一个test就能执行test所指向的if逻辑呢?
- 如果一个if逻辑快块是一个闭包?那么还有什么逻辑块可以是闭包呢?

下面,我们会一一给你解答。

为什么会设计闭包这种结构?

从上述的例子来说,我们可以看出来,其实定义一个函数就好了,为什么设计编程语言的人要设计闭包这么一个结构呢?这就得从作用域开始说起。变量的作用域无非就是两种:全局变量局部变量

  • 全局变量

就Kotlin语言而言,函数内部可以直接读取全局变量。

var n = 999;
fun f1() 
    println(n) // 打印999

f1()
  • 局部变量

另一方面,在函数外部自然无法读取函数内的局部变量。

fun f1()
    var n=999

println(n) // 报错!!! n是函数内局部变量,外部无法调用

那么,如何在外部调取局部的变量呢?答案就是——闭包

这里,我们给闭包下一个定义:闭包就是能够读取其他函数内部变量的函数。

闭包的用途在哪里?

闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。什么意思,没有听懂?下面,我们具体跑个例子试试,如:

/**
 * 计数统计
 */
fun justCount():() -> Unit
    var count = 0
    return 
        println(count++)
    



fun main(args: Array<String>) 

    val count = justCount()
    count()  // 输出结果:0
    count()  // 输出结果:1
    count()  // 输出结果:2

有没有发现闭包这点的好处,闭包就是在函数被创建的时候,存在的一个私有作用域,并且能够访问所有的父级作用域。每个功能模块我们都能够拆解到不同fun里,不同fun里的变量保持相互调用的可能性,相互独立还彼此不影响。我们可以函数式编程了!

广义上来说,在Kotlin语言之中,函数、条件语句、控制流语句、花括号逻辑块、Lambda表达式都可以称之为闭包,但通常情况下,我们所指的闭包都是在说Lambda表达式。

自执行闭包

自执行闭包就是在定义闭包的同时直接执行闭包,一般用于初始化上下文环境。 例如:

 x: Int, y: Int ->
    println("$x + y")
(1, 3)

以上是关于Kotlin语法基础,函数与闭包的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin——基础的函数/方法详解

kotlin学习笔记之闭包

kotlin学习笔记之闭包

python基础——函数对象和闭包

闭包的构成条件与语法格式

kotlin 实战之函数与 lambda 表达式总结