总结 1
本部分总结和回顾了从Hello, World!开始到Expressions & Statements结束的各个小节。
如果你是有经验的程序员,这应该是你的第一个小节。新手程序员应该阅读本小节,并完成练习,以复习第一部分的内容。
如果有任何不清楚的地方,请查阅相关主题的小节(小节标题对应了各个小节的名称)。
你好,世界!
Kotlin 支持 //
单行注释和 /*
-*/
多行注释。程序的入口点是函数 main()
:
// Summary1/Hello.kt
fun main() {
println("你好,世界!")
}
/* 输出:
你好,世界!
*/
本书中每个示例的第一行都是一个注释,包含了该章节的子目录名称,后跟 /
和文件名。你可以通过 AtomicKotlin.com 找到所有提取的代码示例。
println()
是一个标准库函数,它接受一个 String
参数(或可以转换为 String
的参数)。println()
在显示其参数后将光标移动到新行,而 print()
则将光标保留在同一行上。
Kotlin 不需要在表达式或语句的末尾使用分号。分号只在单行上分隔多个表达式或语句时才是必需的。
var
和 val
,数据类型
要创建一个不可更改的标识符,请使用 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 无法推断参数类型。函数本身具有类型,与 var
或 val
一样定义(冒号后跟类型)。函数的类型是返回结果的类型。
函数签名后跟花括号内的函数体。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
是一个表达式,它会产生一个结果。此结果可以分配给 var
或 val
。在这里,你还可以看到使用 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 提供整数类型(Int
、Long
)和浮点数类型(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))
}
/* 输出:
..........
*/
while
和 do-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 找到。