玩转Kotlin之程序结构

目录

前言

一、常量与变量

二、函数

三、Lambda表达式

四、类的成员

五、基本运算符

六、表达式

七、循环语句

八、异常捕获

九、具名参数、变长参数和默认参数

十、综合案例——命令行计算器


前言

磨磨唧唧的又更了一篇,不容易啊!不能怪我,要怪就怪庆余年太好看了,导致我边看剧边写作产出略低啊!今天继续Kotlin语言的学习,上一篇介绍的基本数据类型属于词法,这里接着上一篇的内容来说说Kotlin的语法,也就是程序结构。

玩转Kotlin之程序结构_第1张图片

一、常量与变量

1、什么是常量?

val=value,值类型,类似于Java的final,不可能重复赋值,举个栗子,运行时常量:val x = getX(),编译期常量:const val x = 2

运行时常量在编译器编译的时候并不能确切的知道它的值到底是什么,对于编译期常量,编译器在编译的时候就已经知道它的值了,并且把代码中其他地方对它的引用都替换成它的值,这样可以提高代码的执行效率,注意编译期常量需要加const关键字修饰。

2、什么是变量?

变量是用var关键字进行修饰的,var = variable,举个栗子:var x = "HelloKotlin" //定义变量             x = "HiKotlin" //再次赋值

3、类型推导

val string = "Kotlin" //推导出String类型        val int = 5 //Int类型       var x = getString()+5 //String类型

简单写点代码看一下吧,不写纯讲理论如果不熟悉的话看着还是挺耍流氓的:

val FINAL_HELLO_KOTLIN: String = "Hello Kotlin" //定义常量
val FINAL_HELLO_JAVA = "Hello Java" //类型推导,可以不用具体指定类型
var hello = "Hello Kotlin" //定义变量

fun main(args: Array) {
    //FINAL_HELLO_KOTLIN = "a" 编译器会报错 val实际上就是value
    println(FINAL_HELLO_KOTLIN)
    println(FINAL_HELLO_JAVA)
    hello = "Hello Python" //再次给变量赋值
    println(hello)
}

来看结果,很明显了:

玩转Kotlin之程序结构_第2张图片

二、函数

1、什么是函数?

以特定功能组织起来的代码块

  • fun[函数名]([参数列表]):[返回值类型]{[函数体]}
  • fun[函数名]([参数列表])=[表达式]

