什么是变量
变量,指值可以变的量。变量以非数字的符号来表达,一般用拉丁字母。变量的用处在于能一般化描述指令的方式。结果只能使用真实的值,指令只能应用于某些情况下。变量能够作为某特定种类的值中任何一个的保留器。
比如一个公式 x + 2 = 6 此时 x
就是一个变量,变量往往代表着某个值,比如这里的 x
就代表的是4这个值。在Kotlin中,我们也可以让变量去代表一个具体的值,并且变量的值是可以发生变化的,在程序中,我们也可以使用变量,并且变量具有类型。
变量的声明与使用
声明变量
要声明一个变量,我们需要使用以下格式:
var [变量名称] : [数据类型]
或
```kotlin
val [变量名称] : [数据类型]
- var/val
var
定义的是可变变量,val
定义的是不可变变量。 - 数据类型
就是数据的类型,比如整数就是Int
类型,不同类型的变量可以存储不同的类型的值。 - 变量名称
顾名思义,就像x
一样,这个名称我们可以随便起一个,但是注意要满足以下要求:- 标识符可以由大小写字母、数字、下划线(_)和美元符号($)组成,但是不能以数字开头。
- 变量不能重复定义,大小写敏感,比如A和a就是两个不同的变量。
- 不能有空格、@、#、+、-、/ 等符号。
- 应该使用有意义的名称,达到见名知意的目的(一般我们采用英文单词),最好以小写字母开头。
- 不可以是 true 和 false。
- 不能与Kotlin语言的关键字或是基本数据类型重名
使用变量
var定义变量
现在我们想要定义一个整数(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 = 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定义常量
对于那些只读的变量,我们可以将其表示为一个常量,使用 val
关键字:
fun main() {
val a = 666 //使用val关键字,表示这是一个常量,常量的值不允许发生修改
}
如果修改val定义的变量,编译时会报错:
```kotlin
fun main() {
val a = 666
a = 777;
}
val也可以先定义变量不赋值,然后再赋值
fun main() {
val a: Int
a = 777;
}
总之,常量只能赋一次值,其他任何情况下都不能修改。
数据类型
前面我们了解了如何创建变量,并进行使用,但是我们知道,不同的数据往往对应着不同的类型,比如整数我们使用的就是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) |
默认情况下,我们使用的常量数字都是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进制的数字
二进制
val a = 0b1001 //0b前缀表示二进制数据,后面的1010对应着十进制的9
无符号整数类型
除了整数类型外,Kotlin还为无符号整数提供以下类型:
UByte
:一个无符号8位整数,范围从0到255UShort
:无符号16位整数,范围从0到65535UInt
:一个无符号32位整数,范围从0到2^32 - 1ULong
:一个无符号64位整数,范围从0到2^64 - 1
为了使无符号整数更易于使用,Kotlin同样提供了用后缀标记,该后缀表示无符号类型(类似于上面的 Long
类型添加L字母)
-
使用
u
或U
字母作为后缀表示无符号整数。而具体的类型是根据前面变量的类型确定的,如果变量没有提供类型,编译器将根据数字的大小使用UInt
或ULong
: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类型,根据数字大小自动推断得到
-
uL
和UL
可以将文字直接标记为无符号Long类型:val a = 1UL // ULong类型,直接使用后缀标记
浮点数类型
对于小数来说,Kotlin提供符合IEEE 754标准的浮点类型 Float
和 Double
,Float
为IEEE 754标准中的单精度数据,而Double位标准中的双精度数据,对于单双精度,本质上就是能够表示的小数位精度,双精度比单精度的小数精度更高。
这些类型的大小不同,并为不同精度的浮点数提供存储:
类型 | 大小(位) | 符号与尾数位数 | 阶码位数 | 小数位数 |
---|---|---|---|---|
Float |
32 | 24 | 8 | 6-7 |
Double |
64 | 53 | 11 | 15-16 |
我们也可以直接创建小数类型的 Double
和 Float
变量,小数部分与整数部分由一个小数点(.
)隔开,编译器默认情况下会将所有的小数自动推断为推断 Double
类型:
val pi = 3.1415 // 默认推断为Double类型
val one: Double = 1 // 这种写法是错误的,因为1不是小数,无法编译通过
val one: Double = 1.0 // 但是这种写法就是对的,因为这样表示就是小数,即使小数位是0
由于默认是Double类型,如果我们要明确指定值为 Float
类型,那么需要添加后缀 f
或 F
,并且由于精度问题,如果该值包含超过6-7位小数,则会丢失一部分精度:
val e = 2.7182818284 // Double类型的数值
val e: Float = 2.7182818284f // 这里表示为Float会导致精度折损,得到2.7182817
与其他一些语言不同,Kotlin中的数字类型没有隐式转换的操作,例如,一个 Double
类型的变量无法将其值赋值给 Int
类型变量:
如果需要将一个整数转换为小数,我们需要使用对应的函数进行显示类型转换。
数字类型的运算
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提供了一组整数的位运算操作,可以直接在二进制层面上与数字表示的位进行操作,不过只适用于 Int
和 Long
类型的数据:
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 了,注意,不存在无符号左移操作。
最后我们再总结一下不同运算符的优先级,对应的优先级从上往下依次减弱:
- 一元运算符:例如 ++、--、+、-、!、~
- 乘法和除法运算符:*、/、%
- 加法和减法运算符:+、-
- 位移运算符:shl、shr、ushr
- 按位与运算符:and
- 按位或运算符:or
- 按位异或运算符:xor
- 逻辑运算符:&&、||
- 比较运算符:>、>=、<、<=、==、!=
- 区间运算符:..
- 赋值运算符:=、+=、-=、*=、/=、%=
布尔类型
布尔类型是Kotlin中的一个比较特殊的类型,它并不是存放数字的,而是状态,它有下面的两个状态:
- true - 真
- false - 假
布尔类型(boolean)只有 true
和 false
两种值,也就是要么为真,要么为假,布尔类型的变量通常用作流程控制判断语句(不同于C语言,C语言中一般使用0表示false,除0以外的所有数都表示true)
val a: Boolean = true
如果给一个其他的值,会无法编译通过:
布尔值除了可以直接赋值得到,也可以通过一些关系运算得到,常见的关系运算有大于、小于以及等于,所有的关系运算在下方:
- 判断两个数是否相等:
a == b
和a != b
- 判断数之间大小:
a < b
,a > b
,a <= b
,a >= b
- 判断数是否在指定范围中:
a..b
,x in a..b
,x !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)
注意,字符只能表示一单个字符,我们之前遇到的字符串跟字符不一样,关于字符串我们会在下节课进行介绍。
我们打印出来的也是单个字符:
那么可能会有小伙伴好奇,字符类型在计算机底层是怎么进行存储的呢?实际上每个字符在计算机中都会对应一个字符码,首先我们需要介绍ASCII码:
比如我们的英文字母 A
要展示出来,那就是一个字符的形式,而其对应的ASCII码值为65,我们可以使用 .code
来获取某个字符对应的ASCII码,比如下面这样:
fun main() {
val c: Char = 'A'
println(c.code) //这里就会打印字符对应的ASCII码
}
得到结果为:
字符型占据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就能表示几乎所有国家语言的字符,这样就很方便了。
接着我们来介绍一下转译字符,对于一些我们平时很难直接通过键盘或是输入法打出来的字符,比如一些特殊符号:
这些符号我们没办法直接打出来,但是现在我们又想要表示它们,该怎么做呢?我们可以使用转义来将这些字符对应的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)
}
效果如下:
可以看到确实是够原始的,把我代码里面的缩进都给打印出来了,这样肯定不是我们希望的样子,我们希望的仅仅是一个简单换行而已,那这里该怎么去处理呢?后面我们在讲解函数之后,会额外补充这里的内容。
有时候为了方便,我们可以将不同的字符串拼接使用:
fun main() {
val str1 = "Hello"
val str2 = "World"
val str = str1 + str2
println(str) //使用 + 来拼接两个字符串,得到的结果就是两个字符串合在一起的结果
}
字符串除了和字符串拼接之外,也可以和其他类型进行拼接:
fun main() {
val a = 10
val text = "这是拼接的值" + a
println(text) //打印出来就是与其他类型的拼接结果
}
但是我们需要注意字符串拼接的顺序,只能由字符串拼接其他类型,如果是其他类型拼接字符串,可能会出现问题:
但是现在我们就是希望其他类型的数据拼在最前面,这里应该怎么做呢?我们可以使用字符串模版来完成:
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 = """
${'$'}这是美元符
"""
评论区