kotlin学习

基础篇

Clip_2024-08-04_13-59-45-1722751250661-2

1.变量与基础类型

变量

可变变量:var 不可变量:val(一但定义值后就不可修改)

fun main() {
   var X :int=7
    println(X)
}
数字类型

​ 比如 a=10, 运算的结果都是11,但是区别如下:

fun main() {
    var a = 10
    println(a++)   //这里++在后面,打印a的值依然是10,但是结束之后a的值就变成11了
    println(++a)   //这里++在前面,打印a的值是这里先自增之后的结果,就是12了
}
字符类型
fun main() {
    val d = ''    //可以直接打符号进去!
    val c = '\u2713'   //符号✓对应的Unicode编码为10003,这里需要转换为16进制表示,结果为0x2713
    println(c)
}

​ 转义类型:

\n – 换行(LF)

\t – 选项卡

\b – 退格

\r – 回车(CR)

\' – 单引号

\" – 双引号

\\ –反斜杠

\$ – 美元符号

使用:

fun main() {
   var tex ="你好\n" +"这里开始"
   println(tex)
}

打印结果:
Clip_2024-08-04_15-58-47-1722758331756-8

如果要打印多行文字,用“”“ 文本 ”“”,但是转义符号无效(即不能用换行符号)

fun main() {
   var tex ="""
      你好!
      这里是七种文学!
      我在测试\n多行文字
   """
   println(tex)
}

打印结果:
Clip_2024-08-04_16-02-28-1722758553432-11

也可以这样使用:

fun main() {
   val a = 10
   val text1 = "这是拼接的值$a"  //这里的$为模版表达式,可以直接将后面跟着的变量或表达式以字符串形式替换到这个位置
   val text2 = "$a 这是拼接的值" //注意这里$a之后必须空格,否则会把后面的整个字符串认为这个变量的名字
   println(text1)
   println(text2)
}

2.逻辑语句

IF语句

简单嵌套:

fun main() {
    val score = 12
    if (score < 60) {   //先判断不及格
        if (score > 30) //在内层再嵌套一个if语句进行进一步的判断
            println("分数在30-60之间") 
        else 
            println("分数<30")
    }
}

如果简单语句只有一行,可以不用 { } 包裹

fun main() {
    val score = 2
  	//这里判断socre是否大于60,是就得到Yes,否就得到No,并且可以直接赋值给变量
    val res = if (score > 60) "Yes" else "No"
}

对于多行,默认最后一行为返回值

fun main() {
    val score = 2
    val res = if (score > 60) {
        println("不错啊期末没挂科")
        "Yes"   //代码块默认最后一行作为返回结果
    } else {		//注意这里必须要有else作为返回值
        println("不会有人Java期末还要挂科吧")
        "No"     
    }
}
When选择语句 【相当于Switch】

基本结构:(需要考虑所有情况,否则必须有else)

when (目标) {
    匹配值1 -> 代码...   //我们需要传入一个目标,比如变量,或是计算表达式等
    匹配值2 -> 代码...   //如果目标的值等于我们这里给定的匹配值,那么就执行case后面的代码
    else -> {
        代码...    //如果以上条件都不满足,就进入else中(可以没有),类似于之前的if-elseif-else
    }
}

例子: (打印结果为:去尖子班!准备冲刺985大学!)

fun main() {
    val c = 'A'
    when (c) {
        'A' -> println("去尖子班!准备冲刺985大学!")
        'B' -> println("去平行班!准备冲刺一本!")
        'C' -> println("去职高深造。")
    }
}
While循环语句

for语句的简单版本

基本结构:

while(循环条件) 循环体;

例子:(运行结果为:100 50 25 12 6 3 1)

fun main() {
    var i = 100 //比如现在我们想看看i不断除以2得到的结果会是什么,但是循环次数我们并不明
    while (i > 0) {   //现在唯一知道的是循环条件,只要大于0那么就可以继续除
        println(i)
        i /= 2 //每次循环都除以2
    }
}

也支持break打断:(运行结果为:100 50 25 12)

