Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

总结 1

本部分总结和回顾了从Hello, World!开始到Expressions & Statements结束的各个小节。

如果你是有经验的程序员,这应该是你的第一个小节。新手程序员应该阅读本小节,并完成练习,以复习第一部分的内容。

如果有任何不清楚的地方,请查阅相关主题的小节(小节标题对应了各个小节的名称)。

你好,世界!

Kotlin 支持 // 单行注释和 /*-*/ 多行注释。程序的入口点是函数 main()

// Summary1/Hello.kt

fun main() {
  println("你好,世界!")
}
/* 输出:
你好,世界!
*/

本书中每个示例的第一行都是一个注释,包含了该章节的子目录名称,后跟 / 和文件名。你可以通过 AtomicKotlin.com 找到所有提取的代码示例。

println() 是一个标准库函数,它接受一个 String 参数(或可以转换为 String 的参数)。println() 在显示其参数后将光标移动到新行,而 print() 则将光标保留在同一行上。

Kotlin 不需要在表达式或语句的末尾使用分号。分号只在单行上分隔多个表达式或语句时才是必需的。

varval,数据类型

要创建一个不可更改的标识符,请使用 val 关键字,后跟标识符名称、冒号和该值的类型。然后添加等号和要分配给该 val 的值:

val 标识符: 类型 = 初始化

一旦为 val 分配了值,就不能重新分配。

Kotlin 的类型推断通常可以根据初始化值自动确定类型,从而产生更简单的定义:

val 标识符 = 初始化

以下两者都是有效的:

val 二月天数 = 28
val 三月天数: Int = 31

var(变量)定义看起来与此类似,使用 var 而不是 val

var 标识符1 = 初始化
var 标识符2: 类型 = 初始化

val 不同,你可以修改 var,因此以下内容是合法的:

var 花费小时数 = 20
花费小时数 = 25

然而,类型不能改变,所以如果你说:

花费小时数 = 30.5

Kotlin 在定义 花费小时数 时推断为 Int 类型,因此不会接受更改为浮点值。

函数

函数命名的子例程

fun 函数名(参数1: 类型1, 参数2: 类型2, ...): 返回类型 {
  // 代码行...
  return 结果
}

fun 关键字后跟函数名和带有参数列表的括号。每个参数必须具有显式类型,因为 Kotlin 无法推断参数类型。函数本身具有类型,与 varval 一样定义(冒号后跟类型)。函数的类型是返回结果的类型。

函数签名后跟花括号内的函数体。return 语句提供函数的返回值。

当函数由单个表达式组成时,可以使用简写语法:

fun 函数名(参数1: 类型1, 参数2: 类型2, ...): 返回类型 = 结果

这种形式称为表达式体。使用等号和表达式,而不是花括号。你可以省略返回类型,因为 Kotlin 会推断它。

以下是一个生成其参数的立方值的函数以及一个向 String 添加感叹号的函数:

// Summary1/BasicFunctions.kt

fun 立方(x: Int): Int {
  return x * x * x
}

fun 感叹(s: String) = s + "!"

fun main() {
  println(立方(3))
  println(感叹("流行"))
}
/* 输出:
27
流行!
*/

立方() 具有块体,包含了显式的 return 语句。感叹() 是表达式体,生成函数的返回值。Kotlin 推断 感叹() 的返回类型为 String

布尔值

为了进行布尔代数运算,Kotlin 提供了诸如以下操作符:

  • !(非)逻辑否定值(将 true 转换为 false,反之亦然)。
  • &&(与)仅在两个条件都为 true 时返回 true
  • ||(或)如果至少有一个条件为 true,则返回 true
// Summary1/Booleans.kt

fun main() {
  val 开门时间 = 9
  val 关门时间 = 20
  println("营业时间:$开门时间 - $关门时间")
  val 当前时间 = 6
  println("当前时间:" + 当前时间)

  val 是否营业 = 当前时间 >= 开门时间 && 当前时间 <= 关门时间
  println("营业中:" + 是否营业)
  println("不营业:" + !是否营业)

  val 是否关门 = 当前时间 < 开门时间 || 当前时间 > 关门时间
  println("关门了:" + 是否关门)
}
/* 输出:
营业时间:9 - 20
当前时间:6
营业中:false
不营业:true
关门了:true
*/

是否营业 的初始化使用 && 测试两个条件是否都为 true。第一个条件 当前时间 >= 开门时间false,因此整个表达式的结果变为 false是否关门 的初始化使用 ||,当至少有一个条件为 true 时返回 true。表达式 当前时间 < 开门时间true,因此整个表达式为 true

