Skip to content

Kotlin

Kotlin is a modern yet mature programming language designed to make developers happier. It is concise, safe, interoperable with Java and other languages, and provides multiple ways to reuse code across different platforms. Designed and developed by JetBrains in 2011, Kotlin was officially released in 2016. It aims to address Java’s shortcomings in coding efficiency and code quality while maintaining full compatibility with Java. By simplifying syntax, offering more powerful features, and reducing boilerplate code, Kotlin enables developers to write clear, concise, and safe code more efficiently.

Kotlin是一种现代但已经成熟的编程语言,旨在让开发人员更快乐。它简洁、安全、可与Java和其他语言互操作,并提供了许多在多个平台之间重用代码的方法。它由JetBrains公司于2011年设计和开发,并在2016年正式发布。Kotlin旨在解决Java语言在编码效率和代码质量方面存在的问题,并且与Java语言完全兼容。Kotlin通过简化语法、提供更强大的功能以及减少样板代码的编写,使得开发者能够更高效地编写清晰、简洁而又安全的代码。

The name "Kotlin" originates from an ancient Slavic tribe. JetBrains developed Kotlin primarily to replace Java in Android development while also serving as a general-purpose programming language. By reducing boilerplate code and introducing modern language features, Kotlin provides better tools and libraries to simplify Android application development. Due to its design philosophy and unique features, Kotlin quickly gained traction and has been widely adopted. Today, Kotlin is a popular programming language used for Android app development, server-side development, and other software development fields.

Kotlin语言名字的来源是基于一个古老斯拉夫部落的名字。JetBrains开发Kotlin的初衷是为了在Android开发上取代Java,并且作为一门通用的编程语言。Kotlin通过减少样板代码和增加现代化的语言特性,提供了更好的工具和库来简化Android应用开发。由于Kotlin的设计理念和特性吸引了广泛的开发者关注,它也迅速被接受并得到了广泛的使用。现在,Kotlin已成为一门流行的编程语言,被许多开发者用于Android应用开发、服务器端开发以及其他领域的软件开发中。

官方网站:https://www.jetbrains.com/opensource/kotlin/

image-20231215224847189

Advantages of Kotlin as a Modern Statically Typed Programming Language:

  1. Strong Java Interoperability: Kotlin seamlessly interoperates with existing Java code, allowing developers to gradually adopt Kotlin in their projects without rewriting the entire codebase. This makes Kotlin an ideal choice for Android app development.
  2. Concise and Readable: Kotlin’s syntax is more concise and readable compared to Java, reducing the amount of boilerplate code required. Developers can achieve the same functionality with fewer lines of code, improving development efficiency.
  3. Null Safety: Kotlin provides better handling of null values. By default, variables are non-null, and if nullable types are needed, they must be explicitly declared. This significantly reduces the occurrence of null pointer exceptions.
  4. Functional Programming Support: Kotlin supports functional programming features such as higher-order functions, lambda expressions, and collection operations, allowing developers to write more maintainable and expressive code.
  5. Extension Functions: Kotlin allows developers to add new methods to existing classes without modifying their source code. This feature provides a flexible way to extend existing libraries.
  6. Coroutine Support: Kotlin introduces coroutines, making asynchronous operations easier to manage and write. Coroutines allow developers to write concurrent code sequentially and avoid callback hell.

Kotlin 是一种现代化的静态类型编程语言,具有以下优势:

  1. 与Java互操作性强:Kotlin 可以与现有的 Java 代码无缝地互操作,允许开发者在现有的项目中逐步采用 Kotlin,而不需要重写整个项目。这使得 Kotlin 成为 Android 应用开发的理想选择。
  2. 简洁易读:Kotlin 的语法简洁并具有更好的可读性,减少了样板代码的编写。相比 Java,Kotlin 可以使用更少的代码来实现同样的功能,从而提高开发效率。
  3. 空安全性:Kotlin 对空值进行了更好的处理。在 Kotlin 中,变量默认是非空的,如果需要使用可能为空的值,需要显式声明类型为可空。这有助于减少空指针异常的发生。
  4. 函数式编程支持:Kotlin 支持函数式编程的特性,如高阶函数、lambda 表达式和函数式编程的集合操作等。这些特性可以让开发者编写更简洁、可维护的代码,并提高代码的表达能力。
  5. 扩展函数:Kotlin 允许开发者为某个类添加新的方法,而不需要修改该类的源代码。这种扩展函数的特性可以为开发者提供更灵活的方式来扩展现有的类库。
  6. 协程支持:Kotlin 引入了协程(coroutine)机制,使得异步操作更易于管理和编写。通过使用协程,开发者可以使用顺序的方式编写并发代码,并避免了回调地狱的问题。

总的来说,Kotlin 是一门功能丰富、可读性高、与 Java 无缝互操作的编程语言,适用于 Android、Web 后端开发等多种场景。

image-20230725132203050

正是因为Kotlin与Java的高度兼容性,再加上简洁、安全、互操作性强等特点,让Kotlin一度成为Android开发的官方指定语言。并且随着时代的发展,现在它不仅仅可以开发安卓应用程序,也可以开发iOS程序,甚至开发Java后端、开发桌面应用程序等。其简洁高效的语法也受到一众开发者追捧。

image-20230725135856171


走进新语言

欢迎大家进入到Kotlin程序设计的学习中,我们将从开发环境配置开始,为各位小伙伴讲解。

编程语言可以被视为人与计算机之间进行交流的方式。它是一种用于编写计算机程序的形式化语言,用于描述计算机任务的操作步骤、算法和数据结构。简单来说,就是以计算机能听懂的语言告诉计算机我们要做什么,然后让计算机来做我们想做的事情,从而解决我们生活中各种各样的问题。

编程语言可以分为多种类型,包括低级语言和高级语言。低级语言(如汇编语言)与计算机硬件更接近,对计算机底层操作进行更精细的控制,但编写和理解起来较为复杂。高级语言(如Java、Python等)则更加易读、易写,并提供了更高层次的抽象,使得程序员能够更专注于问题的解决和算法的设计。而我们这里要学习的Kotlin语言,也属于高级语言的一种,能够使用我们人类更容易理解的语法来编写程序。

开发环境配置

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

  • Java 8 环境
  • Kotlin 1.9.0 环境

首先我们来安装Java 8 环境,这里我们需要去下载JDK,这里推荐安装免费的ZuluJDK:https://www.azul.com/downloads/?version=java-8-lts&package=jdk