fun main() {
    var i = 100
    while (i > 0) {
        if (i < 10) break
        println(i)
        i /= 2
    }
}

可以先执行再判断:(运行结果为0 1 3 4 5 6 7 8 9)

fun main() {
    var i = 0 //比如现在我们想看看i不断除以2得到的结果会是什么,但是循环次数我们并不明确
    do {  //无论满不满足循环条件,先执行循环体里面的内容
        println(" $i ")
        i++
    } while (i < 10) //再做判断,如果判断成功,开启下一轮循环,否则结束
}
For循环语句

continue:跳过 break:终止

基本机构:

for (遍历出来的单个目标变量 in 可遍历目标) 循环体

这里的可遍历目标有很多,比如:

例子:

fun main(){
   val A=1..5
   for (i in A) println("这是第 $i")    //这里的println可以用 { }包裹,也可以不用,因为是单行简单语句
}   //注意这里的i 是一个局部变量,只能在for语句中有用,在main中用不了

运行结果:

Clip_2024-08-04_16-36-34-1722760597070-14

也可以简单嵌套:

fun main() {
    for (i in 0..2)  //外层循环执行3次
        for (j in 0..2)  //内层循环也执行3次
            println("外层$i,内层$j")
}

输出结果:

Clip_2024-08-04_16-45-59-1722761161834-17

​ 跳过某轮次continue:

fun main(){
for (i in 0..2) {
    if (i == 1) continue  //比如我们希望当i等于1时跳过这一轮,不执行后面的打印
    println("在这么冷的天")
    println("当前i的值为:$i")
   }
}

打印结果:

Clip_2024-08-04_16-48-55-1722761339420-20

提前终止break:

fun main() {
    for (i in 0..2) {
        if (i == 1) break //我们希望当i等于1时提前结束
        println("伞兵一号卢本伟准备就绪!")
        println("当前i的值为:$i")
    }
}

打印结果:

Clip_2024-08-04_16-50-20-1722761427759-23

可以用标记来直接作用整个地方

不用标记:

Clip_2024-08-04_17-00-35-1722762043703-29

用标记:

Clip_2024-08-04_17-04-49

中级篇

1.函数

当一些代码需要重复使用时候,可以写成一个函数,在需要用的地方插入函数即可。可理解为“组件”

fun 函数名称([函数参数...]): 返回值类型 {
    //函数体
}

​ 如果不需要返回值,返回值类型可以不用写(默认为unit,类似Java的void,返回值为空);

例子:(由简单到复杂)
Clip_2024-08-04_17-35-59

下面一张图片中 A(小括号内的自定义参数) 就是形参实际调用函数时候传入的参数 “hello” 就是实参**。

【形参就是告诉函数你创建的函数,需要传入一个什么类型的数据;

​ 实参则是调用函数时候传入的数据】

调用有形参的函数必须填写实参,否则会报错!如果设置了形参默认值可以不写实参

Clip_2024-08-04_17-46-15

Clip_2024-08-04_18-10-13

Clip_2024-08-04_18-03-04

Clip_2024-08-04_18-17-32

编写同名但不同参数,叫函数的重载

Clip_2024-08-04_18-26-33

2.递归函数

就是函数本身调用本身函数,但是需要有终止代码

合理利用递归函数,有利于处理 循环问题

例子:

fun main() {
    println(test(5))  //计算0-5的和
}

//这个函数实现了计算0-n的和的功能
fun test(n: Int): Int{
    if(n <= 0) return 0  //当n等于0的时候就不再向下,而是直接返回0
    return n + test(n - 1)  //n不为0就返回当前的n加上test参数n-1的和
}

3.高阶函数

正是得益于函数可以作为变量的值进行存储,因此,如果一个函数接收另一个函数作为参数,或者返回值的类型就是一个函数,那么该函数称为高阶函数。

【上】

要声明函数类型,需要按照以下规则:

我们可以像下面这样编写:

