kotlin基础语法

1. 序言

1.1 开发环境配置

要开发Kotlin程序,我们首先需要安装Java环境,我们一般使用Kotlin都是在JVM平台上进行开发(Kotlin同样可以开发系统原生程序、JavaScript程序、安卓程序、iOS程序等)因为Java支持跨平台,能在编译后再任意平台上运行,因此,我们将JVM环境中学习Kotlin程序的开发,接下来我们要安装两个环境:

  • Java 17 环境
  • Kotlin 1.9.0 环境

1.2 程序代码基本结构

示例代码:

1
2
3
fun main() {
println("Hello World!")
}

运行结果:

1725710719048.png

注意事项:其中严格区分大小写,一行代码结束不需要添加分号结尾,除非需要在一行中添加多行代码。

1.3 程序注释编写

同java一样,单行使用 // ,多行使用 /* */

2. 变量与基本类型

2.1 变量的声明与使用

要声明一个变量,我们需要使用以下格式:

1
var [变量名称] : [数据类型]
  • 标识符可以由大小写字母、数字、下划线(_)和美元符号($)组成,但是不能以数字开头。
  • 变量不能重复定义,大小写敏感,比如A和a就是两个不同的变量。
  • 不能有空格、@、#、+、-、/ 等符号。
  • 应该使用有意义的名称,达到见名知意的目的(一般我们采用英文单词),最好以小写字母开头。
  • 不可以是 true 和 false。
  • 不能与Kotlin语言的关键字或是基本数据类型重名

例如:

1
2
3
fun main() {
var a : Int = 10
}

也可以简化为如下:

1
2
3
fun main() {
var a = 10
}

常量的值只有第一次赋值可以修改,其他任何情况下都不行:

如果需要申明一个常量:

1
2
3
4
fun main() {
val a: Int
a = 777;
}

2.2 数字类型

Kotlin提供了一组表示数字的内置类型,对于整数,有四种不同大小的类型,因此,值范围:

类型 大小(位) 最小值 最大值
Byte 8 -128 127
Short 16 -32768 32767
Int 32 -2,147,483,648 (-2^31) 2,147,483,647(2^31-1)
Long 64 -9,223,372,036,854,775,808 (-2^63) 9,223,372,036,854,775,807(2^63 - 1)

例如:

1
2
3
4
val one = 1 // Int
val threeBillion = 3000000000 // Long
val oneLong = 1L // 我们也可以在数字后面添加大写字母L来表示这是一个Long类型的数值
val oneByte: Byte = 1 //Int类型数据也可以在符合其他类型范围时自动转换

对于一些比较长的数字,我们可能需要使用类似于分隔符一类的东西来方便我们计数,比如:

银行往往把1000000000这种长数字记为1,000,000,000,这样看起来会更直观

在Kotlin中也可以像这样去编写:

1
val a = 1_000_000_000

除了整数类型外,Kotlin还为无符号整数提供以下类型:

  • UByte:一个无符号8位整数,范围从0到255
  • UShort:无符号16位整数,范围从0到65535
  • UInt:一个无符号32位整数,范围从0到2^32 - 1
  • ULong:一个无符号64位整数,范围从0到2^64 - 1

为了使无符号整数更易于使用,Kotlin同样提供了用后缀标记,该后缀表示无符号类型(类似于上面的Long类型添加L字母)

  • 使用uU字母作为后缀表示无符号整数。而具体的类型是根据前面变量的类型确定的,如果变量没有提供类型,编译器将根据数字的大小使用UIntULong
1
2
3
4
5
6
val b: UByte = 1u  // UByte类型, 由变量提供的类型
val s: UShort = 1u // UShort类型, 由变量提供的类型
val l: ULong = 1u // ULong类型, 由变量提供的类型