在这里选择自己的操作系统对应的安装包:

image-20220916155142546

比如Windows下,我们就选择.msi的安装包即可(MacOS、Linux下同样选择对应的即可)

image-20220916155242814

Screenshot 2025-03-19 at 19.38.12

下载完成后,我们直接双击安装:

image-20220916160027645

Screenshot 2025-03-19 at 19.40.54

Screenshot 2025-03-19 at 19.47.59

Screenshot 2025-03-19 at 19.48.57

注意,这里不建议各位小伙伴去修改安装的位置!新手只建议安装到默认位置(不要总担心C盘不够,该装的还是要装,尤其是这种环境,实在装不下就去将其他磁盘的空间分到C盘一部分)如果是因为没有安装到默认位置出现了任何问题,你要是找不到大佬问的话,又得重新来一遍,就很麻烦。

剩下的我们只需要一路点击Next即可,安装完成之后,我们打开CMD命令窗口(MacOS下直接打开“终端”)来验证一下(要打开CMD命令窗口,Windows11可以直接在下面的搜索框搜索cmd即可,或者直接在文件资源管理器路径栏输入cmd也可以)

我们直接输入java命令即可:

image-20220916160756046

如果能够直接输出内容,说明环境已经安装成功了,正常情况下已经配置好了,我们不需要手动去配置什么环境变量,所以说安装好就别管了。

输入java -version可以查看当前安装的JDK版本:

image-20230728003717281

Screenshot 2025-03-19 at 19.51.03

只要是1.8.0就没问题了,后面的小版本号可能你们会比我的还要新。

接着是Kotlin 1.9.0 环境,我们需要前往:https://github.com/JetBrains/kotlin/releases 下载最新的Kotlin编译器并进行安装:

image-20230728003925164

Screenshot 2025-03-19 at 20.07.58

这里我们可以直接解压然后拖入到刚刚Java安装的同级目录下,我这里是 C:\Program Files 文件夹,也可以自定义位置,但是不推荐,因为很多小伙伴配环境直接配到自闭。

Screenshot 2025-03-19 at 20.13.03

然后我们需要手动配置一下环境变量,打开系统环境变量配置:

image-20230728004205319

添加路径 C:\Program Files\kotlinc\bin 到Path环境变量下即可,然后我们依然打开CMD查看是否安装成功,输入kotlinc -version来查看安装情况:

image-20230728004657590

Screenshot 2025-03-19 at 20.14.54

这样我们就完成了所有环境的安装,我们可以来体验一下编写并且编译运行一个简单的Kotlin程序,我们新建一个文本文档,命名为Main.txt(如果没有显示后缀名,需要在文件资源管理器中开启一下)然后用记事本打开,输入以下内容:

fun main() {
    println("Hello, World!")
}

现在看不懂代码没关系,直接用就行,我们后面会一点一点讲解的。

编辑好之后,保存退出,接着我们将文件的后缀名称修改为.kt这是Java源程序文件的后缀名称:Screenshot 2025-03-19 at 20.45.47

image-20230728004854748

此时我们打开CMD,注意要先进入到对应的路径下.

我们使用cd命令先进入到这个目录下:

image-20220916161802753

要编译一个Kotlin程序,我们需要使用kotlinc命令来进行,将我们的程序编译为jar包,并包含Kotlin的运行时依赖:

kotlinc Main.kt -include-runtime -d Main.jar

执行后,可以看到目录下多出来了一个.jar文件,这是一个打包好的标准Java程序:

image-20230728005317422

Screenshot 2025-03-19 at 20.46.50

接着我们就可以将其交给JVM运行了,我们直接使用java -jar命令即可:

image-20230728005354810

可以看到打印了一个 Hello World! 字样,这样我们的第一个Kotlin程序就成功运行了。

Screenshot 2025-03-19 at 20.47.33

IDEA安装与使用

前面我们介绍了Kotlin开发环境的安装以及成功编译运行了我们的第一个Kotlin应用程序。

但是我们发现,如果我们以后都使用记事本来进行Kotlin程序开发的话,是不是效率太低了点?我们还要先编辑,然后要改后缀,还要敲命令来编译,有没有更加方便一点的写代码的工具呢?这里我们要介绍的是:IntelliJ IDEA(这里不推荐各位小伙伴使用Eclipse,因为操作上没有IDEA这么友好)

IDEA准确来说是一个集成开发环境(IDE),它集成了大量的开发工具,编写代码的错误检测、代码提示、一键完成编译运行等,非常方便。

下载地址:IntelliJ IDEA:JetBrains 功能强大、符合人体工程学的 Java IDE

image-20220916162544360

我们直接点击下载即可,注意要下载下面的社区版,不要下载到终极版了:

image-20230728010334215

Screenshot 2025-03-19 at 20.50.43

这个软件本身是付费的,比较贵,而且最近还涨价了,不过这里我们直接下载面的社区版本就行了(JavaSE学习阶段不需要终极版,但是建议有条件的还是申请一个,功能更强大,体验更友好)

下载好之后,直接按照即可,这个不强制要求安装到C盘,自己随意,但是注意路径中不要出现中文!

image-20220916163329410

这里勾选一下创建桌面快捷方式就行:

image-20220916163401880

安装完成后,我们直接打开就可以了:

image-20230728011902631

此时界面是全英文,如果各位小伙伴看得惯,可以直接使用全英文的界面(使用英文界面可以认识更多的专业术语词汇,但是可能看起来没中文那么直观,而且IDEA本身功能就比较多,英语不好的小伙伴就很头疼)这里还是建议英语不好的小伙伴使用中文界面,要使用中文只需要安装中文插件即可:

image-20230728012014698

我们打开Plugins插件这一栏,然后直接在插件市场里面搜索Chinese,可以找到一个中文语言包的插件,我们直接Install安装即可,安装完成后点击重启,现在就是中文页面了:

image-20230728012045895

如果各位小伙伴不喜欢黑色主题,也可以修改为白色主题,只需要在自定义中进行修改即可,一共四种主题,我们还可以在下面的设置中开启新UI以及更换各种字体、字体大小等个性化内容。

如果你之前使用过其他IDE编写代码,这里还支持按键映射(采用其他IDE的快捷键方案)有需要的可以自己修改一下:

image-20220916164415447

接下来,我们来看看如何使用IDEA编写Kotlin程序,IDEA是以项目的形式对一个Java程序进行管理的,所以说我们直接创建一个新的项目,点击新建项目:

image-20220916164906998

此时来到创建页面:

image-20230728012243268

  • 名称: 你的Java项目的名称,随便起就行,尽量只带英文字母和数字,不要出现特殊字符和中文。
  • 位置: 项目的存放位置,可以自己根据情况修改,同样的,路径中不要出现中文。
  • 语言: IDEA支持编写其他语言的项目,但是这里我们直接选择Java就行了。
  • 构建系统: 在JavaSE阶段一律选择IntelliJ就行了,Maven我们会在JavaWeb之后进行讲解,Gradle会在安卓开发教程中介绍。
  • JDK: 就是我们之前安装好的JDK,如果是默认路径安装,这里会自动识别(所以说不要随便去改,不然这些地方就很麻烦)

当然,如果JDK这里没有自动识别到,那么就手动添加一下:

Screenshot 2025-03-20 at 20.28.47

image-20220916165351016

没问题之后,我们直接创建项目:

Screenshot 2025-03-20 at 21.11.14

进入之后,可以看到已经自动帮助我们创建好了一个kt源文件,跟我们之前的例子是一样的。要编译运行我们的Kotlin程序,只需要直接点击左边的三角形(启动按钮)即可:

image-20230728012647988

点击之后,会在下方自动开始构建:

image-20230728012720838

完成之后,就可以在控制台看到输出的内容了:

image-20230728012737850

我们可以看到新增加了一个out目录,这里面就是刚刚编译好的.class文件,这种文件是Java的字节码文件,可以直接运行在JVM中:

image-20230728012808045

IDEA非常强大,即使是编译之后的字节码文件,也可以反编译回原代码的样子:

image-20230728012917915

如果我们想写一个新的Kotlin项目,可以退出当前项目重新创建:

image-20230728013013293

此时项目列表中就有我们刚刚创建的Java项目了:

image-20230728013031657

如果你还想探索IDEA的其他功能,可以点击欢迎页最下方的学习:

image-20230728013059958

会有一个专门的引导教程项目,来教你如何使用各项功能:

image-20230728013143382

熟悉了IDEA的使用之后,下节课我们就可以正式地开始学习Kotlin语言的语法了。

程序代码基本结构

还记得我们之前使用的示例代码吗?

fun main() {
    println("Hello World!")
}

这段代码要实现的功能很简单,就是将 Hello World 这段文本信息输出到控制台。

在编写代码时,注意需要区分大小写,Kotlin语言严格区分大小写,如果我们没有按照规则来编写,那么就会出现红色波浪线报错:

image-20230729013954179

只要源代码中存在报错的地方,就无法正常完成编译得到字节码文件,强行运行会提示构建失败:

image-20230729014133372

注意这里包括的花括号是成对出现的,并且一一对应。

所以说各位小伙伴在编写代码时一定要注意大小写。然后第二行,准确的说是最外层花括号内部就是:

fun main() {

}

可以看到外面使用花括号前添加了fun main(),这是我们整个程序的入口点,我们的Kotlin程序也是从这里开始从上往下执行的。而其中的println语句就是用于打印其括号中包裹的文本,我们可以看到这个文本信息使用了""进行囊括,否则会报错:

println("Hello World!")

这段代码的意思就是将双引号括起来的内容(字符串,我们会在后面进行讲解)输出(打印)到控制台上。

比如下面的代码,我们就可以实现先打印Hello World!,然后再打印 KFC vivo 50 到控制台:

fun main() {
    println("Hello World!")
    println("KFC vivo 50")
}

效果如下:

image-20230729014638513

注意我们上面编写的打印语句其实是函数的调用(后续会进行讲解)不能写到同一行中,否则编译器会认为是同一句代码,同样会导致编译不通过:

image-20230729014909257

如果实在要写到同一行,那么我们需要在上一句代码最后添加;来表示上一段的结束:

image-20230729015012657

再比如下面的代码:

image-20230729015512923

这里我们尝试在中途换行或是添加空格,因为没有添加分号,所以说编译器依然会认为是一行代码,因此编译不会出现错误,能够正常通过。当然,为了代码写得工整和规范,我们一般不会随意换行或是添加没必要的空格。注意随意换行和空格仅限于可分割区域,比如println本身是一个函数的完整名称,这就不能从中间直接断开,否则语义就完全不一样了。

Screenshot 2025-03-20 at 21.33.30

程序注释编写

我们在编写代码时,可能有些时候需要标记一下这段代码表示什么意思:

image-20230729020246148

但是如果直接写上文字的话,会导致编译不通过,因为这段文字也会被认为是程序的一部分。

这种情况,我们就需要告诉编译器,这段文字是我们做的笔记,并不是程序的一部分,那么要怎么告诉编译器这不是代码呢?很简单,我们只需要在前面加上双斜杠就可以了:

image-20230729020334200

添加双斜杠之后(自动变成了灰色),后续的文本内容只要没有发生换行,那么都会被认为是一段注释,并不属于程序,在编译时会被直接忽略,之后这段注释也不会存在于程序中。但是一旦发生换行那就不行了:

image-20230729020416439

那要是此时注释很多,一行写不完,我们想要编写很多行的注释呢?我们可以使用多行注释标记:

image-20230729020514528

多行可以使用/**/的组合来囊括需要编写的注释内容。