//典型的函数类型 (参数...) -> 类型  小括号中间是一个剪头一样的符号,然后最后是返回类型
var func0: (Int) -> Unit  //这里的 (Int) -> Unit 表示这个变量存储的是一个有一个int参数并且没有返回值的函数
var func1: (Double, Double) -> String   //同理,代表两个Double参数返回String类型的函数

同样的,作为函数的参数也可以像这样表示:

fun test(other: (Int) -> String){
}

函数类型的变量,我们可以将其当做一个普通的函数进行调用:

fun test(other: (Int) -> String){
    println(other(1))  //这里提供的函数接受一个Int参数返回string,那么我们可以像普通函数一样传入参数调用它
}

由于函数可以接受函数作为参数,所以说你看到这样的套娃场景也不奇怪:

var func: (Int) -> ((String) -> Double)

不过这样写可能有些时候不太优雅,我们可以为类型起别名来缩短名称:

typealias HelloWorld = (String) -> Double
fun main() {
    var func: HelloWorld
}
【下】

可以使用Lambda表达式来表示一个函数实例:

fun main() {
    var func: (String) -> Int = {  //一个Lambda表达式只需要直接在花括号中编写函数体即可
        println("这是传入的参数$it")   //默认情况下,如果函数只有一个参数,我们可以使用it代表传入的参数
        666   //跟之前的if表达式一样,默认最后一行为返回值
    }
  	func("HelloWorld!")
}

是不是感觉特别简便?

fsxB2UhpXZewk4Q

对于参数有多个的情况,我们也可以这样进行编写:

fun main() {
    val func: (String, String) -> Unit = { a, b ->   //我们需要手动添加两个参数这里的形参名称,不然没法用他两
        println("这是传入的参数$a, 第二个参数$b")   //直接使用上面的形参即可
    }
  	val func2: (String, String) -> Unit = { _, b ->
        println("这是传入的第二个参数$b")   //假如这里不使用第一个参数,也可以使用_下划线来表示不使用
    }
    func("Hello", "World")
}

R9WjhIUinaP465p

是不是感觉玩的非常高级?还有更高级的在后面呢!

我们接着来看,如果我们现在想要调用一个高阶函数,最直接的方式就是下面这样:

fun main() {
    val func: (Int) -> String = { "收到的参数为$it" }
    test(func)
}

fun test(func: (Int) -> String) {
    println(func(66))
}

当然我们也可以直接把一个Lambda作为参数传入作为实际参数使用:

fun main() {
    test({ "收到的参数为$it" })
}

不过这样还不够简洁,在Kotlin中,如果函数的最后一个形式参数是一个函数类型,可以直接写在括号后面,就像下面这样:

test() { "收到的参数为$it" }

由于小括号里面此时没有其他参数了,还能继续省,直接把小括号也给干掉:

test { "收到的参数为$it" }   //干脆连小括号都省了,这语法真的绝

当然,如果在这之前有其他的参数,只能写成这样了:

fun main() {
    test(1) { "收到的参数为$it" }
}

//这里两个参数,前面还有一个int类型参数,但是同样的最后一个参数是函数类型
fun test(i: Int, func: (Int) -> String) {
    println(func(66))
}

这种语法也被称为 尾随lambda表达式,能省的东西都省了,不过只有在最后一个参数是函数类型的情况下才可以,如果不是最后一位,就没办法做到尾随了。

最后需要特别注意的是,在Lambda中没有办法直接使用return语句返回结果,而是需要用到之前我们学习流程控制时用到的标签:

fun main() {
    val func: (Int) -> String = test@{
        //比如这里判断到it大于10就提前返回结果
        if(it > 10) return@test "我是提前返回的结果"
        println("我是正常情况")
        "收到的参数为$it"
    }
    test(func)
}

fun test(func: (Int) -> String) {
    println(func(66))
}

如果是函数调用的尾随lambda表达式,默认的标签名字就是函数的名字:

fun main() {
    testName {  //默认使用函数名称
        if(it > 10) return@testName "我是提前返回的结果"
        println("我是正常情况")
        "收到的参数为$it"
    }
}

fun testName(func: (Int) -> String) {
    println(func(66))
}

