玩轉Kotlin之程序結構

目錄

前言

一、常量與變量

二、函數

三、Lambda表達式

四、類的成員

五、基本運算符

六、表達式

七、循環語句

八、異常捕獲

九、具名參數、變長參數和默認參數

十、綜合案例——命令行計算器


前言

磨磨唧唧的又更了一篇,不容易啊!不能怪我,要怪就怪慶餘年太好看了,導致我邊看劇邊寫作產出略低啊!今天繼續Kotlin語言的學習,上一篇介紹的基本數據類型屬於詞法,這裏接着上一篇的內容來說說Kotlin的語法,也就是程序結構。

一、常量與變量

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<String>) {
    //FINAL_HELLO_KOTLIN = "a" 編譯器會報錯 val實際上就是value
    println(FINAL_HELLO_KOTLIN)
    println(FINAL_HELLO_JAVA)
    hello = "Hello Python" //再次給變量賦值
    println(hello)
}

來看結果,很明顯了:

二、函數

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<String>): 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的簡寫形式

結果如下:

三、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<String> = arrayOf("我", "愛", "中", "國")

fun main(args: Array<String>) {
    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 }

來看下結果:

四、類的成員

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<String>) {
    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) //強行告訴編譯器不爲空,運行時還是躲不過空指針異常
}

看下運行結果:

五、基本運算符

  • 任何類可以定義或者重載父類的基本運算符 
  • 通過運算符對應的具名函數來定義 
  • 對參數個數作要求,對參數和返回值類型不做要求
  • 不能像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<String>) {
    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<String>) {
    //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("登錄失敗")
    }
}

來看結果:

3、When表達式

  • 加強版switch,支持任意類型,也不用寫break 
  • 支持純表達式條件分支(類似if)
  • 表達式與完備性

來寫個例子看一下:

fun main(args: Array<String>) {
    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
    }
}

結果如下:

七、循環語句

1、for循環

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

舉個栗子,寫一段代碼來看一下基本的遍歷形式是什麼樣的,這裏還擴展使用了另外兩種形式:

val arrs: Array<String> = arrayOf("我", "愛", "中", "國")

fun main(args: Array<String>) {
    for (arg in arrs) { //遍歷數組,最普通的寫法
        println(arg)
    }
    //public fun <T> Array<out T>.withIndex(): Iterable<IndexedValue<T>>
    for (indexedValue in arrs.withIndex()) { //跟下面的寫法本質上是一樣的
        println("${indexedValue.index} ---> ${indexedValue.value}")
    }
    //public data class IndexedValue<out T>(public val index: Int, public val value: T)
    for ((index, value) in arrs.withIndex()) {
        println("$index ---> $value")
    }
}

看下結果是不是跟你想的一樣呢:

  • 給任意類實現Iterator方法

這種方式大家看一下就行了,它其實就是for循環背後的運行機制,這裏也寫個例子吧:

fun main(args: Array<String>) {
    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<Int>) {
    operator fun next(): Int {
        return iterator.next()
    }

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

class MyIntList { //定義一個集合類
    private val list = ArrayList<Int>()
    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<String>) {
    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<String> = arrayOf("我", "愛", "中", "國")

fun main(args: Array<String>) {
    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<String>) {
    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
}

執行結果如下:

九、具名參數、變長參數和默認參數

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)
}

來看下結果:

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<String>) {
    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)
    }
}

來看一下運行結果:

嗯,還不錯哈,基本上是可以使用了,那我們現在來把這個類導出可執行程序,只要我們有jre的運行時環境,都可以跑我們的這個小程序,那讓我們來試試吧!

第一步:在build.gradle文件中添加一個插件以及我們的main方法所在的類,添加完了之後同步一下,如下所示:

第二步:找到右側的Gradle面板打開,然後依次找到Tasks-->distribution-->installDist,雙擊執行,如下所示:

第三步:找到工程的build目錄下面的 install-->group包-->bin,這裏就是生成的可執行文件了,有linux環境和window環境兩種,我這裏環境是Mac的,所以就在終端上使用了一下,效果還不錯,記得修改文件權限爲可執行權限:

好了,這一次就這麼多了,筆記寫完了,代碼之路遙遙無期啊,大家加油!

寫作不易,您的點贊是我最大的動力,各位晚安!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章