当然还有一种方式就是使用/**来进行更加详细的文档注释:

image-20230729020728328

这种注释可以用来自动生成文档,当我们鼠标移动到Main上时,会显示相关的信息,我们可以自由添加一些特殊的注释,比如作者、时间等信息,也可以是普通的文字信息。

image-20230729020747000

这样,我们编写Kotlin程序的基本规则就讲解完毕了,从下一个小节开始,我们将先给各位小伙伴介绍我们的基本数据类型。


变量与基本类型

我们的程序不可能永远都只进行上面那样的简单打印操作,有些时候可能需要计算某些数据,此时我们就需要用到变量了。那么,什么是变量呢?我们在数学中其实已经学习过变量了:

变量,指值可以变的量。变量以非数字的符号来表达,一般用拉丁字母。变量的用处在于能一般化描述指令的方式。结果只能使用真实的值,指令只能应用于某些情况下。变量能够作为某特定种类的值中任何一个的保留器。

比如一个公式 x + 2 = 6 此时x就是一个变量,变量往往代表着某个值,比如这里的x就代表的是4这个值。在Kotlin中,我们也可以让变量去代表一个具体的值,并且变量的值是可以发生变化的,在程序中,我们也可以使用变量,并且变量具有类型。

计算机中的二进制表示(选学)

进入到变量的学习之前,我们需要先补充一下计算机的底层知识,否则各位小伙伴后面听起来会很困难。

在计算机中,所有的内容都是二进制形式表示。十进制是以10为进位,如9+1=10;二进制则是满2进位(因为我们的计算机是电子的,电平信号只有高位和低位,你也可以暂且理解为通电和不通电,高电平代表1,低电平代表0,由于只有0和1,因此只能使用2进制表示我们的数字!)比如1+1=10=2^1+0,一个位也叫一个bit,8个bit称为1字节,16个bit称为一个字,32个bit称为一个双字,64个bit称为一个四字,我们一般采用字节来描述数据大小。

注意这里的bit跟我们生活中的网速MB/s是不一样的,小b代表的是bit,大B代表的是Byte字节(8bit = 1Byte字节),所以说我们办理宽带的时候,100Mbps这里的b是小写的,所以说实际的网速就是100/8 = 12.5 MB/s了。

十进制的7 -> 在二进制中为 111 = 2^2 + 2^1 + 2^0

现在有4个bit位,最大能够表示多大的数字呢?

  • 最小:0000 => 0
  • 最大:1111 => 23+22+21+20 => 8 + 4 + 2 + 1 = 15

在Kotlin中,无论是小数还是整数,他们可以带有符号,因此,首位就作为我们的符号位,还是以4个bit为例,首位现在作为符号位(1代表负数,0代表正数):

  • 最小:1111 => -(22+21+2^0) => -7
  • 最大:0111 => +(22+21+2^0) => +7 => 7

现在,我们4bit能够表示的范围变为了-7~+7,这样的表示方式称为原码。虽然原码表示简单,但是原码在做加减法的时候,很麻烦!以4bit位为例:

1+(-1) = 0001 + 1001 = 怎么让计算机去计算?(虽然我们知道该去怎么算,但是计算机不知道!)

我们得创造一种更好的表示方式!于是我们引入了反码

  • 正数的反码是其本身
  • 负数的反码是在其原码的基础上, 符号位不变,其余各个位取反

经过上面的定义,我们再来进行加减法:

1+(-1) = 0001 + 1110 = 1111 => -0 (直接相加,这样就简单多了!)

思考:1111代表-0,0000代表+0,在我们实数的范围内,0有正负之分吗?0既不是正数也不是负数,那么显然这样的表示依然不够合理!根据上面的问题,我们引入了最终的解决方案,那就是补码,定义如下:

  • 正数的补码就是其本身 (不变!)
  • 负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1(即在反码的基础上+1,此时1000表示-8)
  • 对补码再求一次补码就可得该补码对应的原码。

比如-7原码为1111,反码为1000,补码就是1001了,-6原码为1110,反码为1001,补码就是1010。所以在补码下,原本的1000就作为新增的最小值-8存在。

所以现在就已经能够想通,-0已经被消除了!我们再来看上面的运算:

1+(-1) = 0001 + 1111 = (1)0000 => +0 (现在无论你怎么算,也不会有-0了!)

所以现在,1111代表的不再是-0,而是-1,相应的,由于消除-0,负数多出来一个可以表示的数(1000拿去表示-8了),那么此时4bit位能够表示的范围是:-8~+7(Kotlin使用的就是补码!)在了解了计算机底层的数据表示形式之后,我们再来学习这些基本数据类型就会很轻松了。

变量的声明与使用

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

var [变量名称] : [数据类型]

这里的数据类型我们会在下节课开始逐步讲解,比如整数就是Int类型,不同类型的变量可以存储不同的类型的值。后面的变量名称顾名思义,就像x一样,这个名称我们可以随便起一个,但是注意要满足以下要求:

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

当然各位小伙伴没必要刻意去进行记忆有哪些关键字,我们会在学习的过程中逐步认识到这些关键字。新手要辨别一个单词是否为关键字,只需要通过IDEA的高亮颜色进行区分即可,比如:

image-20230729021646779

深色模式下,关键字会高亮为橙色,浅色模式下会高亮为深蓝色,普通的代码都是正常的灰白色。

比如现在我们想要定义一个整数(Int)类型的变量a,那么就可以这样编写:

fun main() {
    var a : Int
}

但是这个变量一开始没有任何值,比如现在我们要让这个变量表示10,那么就可以将10赋值给这个变量:

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

不过由于变量在一开始就被赋值为10这个整数,此时类型是确定的,Kotlin的编译器非常聪明,它支持自动推断类型,这里会自动将变量a的类型推断为Int类型,我们可以直接省略掉后面的Int类型:

fun main() {
    var a = 10
}

或者我们可以在使用时再对其进行赋值:

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

是不是感觉跟数学差不多?这种写法对于我们人来说,实际上是很好理解的,意思表达很清晰。为了更直观地查看变量的值,我们可以直接将变量的值也给打印到控制台:

fun main() {
    var a = 10
    println(a)
}

image-20230729130856235

变量的值也可以在中途进行修改:

fun main() {
    var a = 666
    a = 777
    println(a)   //这里打印得到的就是777
}

变量的值也可以直接指定为其他变量的值:

fun main() {
    var a = 10
    var b = a //直接让b等于a,那么a的值就会给到b
    println(b) //这里输出的就是10了
}

我们还可以让变量与数值之间做加减法(运算符会在后面详细介绍):

fun main() {
    var a = 9   //a初始值为9
    a = a + 1   //a = a + 1也就是将a+1的结果赋值给a,跟数学是一样的,很好理解对吧
    println(a)  //最后得到的结果就是10了
}

对于那些只读的变量,我们可以将其表示为一个常量,使用val关键字:

fun main() {
    val a = 666 //使用val关键字,表示这是一个常量
    a = 777;    //常量的值不允许发生修改
}

编译时得到报错:

image-20230729142023779

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

fun main() {
    val a: Int
    a = 777;
}

至此,声明变量和常量我们就介绍完毕了,下一部分我们将介绍常见的一些数据类型。

Screenshot 2025-03-21 at 09.44.39

数字类型介绍

前面我们了解了如何创建变量,并进行使用,但是我们知道,不同的数据往往对应着不同的类型,比如整数我们使用的就是Int,而这一部分我们将学习更多的基本数据类型。

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)

为什么不同的数据类型有着值范围呢?这是因为我们的计算机底层是采用0和1表示数据的,并且数据的表示位数有限,我们以二进制来计算,就像下面这样:

1 + 1 = 10

可能很多小伙伴会好奇,为什么1 + 1得到的结果是数字十?这是因为二进制中只有0和1,因此只要满二就进一,所以就变成这样的结果了,如果各位是初次学习,可能会不太好理解。

这里以上面的8位大小的Byte类型为例,在计算机底层存储数据时,只有8个bit位(一个bit位就可以表示一个0或1)来存储它,那么它能表示的最大值和最小值就是:

00000000 ~ 11111111 转换为十进制就是 0 ~ 255

不过为了能够表示负数,计算机一般使用补码进行表示,所以,上面的最小值和最大值就变成了-128 ~ 127了。

默认情况下,我们使用的常量数字都是Int类型,除非它的大小已经超出Int类型能够表示的最大范围,在超出Int类型可以表示的最大范围之后,默认为Long类型:

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

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

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

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

val a = 1_000_000_000

数字类型不仅可以写成十进制,也可以以十六进制或是二进制表示(Kotlin不支持八进制表示)只需要添加对应的前缀即可,比如一个十六进制数据:

val a = 0xAF

因为十六进制中大于等于十的数据没有对应的阿拉伯数字可以表示,所以在计算机中就以ABCDEF来替代这无法表示的6个数字。并且我们需要在数字前面添加0x表示这是16进制的数字,接下来是2进制:

val a = 0b1001   //0b前缀表示二进制数据,后面的1010对应着十进制的9

除了整数类型外,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
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类型:
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类型:

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

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

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

与其他一些语言不同,Kotlin中的数字类型没有隐式转换的操作,例如,一个Double类型的变量无法将其值赋值给Int类型变量:

image-20230729211441090

如果需要将一个整数转换为小数,我们会在后面学习函数之后再给各位小伙伴讲解如何调用函数进行显示类型转换。

数字类型的运算

Kotlin支持数学上标准的算术运算集,例如:+-*/% 并且这些运算符都是通过运算符重载实现的具体功能,我们会在后续的章节中讲解Kotlin的运算符重载机制,这里各位小伙伴就当做是普通的运算操作即可。