不过,为什么要这么麻烦呢,还要打标签才能返回,这不多此一举么?这个问题我们会在下一节内联函数中进行讲解。

4.内联函数

使用高阶函数会可能会影响运行时的性能:每个函数都是一个对象,而且函数内可以访问一些局部变量,但是这可能会在内存分配(用于函数对象和类)和虚拟调用时造成额外开销。

为了优化性能,开销可以通过内联Lambda表达式来消除。使用inline关键字会影响函数本身和传递给它的lambdas,它能够让方法的调用在编译时,直接替换为方法的执行代码,什么意思呢?比如下面这段代码:

fun main() {
    test()
}

//添加inline表示内联函数
inline fun test(){
    println("这是一个内联函数")
  	println("这是一个内联函数")
  	println("这是一个内联函数")
}

由于test函数是内联函数,在编译之后,会原封不动地把代码搬过去:

fun main() {
    println("这是一个内联函数")   //这里是test函数第一行,直接搬过来
  	println("这是一个内联函数")
  	println("这是一个内联函数")
}

同样的,如果是一个高阶函数,效果那就更好了:

fun main() {
    test { println("打印:$it") }
}

//添加inline表示内联函数
inline fun test(func: (String) -> Unit){
    println("这是一个内联函数")
    func("HelloWorld")
}

由于test函数是内联的高阶函数,在编译之后,不仅会原封不动地把代码搬过去,还会自动将传入的函数参数贴到调用的位置:

fun main() {
    println("这是一个内联函数")   //这里是test函数第一行
  	val it = "HelloWorld"  //这里是函数内传入的参数
    println("打印:$it")  //第二行是调用传入的函数,自动贴过来
}

内联会导致编译出来的代码变多,但是同样的换来了性能上的提升,不过这种操作仅对于高阶函数有显著效果,普通函数实际上完全没有内联的必要,也提升不了多少性能。

注意,内联函数中的函数形参,无法作为值给到变量,只能调用:

ufLoiIyGKTbYV9H

同样的,由于内联,导致代码被直接搬运,所以Lambda中的return语句可以不带标签,这种情况会导致直接返回:

fun main() {
    test { return }  //内联高阶函数的Lambda参数可以直接写return不指定标签
    println("调用上面方法之后")
}

inline fun test(func: (String) -> Unit){
    func("HelloWorld")
    println("调用内联函数之后")
}

上述代码的运行结果就是,直接结束,两句println都不会打印,这种情况被称为非局部返回

回到上一节最后我们提出的问题,实际上,在Kotlin中Lambda表达式支持一个叫做"标签返回"(labeled return)的特性,这使得你能够从一个Lambda表达式中返回一个值给外围函数,而不是简单地返回给Lambda表达式所在的最近的封闭函数,就像下面这样:

fun main() {
    test { return@main }  //标签可以直接指定为外层函数名称main来提前终止整个外部函数
    println("调用上面方法之后")
}

inline fun test(func: (String) -> Unit){
    func("HelloWorld")
    println("调用内联函数之后")
}

效果跟上面是完全一样的,为了避免这种情况,我们也可以像之前一样将标签写为@test来防止非局部返回。

fun main() {
    test { return@test }  //这样就只会使test返回,而不会影响到外部函数了
    println("调用上面方法之后")
}

有些时候,可能一个内联的高阶函数中存在好几个函数参数,但是我们希望其中的某一个函数参数不使用内联,能够跟之前一样随意当做变量使用:

fun main() {
    test({ println("我是一号:$it") }, { println("我是二号:$it") })
}

//在不需要内联的函数形参上添加noinline关键字,来防止此函数的调用内联
inline fun test(func: (String) -> Unit, noinline func2: (Int) -> Unit){
    println("这是一个内联函数")
    func("HelloWorld")
  	var a = func2  //这样就不会报错,但是不会内联了
    func2(666)
}

最后编译出来的结果,类似于:

fun main() {
    println("这是一个内联函数")
    val it = "HelloWorld"
    println("打印:$it")
  	//第二个参数由于不是内联,这里依然作为Lambda使用
    val func2: (Int) -> Unit = { println("我是二号:$it") }
    func2(666)
}

