Functions

函数声明

kotlin中的函数声明使用fun关键字

fun double(x: Int): Int{
    return 2 * x
}

函数用法

通过传统途径调用方法:

var result = double(2)

参数

函数的参数通过Pascal符号定义,如name:type, 参数之间通过逗号隔开,每个参数必须指定的明确类型:

fun powerOf(number: Int, exponent: Int){ ... }

默认参数

函数的参数可以有默认值,这种情况发生在一个函数的参数在被调用是,本应传递的参数被省略掉。相比于其他的语言,这可以减少重载的数量。

fun read(b: Array<Byte>, off: Int = b.size){ ... }

默认值是通过在其声明的参数后面加上‘=’来定义的。

重写方法总是使用与基本方法相同的默认参数值。当覆盖带有默认参数值的方法时,必须从签名中省略默认的参数值:

open class A{
    open fun foo(i: Int = 10){ ... }
}

class B: A(){
    override fun foo(i: Int){ ... }
}

如果缺省参数先于没有缺省值的参数,则只能通过调用带有命名参数的函数来使用缺省值:

fun foo(bar: Int = 0, baz: Int){ ... }

foo(baz = 1)    //缺省参数bar = 0已经被使用

但是,如果最后一个参数lambda被传递给括号外的函数调用,则允许传递缺省参数的值:

fun foo(bar: Int = 0, baz: Int = 1, qux: ()-> Unit){ ... }
foo(1) { println("hello") } //使用缺省参数 baz = 1
foo() { println("hello") }  //使用两个缺省参数 bar = 0 和 baz = 1

命名参数

函数参数可以在被调用时命名,当一个参数有大量的参数或者是默认值的时候,这是非常方便的。给出下面的函数:

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

我们可以通过使用参数默认值调用函数:

reformat(str)

然而,当此函数没法使用默认值来进行调用的时候,它看起来会像下面这样:

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

为了使代码更具有可读性,我们使用命名参数如下:

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

并且,当我们不需要所有的参数时:

reformat(str, wordSeparator = '_')

当在调用一个函数时同时使用位置和命名参数的时候,所有的位置参数必须先于第一个命名参数,例如,调用f(1, y=2)是允许的,但是f(x=1, 2)是不允许的。 

可变数量的参数(vararg)可以通过使用扩展操作符在指定的表单中传递:

fun foo(vararg strings: String){ ... }
foo(strings = *arrayOf("a", "b", "c"))

注意在调用java函数的时候,命名参数语法是无法使用的,因为java字节码不会在意保护方法参数的名字。

空返回函数

如果一个函数没有返回有用的值,它的返回类型用Unit表示,Unit是一种只有一个值的类型-Unit,这个值不用显式返回。

fun printHello(name: String?):Unit{
    if(name != null){
        println("Hello ${name}")
    }else{
        println("Hi there!")
    }
    //return Unit 或者 return 是可选的
}

Unit 返回类型的声明是可选的,上面的代码等同于:

fun printHello(name: String){ ... }

单一表达式函数

当一个函数只返回一个表达式,花括号可以省略并且用一个“=”替换:

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

当返回类型可以编译器推测出来的时候,返回类型也可以直接省略:

dun double(x: Int) = x * 2

明确的返回类型

块函数通常都必须指定返回类型,除非打算返回空(Unit, 这种情况是可选的),因为块函数本身的复杂的控制流程,kotlin没有为块函数做返回类型推测的处理,并且有时候返回类型对读者并非显而易见的(甚至于有时对编译器也是如此)。

变长参数

函数的参数被标记符 vararg 修饰:

fun<T> asList(vararg ts: T):List<T>{
    val result = ArrayList<T>()
    for(t in ts){   //ts 是一个数组
        result.add(t)
    }
    return result
}

允许变长参数传递到函数中:

val list = asList(1,2,3)

在一个函数中,一个T类型的变长参数可以看成是T类型的一个数组,比如上面例子中的ts变量是类型Array<out T>数组

 

只有一个参数可以被标记为vararg, 如果被标记为vararg的参数不在参数列表中的最后一个,那么余下的参数可以通过命名参数语法传递,或者如果参数是一个函数类型,通过在括号外面传递一个lambda。 
当我们调用一个变长参数函数,我们可以一个一个的传递参数,比如asList(1, 2, 3),或者加入我们已经有一个数组并且想把它的内容传递到函数中,我们可以使用扩展操作符(给数组加一个前缀‘*’):