Kotlin支持运算符重载,运算符重载是一种允许程序员重新定义运算符的语言特性,通过运算符重载,您可以为自定义的类或数据类型定义一些特定操作的行为。

其中加减乘除操作这里就不做介绍了,而%符号用于取余操作,也就是计算前面的数整除后面的数得到的余数:

println(1 + 2)   //计算1加上2的结果
println(2_500_000_000L - 1L)   //计算2500000000减去1的结果
println(3.14 * 2.71)   //计算3.14与2.71的乘积
println(10.0 / 3)   //计算10除以3得到的结果
println(10 / 3)   //10除以3得到的余数为1

以上运算都比较简单,但是注意在除法运算中,只有两个操作数中出现小数,除法的结果才是小数,如果两个操作数都是整数,那么得到的结果也是整数,并且直接丢失小数位(不会四舍五入)

println(5 / 2)    //结果是2,而不是2.5

同样的,除了直接使用字面量来进行运算,我们也可以将定义的变量参与到运算中:

fun main() {
    val a = 10
    println(a / 2)
}

注意,在Kotlin中不同的算数运算符,它们的优先级也不一样:

println(1 + 2 * 3)

在数学中,乘法运算的优先级比加法运算更高,因此我们需要先计算乘法,再计算加法,而在Kotlin中是一样的,乘法和除法运算符的优先级是高于加法运算符的,所以说上面算出来的结果是7,同样的,我们数学中使用括号来提升某些运算的优先级,在Kotlin中同样可以,比如:

println((1 + 1) * 3)   //使用小括号来强制提升优先级

有些时候,我们可能想要让某个变量的值增加一定数值,比如下面这样:

var a = 10
a = a + 9   //让a等于a+9的结果

对于这种让变量本身加减乘除某个值的情况,可以使用赋值运算符简化:

a += 9   //等价于 a = a + 9
a /= 9   //等价于 a = a / 9
a %= 2   //等价于 a = a % 2

如果我们只是希望某个变量自增或自减1,那么我们可以像这样去写:

fun main() {
    var a = 10
    a++    //使用两个++表示自增1
    println(a)     //打印得到11
    a--    //使用两个--表示自减1
}

不过,这个双++符号,可以放在变量的前后,都能实现自增操作:

var a = 10
++a   //最终效果等价于a++

但是他们有一个本质区别,就是++在前面,a是先自增再得到结果,而++在后面,是a先得到结果,再进行自增,比如:

fun main() {
    var a = 10
    println(a++)   //这里++在后面,打印a的值依然是10,但是结束之后a的值就变成11了
    println(++a)   //这里++在前面,打印a的值是这里先自增之后的结果,就是12了
}

对于新手来说,这个很容易搞混,所以说一定要记清楚。

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

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

这里我们从按位与开始讲解,比如下面的两个数:

fun main() {
    val a = 9
    val b = 3
    val c = a and b //进行按位与运算
    println(c)
}

按位与实际上就是让这两个数每一位都进行比较,如果这一位两个数都是1,那么结果就是1,否则就是0:

  • a = 9 = 1001
  • b = 3 = 0011
  • c = 1 = 0001(因为只有最后一位,两个数都是1,所以说结果最后一位是1,其他都是0)

同样的,按位或,其实就是只要任意一个为1(不能同时为0)那么结果就是1:

fun main() {
    val a = 9
    val b = 3
    val c = a or b
    println(c)
}
  • a = 9 = 1001
  • b = 3 = 0011
  • c =11= 1011(只要上下有一个是1或者都是1,那结果就是1)

按位异或的意思就是只有两边不相同的情况下,结果才是1,也就是说一边是1一边是0的情况:

  • a = 9 = 1001
  • b = 3 = 0011
  • c =10= 1010(从左往右第二位、第四位要么两个都是0,要么两个都是1,所以说结果为0)

按位取反操作跟前面的正负号一样,只操作一个数,最好理解,如果这一位上是1,变成0,如果是0,变成1:

  • 127 = 01111111
  • -128 = 10000000

所以说计算的结果就是-128了。

除了以上的四个运算符之外,还有位移运算符,比如:

fun main() {
    val c = 1 shl 2 //shl表示左移运算
    println(c)
}
  • 1 = 00000001
  • 4 = 00000100(左移两位之后,1跑到前面去了,尾部使用0填充,此时就是4)