if 表达式

由于 if 是一个表达式,它会产生一个结果。此结果可以分配给 varval。在这里,你还可以看到使用 else 关键字:

// Summary1/IfResult.kt

fun main() {
  val 结果 = if (99 < 100) 4 else 42
  println(结果)
}
/* 输出:
4
*/

if 表达式的任何分支都可以是由花括号括起来的多行代码块:

// Summary1/IfExpression.kt

fun main() {
  val 活动 = "游泳"
  val 小时 = 10

  val 是否营业 = if (
    活动 == "游泳" ||
    活动 == "滑冰") {
    val 开门时间 = 9
    val 关门时间 = 20
    println("营业时间:" +
      开门时间 + " - " + 关门时间)
    小时 >= 开门时间 && 小时 <= 关门时间
  } else {
    false
  }
  println(是否营业)
}
/* 输出:
营业时间:9 - 20
true
*/

在代码块内定义的值(例如 开门时间)在该代码块的作用域之外不可访问。因为它们在 if 表达式中被全局定义,所以 活动小时if 表达式内部是可访问的。

if 表达式的结果是所选分支的最后一个表达式的结果。在这里,它是 小时 >= 开门时间 && 小时 <= 关门时间,它的结果是 true

字符串模板

你可以使用字符串模板在 String 内插入值。在标识符名称之前使用 $

// Summary1/StrTemplates.kt

fun main() {
  val 答案 = 42
  println("找到 $答案!")            // [1]
  val 条件 = true
  println(
    "${if (条件) 'a' else 'b'}")  // [2]
  println("打印 a $1")             // [3]
}
/* 输出:
找到 42!
a
打印 a $1
*/
  • [1] $答案答案 中包含的值进行替换。
  • [2] ${if(条件) 'a' else 'b'} 评估并用 ${} 内部表达式的结果进行替换。
  • [3] 如果 $ 后面跟的是不可识别为程序标识符的任何内容,则不会发生任何特殊情况。

使用三重引号的 String 存储多行文本或带有特殊字符的文本:

// Summary1/ThreeQuotes.kt

fun json(问题: String, 答案: Int) = """{
  "question" : "$问题",
  "answer" : $答案
}"""

fun main() {
  println(json("终极问题", 42))
}
/* 输出:
{
  "question" : "终极问题",
  "answer" : 42
}
*/

你无需在三重引号的 String 内转义特殊字符,比如 "(在普通 String 中,你可以写 \" 来插入双引号)。与普通 String 一样,你可以在三重引号的 String 内部使用 $ 插入标识符或表达式。

数值类型

Kotlin 提供整数类型(IntLong)和浮点数类型(Double)。默认情况下,整数常数是 Int 类型,如果你附加了 L,则为 Long 类型。如果常数包含小数点,则为 Double 类型:

// Summary1/NumberTypes.kt

fun main() {
  val n = 1000    // Int
  val l = 1000L   // Long
  val d = 1000.0  // Double
  println("$n $l $d")
}
/* 输出:
1000 1000 1000.0
*/

Int 可以存储介于 -231 和 +231-1 之间的值。整数值可能会溢出;例如,将任何值加到 Int.MAX_VALUE 上会产生溢出:

// Summary1/Overflow.kt

fun main() {
  println(Int.MAX_VALUE + 1)
  println(Int.MAX_VALUE + 1L)
}
/* 输出:
-2147483648
2147483648
*/

在第二个 println() 语句中,我们将 1 后面附加了 L,将整个表达式的类型强制为 Long,从而避免了溢出。(Long 可以存储介于 -263 和 +263-1 之间的值)。

当你将一个 Int 除以另一个 Int 时,Kotlin 会产生一个 Int 结果,并截断任何余数。因此,1/2 会产生 0。如果涉及到 Double,则 Int 会在操作之前被提升Double,因此 1.0/2 会产生 0.5

你可能期望以下代码中的 d1 产生 3.4

// Summary1/Truncation.kt

fun main() {
  val d1: Double = 3.0 + 2 / 5
  println(d1)
  val d2: Double = 3 + 2.0 / 5
  println(d2)
}
/* 输出:
3.0
3.4
*/

由于计算顺序的原因,实际上不会这样。Kotlin 首先将 2 除以 5,整数运算得到 0,从而得到了 3.0 的结果。但是对于 d2,由于计算顺序的相同,得到了预期的结果。将 2.0 除以 5 得到 0.4。由于我们将其与 Double (0.4) 相加,3 被提升为 Double,从而产生了 3.4