val a1 = 42u // UInt类型,根据数字大小自动推断得到
val a2 = 0xFFFF_FFFF_FFFFu // ULong类型,根据数字大小自动推断得到
  • uLUL可以将文字直接标记为无符号Long类型:

    1
    val a = 1UL // ULong类型,直接使用后缀标记

    对于小数来说,Kotlin提供符合IEEE 754标准的浮点类型FloatDoubleFloat为IEEE 754标准中的单精度数据,而`Double位标准中的双精度数据,对于单双精度,本质上就是能够表示的小数位精度,双精度比单精度的小数精度更高。

    这些类型的大小不同,并为不同精度的浮点数提供存储:

    类型 大小(位) 符号与尾数位数 阶码位数 小数位数
    Float 32 24 8 6-7
    Double 64 53 11 15-16

我们也可以直接创建小数类型的DoubleFloat变量,小数部分与整数部分由一个小数点(.)隔开,编译器默认情况下会将所有的小数自动推断为推断Double类型:

1
2
3
val pi = 3.1415 // 默认推断为Double类型
val one: Double = 1 // 这种写法是错误的,因为1不是小数,无法编译通过
val one: Double = 1.0 // 但是这种写法就是对的,因为这样表示就是小数,即使小数位是0

由于默认是Double类型,如果我们要明确指定值为Float类型,那么需要添加后缀fF,并且由于精度问题,如果该值包含超过6-7位小数,则会丢失一部分精度:

1
2
val e = 2.7182818284 // Double类型的数值
val e: Float = 2.7182818284f // 这里表示为Float会导致精度折损,得到2.7182817

2.3 数字类型的运算

Kotlin支持数学上标准的算术运算集,例如:+-*/% 并且这些运算符都是通过运算符重载实现的具体功能,与Java没有很大的区别

Kotlin提供了一组整数的位运算操作,可以直接在二进制层面上与数字表示的位进行操作,不过只适用于IntLong类型的数据:

  • shl(bits)– 有符号左移
  • shr(bits)– 有符号右移
  • ushr(bits)– 无符号右移
  • and(bits)– 按位与
  • or(bits)– 按位或
  • xor(bits)– 按位异或
  • inv()– 取反

以上位运算都需要将数据转换成二进制后在进行相应的计算

2.4 布尔类型*

布尔类型是Kotlin中的一个比较特殊的类型,和,它并不是存放数字的,而是状态,它有下面的两个状态:

  • true - 真
  • false - 假

在Kotlin中,布尔类型 Boolean 只能使用 truefalse 表示。不像一些其他语言(如C或Java),在Kotlin中你不能使用数字 10 来代表 truefalse。Kotlin 是一种静态类型语言,有着严格的类型检查,因此要求布尔表达式必须使用布尔值。

例如,下面的代码在Kotlin中是错误的:

1
val isTrue: Boolean = 1  // 错误:类型不匹配

正确的做法是:

1
val isTrue: Boolean = true

如果你需要将数字转换为布尔值,你需要显式地进行转换。通常,非零值在许多语言中会被视为 true,而零被视为 false。在Kotlin中,你可以使用 != 0 来检查一个整数值是否为非零,并将其用作布尔值:

1
2
val number = 1
val isTrue = number != 0 // 结果是 true

或者,你可以使用 toInt() 函数将布尔值转换为 01

1
2
val boolValue = true
val numericValue = boolValue.toInt() // 结果是 1

总之,Kotlin 强调类型安全,不允数字直接用作布尔值。

布尔值除了可以直接赋值得到,也可以通过一些关系运算得到,常见的关系运算有大于、小于以及等于,所有的关系运算在下方:

  • 判断两个数是否相等:a == ba != b
  • 判断数之间大小:a < ba > ba <= ba >= b
  • 判断数是否在指定范围中:a..bx in a..bx !in a..b

例如以下例子:

1
2
3
4
5
6
7
8
9
fun main() {
val a = 10
val b = 8
println(a == b) //判断a是否等于b(注意等号要写两个,因为单等号为赋值运算)
println(a >= b) //判断a是否大于等于b
println(a < b) //判断a是否小于b
val c: Boolean = a != b //判断a是否不等于b并将结果赋值给变量c
println(c)
}

1725718502514.png

可以看到,通过逻辑运算得到的结果,都是true或false,也就是我们这里学习的Boolean类型值。在Kotlin中,我们为了快速判断某个数是否在一个区间内,可以直接使用 a..b 来表示一个数学上[a, b]这样的闭区间,比如我们这里要判断变量a的值是否在1~10之间:

2.5 字符类型

字符类型也是一个重要的基本数据类型,它可以表示计算机中的任意一个字符(包括中文、英文、标点等一切可以显示出来的字符)字符由Char类型表示,字符值用单引号:' '囊括:

1
2
val c: Char = 'A'
println(c)

实际上每个字符在计算机中都会对应一个字符码,首先我们需要介绍ASCII码:

1725722115236.png

比如我们的英文字母A要展示出来,那就是一个字符的形式,而其对应的ASCII码值为65,我们可以使用.code来获取某个字符对应的ASCII码,比如下面这样:

1
2
3
4
fun main() {
val c: Char = 'A'
println(c.code) //这里就会打印字符对应的ASCII码
}

1725722516077.png

字符型占据2个字节的空间用于存放数据:

  • char 字符型(16个bit,也就是2字节,它不带符号)范围是0 ~ 65535

不过,这里的字符表里面不就128个字符吗,那char干嘛要两个字节的空间来存放呢?我们发现表中的字符远远没有我们所需要的那么多,这里只包含了一些基础的字符,中文呢?那么多中文字符(差不多有6000多个),用ASCII编码表那128个肯定是没办法全部表示的,但是我们现在需要在电脑中使用中文,这时,我们就需要扩展字符集了。

Unicode是一个用于表示文本字符的标准字符集。它包含了世界上几乎所有的已知字符,包括不同国家和地区的字母、数字、标点符号、符号图形以及特殊的控制字符。

与Unicode不同,ASCII(American Standard Code for Information Interchange)是一个只包含128个字符的字符集。它最初是为了在计算机系统中传输基本英语字符而设计的。ASCII字符集包含了常见的拉丁字母、数字、标点符号以及一些特殊字符。

Unicode采用了一个更加广泛的字符编码方案,包括了不同的字符集编码,比如UTF-8和UTF-16等。UTF-8是一种可变长度的编码方案,它可以用来表示Unicode中的任意字符,且向后兼容ASCII字符集。而UTF-16则是一种固定长度的编码方案,它使用两个字节来表示一个Unicode字符。

与ASCII相比,Unicode的主要优势在于它能够表示各种不同的语言和字符,而不仅仅限于英语字符。这使得Unicode成为全球通用的字符编码标准,为不同国家和地区的语言提供了统一的编码方式。

所以,一个Char就能表示几乎所有国家语言的字符,这样就很方便了。

接着我们来介绍一下转译字符,对于一些我们平时很难直接通过键盘或是输入法打出来的字符,比如一些特殊符号:

1725722686074.png

这些符号我们没办法直接打出来,但是现在我们又想要表示它们,该怎么做呢?我们可以使用转义来将这些字符对应的Unicode编码转换为对应的字符,只需要在前面加上\u即可,比如这个符号:

1
2
3
4
fun main() {
val c = '\u2713' //符号✓对应的Unicode编码为10003,这里需要转换为16进制表示,结果为0x2713
println(c)
}

除了能像这样表示一个特殊字符,我们也可以使用一些其他的转义字符来表示各种东西:

  • \t – 选项卡
  • \b – 退格
  • \n – 换行(LF)
  • \r – 回车(CR)
  • \' – 单引号
  • \" – 双引号
  • \\ –反斜杠
  • \$ – 美元符号

2.6 字符串类型

字符串类是一个比较特殊的类型,它用于保存字符串。我们知道,基本类型Char可以保存一个2字节的Unicode字符,而字符串则是一系列字符的序列,它的类型名称为String,和java没有太多的区别。

字符串通常由双引号""囊括,它可以表示一整串字符:

1
val str: String = "Hello Kotlin"

可以使用变量也可以使用常量进行储存:

1
2
3
4
5
fun main() {
var c: String = "abc"
c="didi"
println(c) //这里就会打印字符对应的ASCII码
}

不过,字符串只能写一行,有时候有点不太够用,可能我们想要打印多行文本,我们除了用\n转义字符来换行之外,也可以直接使用三个双引号"""来表示一个原始字符串,但是原始字符串无法使用转义字符:

1
2
3
4
5
6
7
fun main() {
val text = """
这是第一行
这第二行
"""
println(text)
}

1725785410500.png

同时也可以进行字符串的拼接;

1
2
3
4
5
6
7
8
9
10
11
12
13
fun main() {
val str1 = "Hello"
val str2 = "World"
val str = str1 + str2
println(str) //使用 + 来拼接两个字符串,得到的结果就是两个字符串合在一起的结果
val a = 10
val text1 = "${a}这是拼接的值" //添加花括号就可以消除歧义了
val text2 = "${a > 20}这是拼接的值" //花括号中也可以写成表达式
val text3 = "这是拼接的值$a"
println(text1)
println(text2)
println(text3)
}

1725785645308.png

3. 流程控制

3.1 选择结构(if-else)

和其他语言并无差异:

1
2
3
4
5
6
7
8
9
10
11
fun main() {
val score = 66
if (score >= 90) //90分以上才是优秀
println("优秀")
else if (score >= 70) //当上一级if判断失败时,会继续判断这一级
println("良好")
else if (score >= 60)
println("及格")
else //当之前所有的if都判断失败时,才会进入到最后的else语句中
println("不及格")
}

1725785766593.png

3.2 选择结构(when)*

when定义具有多个分支的条件表达式。它类似于类似Java和C语言中的switch语句,它简单的形式看起来像这样:

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

例如:

1
2
3
4
5
6
7
8
fun main() {
val c = 'A'
when (c) {
'A' -> println("c=A")
'B' -> println("c=B")
'C' -> println("c=C")
}
}

1725786072753.png

有时候我们可能希望某些值都属于同一个情况,可以使用逗号将其条件组合成一行:

1
2
3
4
when (x) {
0, 1 -> print("x == 0 or x == 1")
else -> print("otherwise")
}

举例之前的分数区间段:

1
2
3
4
5
6
7
8
9
10
11
12
fun main() {
val score = 76
val grade = when(score) {
//使用in判断目标变量值是否在指定范围内
in 100..90 -> "优秀"
in 89..80 -> "良好"
in 79..70 -> "及格"
in 69..60 -> "刚好及格"
else -> "不及格"
}
println(grade)
}

1725786273708.png

3.3 循环结构(for)*

要想实现循环的结构,参照之前in a..b的结构

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

例如:

1
2
3
4
fun main() {
for (i in 1..3) //这里直接写入1..3表示1~3这个区间
println("i的值为:$i")
}

1725786419602.png

我们也可以自由控制每一轮增加多少,也就是步长:

1
2
3
4
5
fun main() {
for (i in 1..10 step 2) {
println(i)
}
}

这样,打印出来的数据会按照步长进行增长:

1725786472662.png

那如果我们需要从10到1倒着进行遍历呢?我们可以将..替换为downTo来使用:

1
2
3
4
5
fun main() {
for (i in 5 downTo 1) {
println(i) //这里得到的就是10到1倒着排列的范围了
}
}

1725786543529.png

同样,我们需要提前终止循环结构的依旧是使用 continuebreak 两个关键字跳出循环结构。

关键字 作用
continue 跳出剩余的所有循环
break 跳出当前所在的内层循环

3.4 循环结构(while)

while相当于是一个简化版本,它只需要我们填写循环的维持条件即可,和java没有差距,比如:

1
while(循环条件) 循环体;
1
2
3
4
5
6
7
8
fun main() {
var i = 100
while (i > 0) {
if (i < 10) break
println(i)
i /= 2
}
}

同样也可以写成do whlie的形式:

1
2
3
4
5
6
7
8
9
fun main() {
var i = 100
do {
if (i < 10) break
println(i)
i /= 2
} while (i > 0)
}

1725787569579.png

4. 总结

在变量的声明有所不同,其中带 * 的需要重点了解,尤其在布尔类型中,不能使用常数量来表示true的值,在循环结构中这里使用in ... 两个来代替平常使用中的范围选择。