我们发现,左移操作每进行一次,结果就会x2,所以说,除了直接使用*进行乘2的运算之外,我们也可以使用左移操作来完成。

同样的,右移操作就是向右移动每一位咯:

fun main() {
    val c = 8 shr 2  //shr表示右移运算
    println(c)
}

跟上面一样,右移操作可以快速进行除以2的计算。对于负数来说,左移和右移操作不会改变其符号位上的数字,符号位不受位移操作影响:

fun main() {
    val c = -8 shr 2   //这里得到的依然是个负数
    println(c)
}

我们也可以使用考虑符号位的右移操作,一旦考虑符号位,那么符号会被移动:

fun main() {
    val c = -1 ushr 1 //无符号右移是ushr,移动会直接考虑符号位
    println(c)
}

比如:

  • -1 = 11111111 11111111 11111111 11111111
  • 右移: 01111111 11111111 11111111 11111111(无符号右移使用0填充高位)

此时得到的结果就是正数的最大值 2147483647 了,注意,不存在无符号左移操作。

最后我们再总结一下不同运算符的优先级,对应的优先级从上往下依次减弱:

  1. 一元运算符:例如 ++、--、+、-、!、~
  2. 乘法和除法运算符:*、/、%
  3. 加法和减法运算符:+、-
  4. 位移运算符:shl、shr、ushr
  5. 按位与运算符:and
  6. 按位或运算符:or
  7. 按位异或运算符:xor
  8. 逻辑运算符:&&、||
  9. 比较运算符:>、>=、<、<=、==、!=
  10. 区间运算符:..
  11. 赋值运算符:=、+=、-=、*=、/=、%=

当然,这里列出的部分运算符各位小伙伴可能还没有遇到,不过在后续的学习中,我们会慢慢认识的,届时各位小伙伴可以回顾一下这里。

布尔类型介绍

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

  • true - 真
  • false - 假

布尔类型(boolean)只有truefalse两种值,也就是要么为真,要么为假,布尔类型的变量通常用作流程控制判断语句(不同于C语言,C语言中一般使用0表示false,除0以外的所有数都表示true)

val a: Boolean = true

如果给一个其他的值,会无法编译通过:

image-20230729214712431

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

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

比如我们想判断变量a和变量b的值是否相同:

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
}

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

fun main() {
    val a = 10
    println(a in 1..10)   //这里1..10表示1~10这个闭区间,使用in关键字来进行判断
    println(a in 1..<10)   //这里1..<10表示1~10这个前闭后开区间,使用in关键字来进行判断
    println(a !in 1..10)   //相反的,使用!in判断是否不在这个区间
}

对于Boolean类型的变量之间,也有一些逻辑运算符用于进行组合条件判断:

  • ||– 逻辑或运算
  • &&– 逻辑与运算
  • !– 取反运算

其中取反运算最好理解,它可以让true变成false,false变为true,比如:

fun main() {
    val a = 10
    val b = 20
    val c = a > b   //这里很明显c应该为false
    println(!c)   //这里进行了取反操作并打印,那么结果就是true了
}

对于逻辑与和逻辑或运算,我们可以像这样去使用:

fun main() {
    val a = 10
    val b = 0
    println(100 >= a && b >= 60)  //我们可以使用与运算符连接两个判断表达式,只有两边都为true结果才是true
    println(100 >= a || b >= 60)  //我们可以使用或运算符连接两个判断表达式,只要两边任意一个为true结果就是true
}

与运算符要求左右两边同时为真,得到的结果才是真,否则一律为假,而或运算就是要求两边只要有一边为真,结果就是真,除非两边同时为false,那么就没戏了。

不过需要注意的是,在与运算中,第一个判断表达式得到了false之后,此时不会再继续运行第二个表达式,而是直接得到结果false(逻辑运算符会出现短路的情况,只要第一个不是真,就算第二个是真也不可能了,所以说为了效率,后续就不用再判断了,在使用时一定要注意这一点)同样的,或运算下当发现第一个判断表达式为true时,也不会继续向后执行了,因为结果已经是顶真了。

字符类型介绍

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

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

注意,字符只能表示一单个字符,我们之前遇到的字符串跟字符不一样,关于字符串我们会在下节课进行介绍。

我们打印出来的也是单个字符:

image-20230729233735560

那么可能会有小伙伴好奇,字符类型在计算机底层是怎么进行存储的呢?实际上每个字符在计算机中都会对应一个字符码,首先我们需要介绍ASCII码:

img

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

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

得到结果为:

image-20230729233949424

字符型占据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就能表示几乎所有国家语言的字符,这样就很方便了。

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

image-20230730000657951

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

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

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

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

这些转义字符都是为了防止在特殊情况下无法表示某些字符,而给我们的替代方案,后续各位小伙伴在使用时可以回来参考一下。

字符串类型介绍

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

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

val str: String = "Hello World"

注意,字符串中的字符一旦确定,无法进行修改,只能重新创建。

如果我们需要再字符串中换行,需要用到转义字符,字符串中同样支持使用转义字符:

fun main() {
    val text = "Hello\nWorld"
    println(text)
}

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

fun main() {
    val text = """
    这是第一行
    这第二行
    别\n了,没用
    真牛逼啊,这功能隔壁Java15才有
    """
    println(text)
}

效果如下:

image-20230730002653406

可以看到确实是够原始的,把我代码里面的缩进都给打印出来了,这样肯定不是我们希望的样子,我们希望的仅仅是一个简单换行而已,那这里该怎么去处理呢?后面我们在讲解函数之后,会额外补充这里的内容。

有时候为了方便,我们可以将不同的字符串拼接使用:

fun main() {
    val str1 = "Hello"
    val str2 = "World"
    val str = str1 + str2
    println(str)   //使用 + 来拼接两个字符串,得到的结果就是两个字符串合在一起的结果
}

字符串除了和字符串拼接之外,也可以和其他类型进行拼接:

fun main() {
    val a = 10
    val text = "这是拼接的值" + a
    println(text)   //打印出来就是与其他类型的拼接结果
}

但是我们需要注意字符串拼接的顺序,只能由字符串拼接其他类型,如果是其他类型拼接字符串,可能会出现问题:

image-20230730003158613

但是现在我们就是希望其他类型的数据拼在最前面,这里应该怎么做呢?我们可以使用字符串模版来完成:

fun main() {
    val a = 10
    val text = "这是拼接的值$a"  //这里的$为模版表达式,可以直接将后面跟着的变量或表达式以字符串形式替换到这个位置
    println(text)
}