val a= arrayOf(1,2,3)
val list = asList(-1, 0, *a, 4, 5) 

中缀表示法

当函数被关键字infix修饰时,此函数可以使用中缀表示法调用(省略点号和圆括号),中缀函数必须满足下面条件:

  • 它们必须是成员函数或者是扩展函数
  • 它们必须只有一个参数
  • 参数不允许是变长参数,并且不能有默认值
infix fun Int.shl(x: Int): Int{ ... }
//通过中缀表示法调用函数
1 shl 2

//等同于
1.shl(2)

中缀函数调用优先级低于算数运算符,类型转换和 **rangeTo** 操作符, 下面的表达式是等价的:

  • -- 1 shl 2 + 3 和 1 shl (2 + 3)
  • -- 0 until n * 2 和 0 nutil (n * 2)
  • -- xs union ys as Set<*> 和 xs union (ys as Set<*>)
另一方面,中缀函数的调用优先级要高于布尔操作符&&和||, is-and, in-cheks和其他的操作符,下面的表达式是等价的:
  • -- a && b xor c 和 a && (b xor c)
  • -- a xor b in c 和 (a xor b) in c

注意中缀函数通常需要调用者和参数都是明确指定的,当你在当前的主体内使用中缀方法调用函数的时,你必须使用this指明;和普通的调用方法不一样,this不能省略,这样要求是为了确保明确的解析。

class MyStringCollection{
    infix fun add(s: String){ ...  }
    
    fun build(){
        this add "acc"  //正确
        add("acc")      //正确
        add "acc"       //错误: 必须明确指出调用者
    }
}

函数作用域

在Kotlin中,函数能够被生命在文件顶层,意味着你不用像java,C#或者Scala语言创建一个类去持有一个函数。除了顶级函数外,Kotlin函数也同样能是本地函数或者作为扩展函数。

本地函数

Kotlin支持本地函数,即一个函数里面中的一个函数:

fun dfs(graph: Graph){
    fun dfs(current: Vertex, visited: Set<Vertex>){
        if(!visited.add(current) return
        for(v in current.neighbors){
            dfs(v, visited)
        }
    }
    
    dfs(graph.vertices[0], HashSet())
}

局部函数能够访问函数外的变量,因此上面例子中,visited可以设置为一个局部变量

fun dfs(graph: Graph){
    val visited = HashSet<Vertex>()
    
    fun dfs(current: Vertex){
        if(!visited.add(current) return
        for(v in current.neighbors){
            dfs(v, visited)
        }
    }
    
    dfs(graph.vertices[0])
}

成员函数

一个成员函数是定义在一个类或者对象中的函数:

class Sample(){
    fun foo() { prin("foo") }
}

成员函数是通过点记法进行调用的。

Sample().foo()  //创建Sample实例,并且调用方法foo

重载函数

在函数参数名称钱有尖括号标明的重载符号

fun <T> singletonList(item : T): List<T> { ... }

内联函数

扩展函数

高阶函数和Lambda

尾递归函数

Kotlin支持一直成为尾递归的函数编程类型,它允许一些算法使用循环来编写,而不是用递归,不会有堆栈溢出的风险。当一个函数被关键字tailrec所修饰并且符合要求的形式,编译器会使用快速高效的循环来优化递归:

tailrec fun findFixPoint(x: Double = 1.0): Double = if(x == Math.cos(x)) x else findFixPoint(Math.cos(x))

上面代码计算数学常量cos的fixpoint,它是简单的从1.0开始重复的调用Math.cos,知道结果不会再改变,结果是:0.7390851332151607,上面的代码等价于:

private fun findFixPoint(): Double{
    var x = 1.0
    while(true){
        val y = Math.cos(x)
        if(x == y) return x
        x = y
    }
}

为了能够使用tailrec进行修饰,函数规定必须在最后执行调用函数本身的操作,如果在递归调用函数本身之后,还有其他的操作,是不允许使用tailrec进行修饰的,并且不能在tra/catch/finally块中调用进行递归操作,当前的尾递归只支持JVM。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值