理解计算顺序有助于你解释程序的运行方式,无论是逻辑操作(布尔表达式)还是数学运算。如果你不确定计算顺序,可以使用括号来强制表达你的意图。这也使得阅读你的代码的人可以清楚地理解。

使用 while 进行循环

while 循环在控制的布尔表达式产生 true 时继续执行:

while (布尔表达式) {
  // 要重复的代码
}

布尔表达式在循环开始时被评估一次,在每次迭代之前再次评估。

// Summary1/While.kt

fun testCondition(i: Int) = i < 100

fun main() {
  var i = 0
  while (testCondition(i)) {
    print(".")
    i += 10
  }
}
/* 输出:
..........
*/

Kotlin 推断 testCondition() 的结果类型为 Boolean

所有数学运算都有短版本的赋值运算符(+=-=*=/=%=)。Kotlin 还支持增量和减量运算符 ++--,无论是前缀形式还是后缀形式。

while 可以与 do 关键字一起使用:

do {
  // 要重复的代码
} while (布尔表达式)

重写 While.kt

// Summary1/DoWhile.kt

fun main() {
  var i = 0
  do {
    print(".")
    i += 10
  } while (testCondition(i))
}
/* 输出:
..........
*/

whiledo-while 之间的唯一区别是 do-while 的主体始终至少执行一次,即使布尔表达式第一次产生 false

循环与范围

许多编程语言通过逐步遍历整数来索引可迭代对象。Kotlin 的 for 允许你直接从可迭代对象(如范围和 String)中获取元素。例如,以下 for 循环会选择字符串 "Kotlin" 中的每个字符:

// Summary1/StringIteration.kt

fun main() {
  for (c in "Kotlin") {
    print("$c ")
    // c += 1 // 错误:
    // 不能重新分配 val
  }
}
/* 输出:
K o t l i n
*/

c 既不能显式定义为 var 也不能定义为 val,Kotlin 会自动将其设为 val,并推断其类型为 Char(你可以显式提供类型,但实际上很少这样做)。

你可以使用范围来遍历整数值:

// Summary1/RangeOfInt.kt

fun main() {
  for (i in 1..10) {
    print("$i ")
  }
}
/* 输出:
1 2 3 4 5 6 7 8 9 10
*/

使用 .. 创建的范围包含两个边界,但使用 until 排除顶部端点:1 until 10 等同于 1..9。你可以使用 step 来指定增量值:1..21 step 3

in 关键字

提供 for 循环迭代的同一个 in 也允许你检查一个值是否属于范围。如果测试值不在范围内,!in 返回 true

// Summary1/Membership.kt

fun inNumRange(n: Int) = n in 50..100

fun notLowerCase(ch: Char) = ch !in 'a'..'z'

fun main() {
  val i1 = 11
  val i2 = 100
  val c1 = 'K'
  val c2 = 'k'
  println("$i1 ${inNumRange(i1)}")
  println("$i2 ${inNumRange(i2)}")
  println("$c1 ${notLowerCase(c1)}")
  println("$c2 ${notLowerCase(c2)}")
}
/* 输出:
11 false
100 true
K true
k false
*/

in 也可以用于测试浮点数范围中的成员资格,尽管这样的范围只能使用 .. 而不是 until 来定义。

表达式与语句

在大多数编程语言中,最小的有用代码片段要么是一个语句,要么是一个表达式。它们之间有一个基本的区别:

  • 语句改变状态
  • 表达式表达

也就是说,表达式产生结果,而语句则不会。因为语句不返回任何东西,所以语句必须改变其周围环境的状态(也就是创建一个副作用)才能做任何有用的事情。

在 Kotlin 中,几乎所有的东西都是表达式:

val hours = 10
val minutesPerHour = 60
val minutes = hours * minutesPerHour

在每种情况下,= 右侧的所有内容都是一个表达式,它产生一个结果,然后将其赋值给左侧的标识符。

println() 这样的函数似乎不会产生结果,但因为它们仍然是表达式,所以它们必须返回一些东西。Kotlin 为这些函数提供了特殊的 Unit 类型:

// Summary1/UnitReturn.kt

fun main() {
  val result = println("返回 Unit")
  println(result)
}
/* 输出:
返回 Unit
kotlin.Unit
*/

经验丰富的程序员在完成本节练习后,可以前往总结 2

练习和解答可以在 www.AtomicKotlin.com 找到。