如果要添加到前面:

val text = "$a 这是拼接的值" //注意这里$a之后必须空格,否则会把后面的整个字符串认为这个变量的名字

出现这种情况除了用空格去解决之外,我们也可以添加一个花括号:

val text = "${a}这是拼接的值"  //添加花括号就可以消除歧义了
val text = "${a > 0}这是拼接的值"  //花括号中也可以写成表达式

由于美元符用于模版表达式了,所以说如果我们希望在字符串中仅仅表示$这个字符,那么我们需要用到转义:

val text = "\$这是美元符"   //普通字符串直接使用\$表示
//原始字符串要套个娃
val str = """
  ${'$'}这是美元符    
    """

至此,关于Kotlin的变量与基本类型的内容我们就暂时告一段落了,不过在后面学习了更多知识后,我们还会回顾这些基本类型,了解他们的更多用法,并且认识我们唯一没有在这一部分介绍的数组类型。


流程控制

经过前面的学习,我们知道,程序都是从上往下依次运行的,但是,仅仅是这样还不够,我们需要更加高级的控制语句来使得程序更加有趣。比如,判断一个整数变量,大于1则输出yes,小于1则输出no,这时我们就需要用到选择结构来帮助我们完成条件的判断和程序的分支走向。

在前面我们介绍了运算符,我们可以通过逻辑运算符和关系运算符对某些条件进行判断,并得到真或是假的结果。这一部分我们将继续使用这些运算符进行各种判断,以及实现流程控制。

选择结构(if-else)

某些时候,我们希望进行判断,只有在条件为真时,才执行某些代码,这种情况就需要使用到选择分支语句,首先我们来认识一下if语句:

if (条件判断) 判断成功执行的代码;

if的小括号中需要我们传入一个Boolean类型的结果,可以是一个Boolean变量,也可以是一个判断语句,反正只能接受true和false两种结果,比如下面的这个例子:

fun main() {
    val a = 10
    if(a == 12)  //只有当a判断等于12时,才会执行下面的打印语句
        println("Hello World!")
    println("我是后续的语句")  //if只会对紧跟着的一行代码生效,后续的内容无效
}

if会进行判断,只有判断成功时才会执行紧跟着的语句,否则会直接跳过,注意,如果我们想要在if中执行多行代码,需要使用代码块将这些代码囊括起来(实际上代码块就是将多条语句复合到一起,使用花括号囊括)所以说,我们以后使用if时,如果分支中有多行代码需要执行,就需要添加花括号,如果只有一行代码,花括号可以直接省略,包括我们后面会讲到的else、while、for语句都是这样的,就像下面这样:

fun main() {
    val a = 15
    if (a > 10) {    //只有判断成功时,才会执行下面的代码块中内容,否则直接跳过
        println("a大于10")
        println("a的值为:$a")
    }
    println("我是外层")
}

如果我们希望判断条件为真时执行某些代码,条件为假时执行另一些代码,我们可以在后面继续添加else语句:

fun main() {
    val a = 15
    if (a > 10) {    //只有判断成功时,才会执行下面的代码块中内容,否则直接跳过
        println("a大于10")
        println("a的值为:$a")
    } else {   //当判断不成功时,会执行else代码块中的代码
        println("a小于10")
        println("a的值为:$a")
    }
    println("我是外层")
}

if-else语句就像两个分支,跟据不同的判断情况从而决定下一步该做什么,这跟我们之前认识的三元运算符性质比较类似。

那如果此时我们需要判断多个分支呢?比如我们现在希望判断学生的成绩,不同分数段打印的等级不一样,比如90以上就是优秀,70以上就是良好,60以上是及格,其他的都是不及格,那么这种我们又该如何判断呢?要像这样进行连续判断,我们需要使用else-if来完成:

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

当然,if分支语句还支持嵌套使用,比如我们现在希望低于60分的同学需要补习,0-30分需要补Java,30-60分需要补C++,这时我们就需要用到嵌套:

fun main() {
    val score = 2
    if (score < 60) {   //先判断不及格
        if (score > 30) //在内层再嵌套一个if语句进行进一步的判断
            println("学习C++") 
        else 
            println("学习Java")
    }
}

除了if自己可以进行嵌套使用之外,其他流程控制语句同样可以嵌套使用,也可以与其他流程控制语句混合嵌套使用。这样,我们就可以灵活地使用if来进行各种条件判断了。

除了直接执行语句之外,我们也可以将if和else用作结果判断,比如:

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

这类似于其他语言,如Java和C中的三元运算,不过Kotlin中没有那样的三元运算符,只能使用上面的表达式,对于多行代码块的情况,默认最后一行作为返回的结果:

fun main() {
    val score = 2
    val res = if (score > 60) {
        println("不错啊期末没挂科")
        "Yes"   //代码块默认最后一行作为返回结果
    } else {
        println("不会有人Java期末还要挂科吧")
        "No"
    }
}

注意,如果需要这种返回结果的表达式,那么必须要存在else分支,否则不满足条件岂不是没结果了?

选择结构(when)

前面我们介绍了if语句,我们可以通过一个if语句轻松地进行条件判断,然后根据对应的条件,来执行不同的逻辑,当然除了这种方式之外,我们也可以使用when语句来实现,它更适用于多分支的情况:

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

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

比如现在我们要根据学生的等级进行分班,学生有ABC三个等级:

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

如果将when用作表达式,则else分支必须存在,除非编译器能推断出所有可能的情况都包含分支条件,比如下面的例子:

fun main() {
    val c = 'A'
    val numericValue = when (c) {
        'B' -> 0
        'A' -> 1
        else -> 2    //还有其他情况,这里必须添加else,不然其他情况岂不是没返回的东西?
    }
}

以下情况就可以不需要else语句:

fun main() {
    val c = true
    val numericValue = when (c) {
        false -> 0
        true -> 1
        // 由于Boolean只具备真和假条件,这里的'else' 就不再强制要求
        // 这同样适用于比如枚举类等
    }
}

when语句中,遇到以下情况,携带else分支是必须的:

  • when分支中仅有一个Boolean类型、枚举 或 密封,以及用于判断的目标变量是可空的情况(后面会讲解)
  • when分支没有包括该判断目标的所有可能的值。

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

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

我们也可以使用任意表达式(不仅仅是常量)作为分支条件,比如之前的if-else案例中我们判断学生成绩:

fun main() {
    val score = 10
    val grade = when(score) {
        //使用in判断目标变量值是否在指定范围内
        in 100..90 -> "优秀"
        in 89..80 -> "良好"
        in 79..70 -> "及格"
        in 69..60 -> "牛逼"
        else -> "不及格"
    }
}

包括我们之后学习的类型判断is表达式、函数调用等,都可以在这里作为分支条件。

循环结构(for)

通过前面的学习,我们了解了如何使用分支语句来根据不同的条件执行不同的代码,我们接着来看第二种重要的流程控制语句:循环语句。

我们在某些时候,可能需要批量执行某些代码:

fun main() {
    println("大烟杆嘴里塞,我只抽第五代")   //把这句话给我打印三遍
    println("大烟杆嘴里塞,我只抽第五代")
    println("大烟杆嘴里塞,我只抽第五代")
}

遇到这种情况,我们由于还没学习循环语句,那么就只能写N次来实现这样的多次执行。但是如果此时要求我们将一句话打印100遍、1000遍、10000遍,那么我们岂不是光CV代码就要搞一下午?

现在,要解决这种问题,我们可以使用for循环语句来多次执行:

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

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

  • 数组
  • 区间
  • 任何实现了运算符重载函数iterator的类

这里我们只学习了区间,我们来看看如何使用,比如我们要打印一段话3遍:

fun main() {
    for (i in 1..3)  //这里直接写入1..3表示1~3这个区间
        println("大烟杆嘴里塞,我只抽第五代:$i")
}

打印结果为:

image-20231216151835790

可以看到,每一次遍历出来的变量i,其实就是每次遍历的下一个目标,比如这里是1..3的区间,那么得到的依次就是1、2、3这三个结果了,唯一需要注意的是,这里的i是局部的,只在for循环内部可用(包括嵌套的内部)并不是整个main中都可以使用:

image-20230730160547655

默认情况下,每一轮循环都会向后+1,我们也可以自由控制每一轮增加多少,也就是步长:

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

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

image-20230801014238248

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

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

我们可以使用调试来观察每一轮的变化,调试模式跟普通的运行一样,也会执行我们的Java程序,但是我们可以添加断点,也就是说当代码运行到断点位置时,会在这里暂停,我们可以观察当代码执行到这个位置时各个变量的值:

image-20230730152627331

调试模式在我们后面的学习中非常重要,影响深远,所以说各位小伙伴一定要学会。调试也很简单,我们只需要点击右上角的调试选项即可(图标像一个小虫子一样,因为调试的英文名称是Debug)

image-20230730152438291

调试开始时,我们可以看到程序在断点位置暂停了:

image-20230730152411984

此时我们可以观察到当前的变量i的值,也可以直接在下方的调试窗口中查看:

image-20230730152653140

随着循环的进行,i的值也会逐渐自增。

和之前的if一样,for循环同样支持嵌套使用:

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

上面的代码中,外层循环会执行3轮,而整个循环体又是一个循环语句,那么也就是说,每一轮循环都会执行里面的整个循环,里面的整个循环会执行3,那么总共就会执行3 x 3次,也就是9次打印语句。

我们也可以在循环过程中提前终止或是加速循环的进行,这里我们需要认识两个新的关键字:

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

我们可以使用continue关键字来跳过本轮循环,直接开启下一轮。这里的跳过是指,循环体中,无论后面有没有未执行的代码,一律不执行,比如上面的判断如果成功,那么将执行continue进行跳过,虽然后面还有打印语句,但是不会再去执行了,而是直接结束当前循环,开启下一轮。

在某些情况下,我们可能希望提前结束循环:

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

我们可以使用break关键字来提前终止整个循环,和上面一样,本轮循环中无论后续还有没有未执行的代码,都不会执行了,而是直接结束整个循环,跳出到循环外部。

虽然使用break和continue关键字能够更方便的控制循环,但是注意在多重循环嵌套下,它只对离它最近的循环生效(就近原则):

fun main() {
    for (i in 1..3) {
        for (j in 1..3) {
            if (i == j) continue  //当i == j时加速循环
            println("$i, $j")
        }
    }
}

这里的continue加速的对象并不是外层的for,而是离它最近的内层for循环,break也是同样的规则:

fun main() {
    for (i in 1..3) {
        for (j in 1..3) {
            if (i == j) break //当i == j时终止循环
            println("$i, $j")
        }
    }
}

那么,要是我们就是想要终止或者是加速外层循环呢?我们可以为循环语句打上标记:

fun main() {
    outer@ for (i in 1..3) {   //在循环语句前,添加 标签@ 来进行标记
        inner@ for (j in 1..3) {
            if (i == j) break@outer  //break后紧跟要结束的循环标记,当i == j时终止外层循环
            println("$i, $j")
        }
    }
}

关于for语句的更多用法,我们会在后续的学习中继续认识。

循环结构(while)

前面我们介绍了for循环语句,我们接着来看第二种while循环,for循环要求我们给一个可遍历的目标,而while相当于是一个简化版本,它只需要我们填写循环的维持条件即可,比如:

while(循环条件) 循环体;

相比for循环,while循环更多的用在不明确具体的结束时机的情况下,而for循环更多用于明确知道循环的情况,比如我们现在明确要进行循环10次,此时用for循环会更加合适一些,又比如我们现在只知道当i大于10时需要结束循环,但是i在循环多少次之后才不满足循环条件我们并不知道,此时使用while就比较合适了。

fun main() {
    var i = 100 //比如现在我们想看看i不断除以2得到的结果会是什么,但是循环次数我们并不明确

    while (i > 0) {   //现在唯一知道的是循环条件,只要大于0那么就可以继续除
        println(i)
        i /= 2 //每次循环都除以2
    }
}

上面的这种情况就非常适合使用while循环。

和for循环一样,while也支持使用break和continue来进行循环的控制,以及嵌套使用:

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

我们也可以反转循环判断的时机,可以先执行循环内容,然后再做循环条件判断,这里要用到do-while语句:

fun main() {
    var i = 0 //比如现在我们想看看i不断除以2得到的结果会是什么,但是循环次数我们并不明确

    do {  //无论满不满足循环条件,先执行循环体里面的内容
        println("Hello World!")
        i++
    } while (i < 10) //再做判断,如果判断成功,开启下一轮循环,否则结束
}

至此,Kotlin程序设计的基础篇内容就讲解完毕了,下一章我们将学习更多Kotlin特性。