由于目前知识的学习还不太够,函数我们只能先暂时告一段落,在后续的学习中我们会继续认识更多函数的特性。


2.类与对象

类是一个抽象的概念,对象是一个具体的实例,如人类与人。

类的创建与使用

一般类可以单独放在一个.kt文件中,便于管理。

对象的初始化操作

在创建对象时候可以执行多个初始化操作

class Student(var name: String) {
    init{
        println("星光荡开宇宙,本人闪亮登场!")
    }
    init {
        println("其实后面可以添加跟过init来实现初始化操作!")

    }    }
fun main() {
   val stu1=Student("东方曜")
}

上面打印结果为:

星光荡开宇宙,本人闪亮登场!
其实后面可以添加跟过init来实现初始化操作!

类的成员函数

在类中可以定义函数,在其他地方通过 . 调用

class Student(var name: String,var age: Int) {
    fun hello(){
        println("大家好!我叫$name,今年已经${age}岁了!")
    }
}
fun main() {
   val stu1=Student("李信",18)
    stu1.hello()
}   //打印结果:大家好!我叫李信,今年已经18岁了!

可见性修饰符

3.封装,继承和多态

封装、继承和多态是面向对象编程的三大特性。

  • 封装,把对象的属性和函数结合成一个独立的整体,隐藏实现细节,并提供对外访问的接口。
  • 继承,从已知的一个类中派生出一个新的类,叫子类。子类实现了父类所有非私有化的属性和函数,并根据实际需求扩展出新的行为。
  • 多态,多个不同的对象对同一消息作出响应,同一消息根据不同的对象而采用各种不同的函数。

正是这三大特性,能够让我们的Kotlin程序更加生动形象。

类的封装

在编写类时一般将成员变量私有化,外部类需要使用Getter和Setter函数来查看和设置变量。

好处:可以直接省略类内部中如何运算,只定义两个方法,传入数据,获取结果 。

​ 外面直接传入数据就可以获取结果。
Clip_2024-08-05_16-13-50

类的继承

在类前面加 open 关键字表示可被继承的父类,如

//父类  Student
open class Student(var name: String){
    fun hello()=println("你好!我是$name")
}
//子类   Artstudent
class Artstudent(name: String): Student(name){
    fun draw()=println("我会画画")
}
fun main() {
    var stu1=Artstudent("公孙离")
    stu1.draw()
    stu1.hello()
}

抽象类

实例代码

//使用abstract表示这个是一个抽象类
abstract class Student {
    abstract val type: String  //抽象类中可以存在抽象成员属性
    abstract fun hello()   //抽象类中可以存在抽象函数
  	//注意抽象的属性不能为private,不然子类就没法重写了
}

当一个子类继承自抽象类时,必须要重写抽象类中定义的抽象属性和抽象函数:

class ArtStudent: Student() {
    override val type: String = "美术生"
    override fun hello() = println("原神,启动!")
}

这是强制要求的,如果不进行重写将无法通过编译。

接口

接口参数全部为抽象,不允许实例化

用interface关键字定义接口

interface A {
    val x: String  //接口中所有属性默认都是abstract的(可省略关键字)
    fun sleep()   //接口中所有函数默认都是abstract的(可省略关键字)
}
interface B {
    fun game()
}
class Student: A, B {   //接口的实现与类的继承一样,直接写到后面,多个接口用逗号隔开
    override val x: String = "测试"   //跟抽象类一样,接口中的内容是必须要实现的
    override fun sleep() = println("管他什么早八不早八的,睡舒服再说")
    override fun game() = println("读大学就该玩游戏玩到爽")
}

类的扩展

通过这种机制,我们可以将那些第三方类不具备的功能强行进行扩展,来方便我们的操作。

class Test {
    fun hello() = println("你干嘛")
}

fun Test.hello() = println("哎哟")

fun main() {
    Test().hello()   //你干嘛
}
❤️ 转载文章请注明出处,谢谢!❤️