举个栗子:

  • fun sayHello(name:String){printlin("Hello,$name)}
  • fun sayHello(name:String)=printlin("Hello,$name")

2、什么是匿名函数

无名氏相当牛逼,没有名字的函数,但是要把它赋值给一个变量,否则会报错

  • fun([参数列表]):[返回值类型]{[函数体]}
  • fun([参数列表])=[表达式]

举个栗子:

val sayHello = fun(name:String)=printlin("Hello,$name")

3、编写函数的注意事项

1、功能要单一 2、函数名要做到顾名思义 3、参数个数不要太多

来写段代码看一下:

val arg1 = 1
val arg2 = 9
fun main(args: Array): Unit {
    println("$arg1 + $arg2 = ${sum(arg1, arg2)}")
    println("$arg1 + $arg2 = ${sum1(arg1, arg2)}")
    println(sum2(3,4)) //匿名函数的调用
    println(sum3(7,8))
}

fun sum(arg1: Int, arg2: Int): Int {
    return arg1 + arg2
}

fun sum1(arg1: Int, arg2: Int) = arg1 + arg2 //等同于函数sum,如果函数体中只是返回一个表达式的值,函数体可省略,直接将表达式赋值给函数

val sum2 = fun(arg1: Int, arg2: Int): Int { //如果用一个变量来接收这个函数,则可以写成匿名函数
    return arg1 + arg2
}
val sum3 = fun(arg1: Int, arg2: Int) = arg1 + arg2 //sum2的简写形式

结果如下:

玩转Kotlin之程序结构_第3张图片

三、Lambda表达式

1、什么是Lambda表达式

实际上就是匿名函数,写法:{[参数列表] -> [函数体,最后一行是返回值]} 

举例说明:val sum = {a:Int,b:Int -> a+b}

2、Lambda表达式的类型

() -> Unit 无参,返回值为Unit

(Int) -> Int 传入整型,返回一个整型

(String,(String)->String) -> Boolean 传入字符串、Lambda表达式,返回Boolean

3、Lambda表达式的调用

用()调用,等价于invoke()方法,举个栗子:val sum = {a:Int,b:Int -> a+b} 调用:sum(1,2) 等价于sum.invoke(1,2)

4、Lambda表达式的简化

函数参数调用时最后一个Lambda可以移出去

函数参数只有一个Lambda,调用时小括号可以省略

Lambda只有一个参数可默认为it

入参、返回值与形参一致的函数可以用函数引用的方式作为实参传入

说完了理论性的东西,我们来写段代码回顾一下吧:

val add1 = 3
val add2 = 4
val arrys: Array = arrayOf("我", "爱", "中", "国")

fun main(args: Array) {
    println(sums(add1, add2)) // ()直接调用等价于invoke()
    println(sums1.invoke(add1, add2)) //invoke是运算符重载的方法
    println(printHello) //打印printHello的类型 ()->Unit
    //遍历数组,下面是从基础写法到终极写法的一个过程
//    arrys.forEach({ element -> println(element) }) //基础写法
//    arrys.forEach({ println(it) }) //对于函数来说,如果参数最后一个是lambda表达式,大括号可以移到小括号的外面
//    arrys.forEach() { println(it) } //小括号可以省略
//    arrys.forEach { println(it) }
    arrys.forEach(::println) //println接收一个参数,类型是Any,forEach的action里接收一个参数T,T是Any的子类
    arrys.forEach ForEach@{
        if (it == "中") returnForEach@
        println(it)
    }
    println("The End")
}

//(Int,Int)->Int 返回值类型是最后一行表达式的值
val sums1 = { arg1: Int, arg2: Int ->
    println("$arg1+$arg2=${arg1 + arg2}")
    arg1 * arg2
}

val printHello = {
    //没有参数 箭头就不用写了
    println("Hello Kotlin")
}

val sums = { arg1: Int, arg2: Int -> arg1 + arg2 }

来看下结果:

玩转Kotlin之程序结构_第4张图片

四、类的成员

1、什么是类的成员

属性:或者说成员变量,类范围内的变量

方法:或者说成员函数,类范围内的函数

2、函数和方法的区别

函数强调功能本身,不考虑从属

方法的称呼通常是从类的角度出发

两者只是叫法不同而已,不要纠结

3、定义方法

写法与普通函数完全一致,只是你写在了类当中,举个栗子:

class Hello{
    fun sayHello(name:String) = println("Hello,$name")
}

4、定义属性

构造方法参数中val/var修饰的都是属性,类内部也可以定义属性,举个栗子:

class Hello(val aField:Int,notAField:Int){
    var anotherField:Float = 3f
}

5、属性访问控制

属性可以定义getter/setter,举例说明:

val a:Int = 0
get() = field //它没有setter方法,因为val是不可变的
var b:Float = 0f
set(value){field = value}

6、属性初始化

  •  属性的初始化尽量在构造方法中完成 
  • 无法在构造方法中初始化,尝试降级为局部变量
  • var用lateinit延迟初始化,val用lazy
  • 可空类型谨慎使用null直接初始化

下面来写一段代码,将上面总结的知识点都囊括进去做个实践:

package com.jarchie.cn.control

class X
class A {
    var b = 0
        //Kotlin默认给我们实现了一套setter和getter,如果想要单独做处理可以这样写
        set(value) {
            println("set some values here")
            field = value
        }
        get() {
            println("you get some values here")
            return field
        }
    //private,protected用来控制访问权限,setter和getter可以简单的按照以下的写法
    //protected var b = 0
    //protected set
    //protected get

    //延迟初始化,var修饰的变量可以使用lateinit可以进行延迟初始化编译器在语法上通过,但是你在调用的时候一定要记得初始化
    lateinit var c: String
    lateinit var d: X
    //val修饰的可以使用lazy,源码中是一个lambda表达式,表达式内部返回泛型的实例即可
    val e: X by lazy {
        //只有在使用的时候才会被调用到
        println("init X")
        X()
    }
    var mm: String? = null //初始化为可空字符串,容易导致空指针,最好的定义方式是都放在构造方法中构造
}

fun main(args: Array) {
    println("start")
    val a = A()
    println("init a")
    println(a.b)
    println(a.e)
    a.d = X()
    println(a.d)
//    println(a.c) //c没有初始化,直接调用会crash
//    println(a.mm!!.length) //强行告诉编译器不为空,运行时还是躲不过空指针异常
}

看下运行结果:

玩转Kotlin之程序结构_第5张图片

五、基本运算符

  • 任何类可以定义或者重载父类的基本运算符 
  • 通过运算符对应的具名函数来定义 
  • 对参数个数作要求,对参数和返回值类型不做要求
  • 不能像Scala一样定义任意运算符

我们来自己定义一个运算的函数,这里就随便写一个了,因为没想到什么好的例子,就瞎编了一个:

class Add(var add1: Int, var add2: Int) {
    operator fun plus(sum: Add): Add {
        return Add(add1+sum.add1,add2+sum.add2) //(1+3,2+4)
    }

    override fun toString(): String {
        return "$add1+$add2" //4+6
    }
}

fun main(args: Array) {
    val num1 = Add(1, 2) //1+2
    val num2 = Add(3, 4) //3+4
    println(num1.plus(num2))
}

结果就是4+6,这里大家了解一下就行了,知道运算符可以定义就行。

六、表达式

1、中缀表达式

如果一个函数只有一个参数,且用infix修饰的函数,举例:

class Book{infix fun on(place:String){...}}
Book() on "My Desk"

2、分支表达式(重点是表达式)

  • if表达式

格式:if...else  例如:  if(a==b)...else if(a==c)...else...

  • 表达式与完备性

val x = if(b<0) 0 else b

val x = if(b<0) 0 //错误,赋值时,分支必须完备

举个栗子吧,来看一段代码,体会一下它的用法:

private const val USERNAME = "kotlin"
private const val PASSWORD = "android"

private const val ADMIN_USER = "admin"
private const val ADMIN_PWD = "admin"

private const val DEBUG = 1
private const val USER = 0

fun main(args: Array) {
    //if表达式 有返回值
    val mode = if (args.isNotEmpty() && args[0] == "1") {
        DEBUG
    } else {
        USER
    }
    println("请输入用户名:")
    val userName = readLine()
    println("请输入密码:")
    val passWord = readLine()
    if (mode == DEBUG && userName == ADMIN_USER && passWord == ADMIN_PWD) {
        println("管理员登录成功")
    } else if (userName == USERNAME && passWord == PASSWORD) {
        println("登录成功")
    } else {
        println("登录失败")
    }
}

来看结果:

玩转Kotlin之程序结构_第6张图片

3、When表达式

  • 加强版switch,支持任意类型,也不用写break 
  • 支持纯表达式条件分支(类似if)
  • 表达式与完备性

来写个例子看一下:

fun main(args: Array) {
    val num = 5
    when (num) {
        is Int -> println("Hello $num")
        in 1..100 -> println("$num is in 1..100")
        !in 1..100 -> println("$num is not in 1..100")
        args[0].toInt() -> println("")
        else -> { //类似于Java中的default
            println("随便输出个结果吧")
        }
    }

    //when跟if语句一样,它也有返回值也是个表达式,每个分支最后一个表达式的值就是when表达式在这种条件下的结果
    val mode = when {
        args.isNotEmpty() && args[0] == "1" -> 1
        else -> 0
    }
}

结果如下:

玩转Kotlin之程序结构_第7张图片

七、循环语句

1、for循环

  • 基本写法:for(element in elements)...

举个栗子,写一段代码来看一下基本的遍历形式是什么样的,这里还扩展使用了另外两种形式:

val arrs: Array = arrayOf("我", "爱", "中", "国")

fun main(args: Array) {
    for (arg in arrs) { //遍历数组,最普通的写法
        println(arg)
    }
    //public fun  Array.withIndex(): Iterable>
    for (indexedValue in arrs.withIndex()) { //跟下面的写法本质上是一样的
        println("${indexedValue.index} ---> ${indexedValue.value}")
    }
    //public data class IndexedValue(public val index: Int, public val value: T)
    for ((index, value) in arrs.withIndex()) {
        println("$index ---> $value")
    }
}

看下结果是不是跟你想的一样呢:

玩转Kotlin之程序结构_第8张图片

  • 给任意类实现Iterator方法

这种方式大家看一下就行了,它其实就是for循环背后的运行机制,这里也写个例子吧:

fun main(args: Array) {
    val list = MyIntList()
    list.add(1)
    list.add(2)
    list.add(3)
    list.add(4)
    for (i in list) {
        println(i)
    }
}

//自定义我们的Iterator
class MyIterator(val iterator: Iterator) {
    operator fun next(): Int {
        return iterator.next()
    }

    operator fun hasNext(): Boolean {
        return iterator.hasNext()
    }
}

class MyIntList { //定义一个集合类
    private val list = ArrayList()
    fun add(int: Int) {
        list.add(int)
    }

    fun remove(int: Int) {
        list.remove(int)
    }
 
    //实现iterator方法
    operator fun iterator(): MyIterator {
        return MyIterator(list.iterator())
    }
}

使用这种方式也能实现遍历,这里结果就是打印1、2、3、4,我就不截图了。

2、While循环

While循环其实没什么好说的,在C、Java这些语言当中都有,语法都是一样的,不再废话了,看个例子:

fun main(args: Array) {
    var num = 4
    while (num > 0) { //先判断后执行
        println(num)
        num--
    }

    do { //先执行后判断
        println(num)
        num--
    } while (num > 0)
}

3、跳过和终止循环

  • 跳过当前循环用continue 
  • 终止循环用break 
  • 多层循环嵌套的终止结合标签使用,这个不是很常用,不做具体介绍
Outter@for(...){
    Inner@while(i<0) { if(...) break@Outter}
}

简单写个例子,都能看懂的那种:

val arrs: Array = arrayOf("我", "爱", "中", "国")

fun main(args: Array) {
    for (arg in arrs) {
        if (arg == "我") continue //跳过本次循环
        if (arg == "中") break //终止循环
        println(arg)
    }
}

这段代码的结果当然就只打印一个“爱”。

八、异常捕获

程序运行当中难免会出异常,为了不给用户带来困扰,同时也让我们的程序能够应对各种意外场景,我们需要对一些异常进行捕获,并做相应的处理,比如异常时给出提示等,基本形式就是 try...catch...。

  • catch分支匹配异常类型 
  • try...catch...本身也是表达式,可以用来赋值
  • finally 无论代码是否抛出异常都会执行
return try{x/y}catch(e:Exception){0}finally{...
    //注意这种情况下finally中的内容也会执行,而且是先执行,再返回整个表达式的值
}

针对 try...catch...来写段代码看一下吧:

fun main(args: Array) {
    try {
        val arg1 = args[0].toInt()
        val arg2 = args[1].toInt()
        println("$arg1 + $arg2 = ${sumPlus(arg1, arg2)}")
    } catch (e: NumberFormatException) {
        println("请输入整数")
    } catch (e: ArrayIndexOutOfBoundsException) {
        println("请输入两个整数")
    } catch (e: Exception) {
        println("程序出现了未知异常。${e.message}")
    } finally {
        println("感谢您使用本程序")
    }
}

fun sumPlus(num1: Int, num2: Int): Int {
    return num1 + num2
}

执行结果如下:

玩转Kotlin之程序结构_第9张图片

九、具名参数、变长参数和默认参数

1、具名参数

给函数的实参附上形参,举个栗子:

fun sum(arg1:Int,arg2:Int) = arg1+arg2
sum(arg1 = 2,arg2 = 5) //因为是具名参数,顺序交换也是没有问题的

2、变长参数vararg

  • 某个参数可以接收多个值
  • 可以不为最后一个参数
  • 如果传参时有歧义,需要使用具名参数

对于Spread Operator(*)——这是一种特殊的运算符,它的特点如下:

  • 只支持展开Array
  • 只用于变长参数列表的实参
  • 不能重载
  • 不算一般意义上的运算符

来写个例子吧:

//变长参数
fun main(vararg args: String) {
    val array = intArrayOf(1, 3, 5)
    hello(3.0, 1, 2, string = "kotlin")
    //*是Spread Operator只能展开数组
    hello(3.0, *array, string = "kotlin")
}

//因为kotlin中有具名参数,所以变长参数可以放在任意位置,这里不同于Java
fun hello(double: Double, vararg int: Int, string: String) {
    int.forEach(::println)
    println(string)
}

来看下结果:

玩转Kotlin之程序结构_第10张图片

3、默认参数

  • 为函数参数指定默认值
  • 可以为任意位置的参数指定默认值
  • 传参时,如果有歧义,需要使用具名参数

我们再来写个例子吧,这次在上一个的例子上稍微做个改动,来看代码:

fun main(vararg args: String) {
    val array = intArrayOf(1, 3, 5)
    hello(int= *array, string = "kotlin")
}

//这里是给double赋上默认值,调用时第一个参数就可以不传
fun hello(double: Double=3.0, vararg int: Int, string: String) {
    println(double)
    int.forEach(::println)
    println(string)
}

十、综合案例——命令行计算器

这一部分我们来写个综合案例,将上面的这些东西做个综合运用,来写个命令行版的简易计算器,大家都可以尝试着写一下,这里直接上代码:

package com.jarchie.kotlin.control

/**
 * 命令行计算器
 * Created by 安奇 on 2019/12/26.
 */
fun main(args: Array) {
    println("-----------欢迎使用命令行计算器------------")
    while (true) {
        try {
            println("请输入算式例如:1 + 1")
            val input = readLine() ?: break
            val splits = input.trim().split(" ")
            if (splits.size < 3) {
                throw IllegalArgumentException("参数个数不正确")
            }
            val arg1 = splits[0].toDouble()
            val op = splits[1]
            val arg2 = splits[2].toDouble()
            println("$arg1 $op $arg2 = ${Operator(op).apply(arg1, arg2)}")
        } catch (e: NumberFormatException) {
            println("您输入的数字格式不正确")
        } catch (e: IllegalArgumentException) {
            println("您输入的算式格式不正确,请用空格分开")
        } catch (e: Exception) {
            println("亲爱的用户,程序发生未知异常,${e.message}")
        }
        println("输入[Y]重新计算:")
        val cmd = readLine()
        if (cmd == null || cmd.toLowerCase() != "y") {
            break
        }
    }
    println("感谢您的使用,期待下次再会!")
}

class Operator(op: String) {
    val opFun: (left: Double, right: Double) -> Double

    init {
        opFun = when (op) {
            "+" -> { l, r -> l + r }
            "-" -> { l, r -> l - r }
            "*" -> { l, r -> l * r }
            "/" -> { l, r -> l / r }
            "%" -> { l, r -> l % r }
            else -> {
                throw UnsupportedOperationException(op)
            }
        }
    }

    fun apply(left: Double, right: Double): Double {
        return opFun(left, right)
    }
}

来看一下运行结果:

玩转Kotlin之程序结构_第11张图片

嗯,还不错哈,基本上是可以使用了,那我们现在来把这个类导出可执行程序,只要我们有jre的运行时环境,都可以跑我们的这个小程序,那让我们来试试吧!

第一步:在build.gradle文件中添加一个插件以及我们的main方法所在的类,添加完了之后同步一下,如下所示:

玩转Kotlin之程序结构_第12张图片

第二步:找到右侧的Gradle面板打开,然后依次找到Tasks-->distribution-->installDist,双击执行,如下所示:

玩转Kotlin之程序结构_第13张图片

第三步:找到工程的build目录下面的 install-->group包-->bin,这里就是生成的可执行文件了,有linux环境和window环境两种,我这里环境是Mac的,所以就在终端上使用了一下,效果还不错,记得修改文件权限为可执行权限:

玩转Kotlin之程序结构_第14张图片

好了,这一次就这么多了,笔记写完了,代码之路遥遥无期啊,大家加油!

写作不易,您的点赞是我最大的动力,各位晚安!

你可能感兴趣的:(Kotlin)