2.4 常量和变量
Swift语言中的基本数据类型,按其取值可以分为常量和变量两种。在程序执行过程中,其值不发生改变的量被称为常量,其值可变的量被称为变量。两者可以和数据类型结合起来进行分类,例如可以分为整型常量、整型变量、浮点常量、浮点变量、字符常量、字符变量、枚举常量、枚举变量。在C语言程序中,常量是可以不经说明而被直接引用的,而变量则必须先定义后使用。在本节的内容中,将对常量和变量的知识进行深入讲解。
2.4.1 声明常量
不管是什么语言,变量都必须确定数据类型,否则不能存储数据。但不同的语言,获取数据类型的方式是有区别的。例如,对于静态语言(Java、C#等),必须在定义变量时指定其数据类型。当然,为了让这个变量什么类型的值都能存储,也可以将变量类型设置为Object或相似的类型。但不管设置为什么,类型是必须要指定一个的。所以,静态语言变量的数据类型是在编译时确定的。
在Swift语言中,在使用常量和变量前必须声明,使用关键字“let”来声明常量,使用关键字“var”来声明变量。
在Swift语言中,使用关键字let声明常量,具体语法格式如下所示。
let name=value
(1)name:表示常量的名字,在命名时建议遵循如下规则。
变量名首字母必须为字母(小写a-z,大写A-Z)或下画线“_”开始;
常量名之间不能包含空格。
(2)value:常量的值。
例如在下面的演示代码中,展示了用常量和变量来记录用户尝试登录次数的方法。
let mm=10
var nn=0
在上述代码中,声明一个名为“mm”的新常量,并且将其赋值为10。然后,声明一个名为“nn”的变量,并且将其值初始化为0。这样,允许的最大尝试登录次数被声明为一个常量,因为这个值不会改变。当前尝试登录次数被声明为一个变量,因为每次尝试登录失败时都需要增加这个值。
对于常量来说,不管指定不指定数据类型,都必须进行初始化。例如,下面两条语句都是不合法的。
let const1 //不合法,常量必须初始化
let const2:Int //不合法,常量必须初始化
要想定义一个合法的常量,必须使用下面的形式。
let const3:Int=20 //合法的常量定义(指定数据类型)
let const3=20 //合法的常量定义(不指定数据类型,动态推导)
实例文件main.swift的具体实现代码如下所示。
本实例执行后的效果如图2-1所示。
图2-1 执行效果
注意:Swift的编程风格
(1)常量通过let关键字定义,而变量使用var关键字定义。任何值如果是一个不变量,那么请使用let关键字恰如其分地定义它,最后你会发现自己喜欢使用let远多于far。
(2)有一个方法可以帮开发者符合该项规则,将所有值都定义成常量,然后编译器提示时将其改为变量。
2.4.2 声明变量
对于动态语言来说,变量也必须要有一个数据类型,只是这个数据类型并不是在定义变量时指定的,而是在程序运行到变量第一次初始化的语句时才确定数据类型。所以,动态语言的变量数据类型是在程序运行时确定的,这也是这种语言被称为动态语言的原因之一。
在Swift语言中,其值可以改变的量被称为变量。一个变量应该有一个名字,在内存中占据一定的存储单元。变量定义必须放在变量使用之前,一般放在函数体的开头部分。可以在Swift程序的同一行代码中声明多个常量或者多个变量,之间用逗号“,”隔开。例如下面的演示代码。
var x=0.0,y=0.0,z=0.0
var m=10,n=2134,q=12345
在Swift语言中,如果在代码中有不需要改变的值,则需要使用关键字“let”将其声明为常量,将需要改变的值声明为变量。
无论是变量还是常量,一旦确定了数据值类型,便不能改变。例如,下面的代码会抛出编译错误。
var value="abc"
value=20
在上述代码中,value已经确定了是字符串(String)类型,不能再次被定义为Int类型。
除此之外还要注意,如果变量或常量在定义时未指定数据类型,初始化什么值都可以。一旦指定了数据类型,必须初始化与数据类型相符的值。例如,下面的代码是错误的。
var value:String=123 //必须初始化字符串值
如果想使用一个var或let定义多个变量或常量,可以用逗号“,”分隔多个变量或常量。例如,下面定义的变量和常量都是合法的。
var v1="abc",v2:Float=20.12,v3:Bool
let const1="xyz",const2:Double=12.34;
在Swift程序中,声明根据变量类型和值的不同有着不同的形式,例如,存储型变量和属性,计算型变量和属性,存储型变量监视器和属性监视器,类和静态变量属性等,所使用的声明形式取决于变量所声明的范围和打算声明的变量类型。
(1)声明存储型变量和存储型属性
在Swift语言中,通过如下所示的格式声明一个存储型变量或存储型变量属性。
var name:type=expression
在上述格式中,“name”表示声明的标量名字,“type”表示声明的变量的类型,“expression”表示具体的变量值描述。在Swift语言中,可以使用上述格式在全局、函数、类和结构体的声明(context)中声明一个变量,具体说明如下所示。
当以上述形式在全局或者一个函数内声明变量时,声明的变量代表的是一个存储型变量。
当在类或者结构体中被声明时,声明的变量代表的是一个存储型变量属性。
初始化的表达式不可以在协议(protocol)定义中出现,在其他情况下的初始化表达式是可选的。如果没有初始化表达式,那么在定义变量时必须显示的声明变量类型(:type)。
正如名字一样,存储型变量的值或存储型变量属性均存储在内存中。
实例文件main.swift的具体实现代码如下所示。
本实例执行后的效果如图2-2所示。
图2-2 执行效果
(2)计算型变量和计算型属性
在Swift语言中,通过如下所示的格式声明一个存储型变量或存储型属性。
在Swift程序中,可以使用上述格式在全局、函数体、类、结构体、枚举和扩展中声明一个变量。具体说明如下所示。
当变量以这种形式在全局或者一个函数内被声明时,声明的变量代表一个计算型变量。
当在类、结构体、枚举、扩展的上下文中被声明时,声明的变量代表一个计算型变量属性。
getter用来读取变量值,setter用来写入变量值。setter子句是可选择的,而getter是必需的。在实际应用中可以将这些语句全部省略,只是简单的直接返回请求值。但是setter语句和getter语句必须成对出现,如果提供了一个setter语句,也必须提供一个getter语句。
setter的名字和圆括号内的语句是可选的。如果设置了一个setter名,它就会作为setter的参数被使用。
和存储型变量和存储型属性不同,计算型属性和计算型变量的值不存储在内存中。
(3)存储型变量监视器和属性监视器
在Swift语言中,可以使用willset和didset监视器来声明一个存储型变量或属性。声明一个包含监视器的存储型变量或属性的语法格式如下所示。
在Swift程序中,可以使用上述格式在全局、函数体、类、结构体、枚举、扩展中声明一个变量。具体说明如下所示。
当变量以上述格式在全局或者一个函数内被声明时,监视器代表一个存储型变量监视器。
当在类、结构体、枚举、扩展中被声明变量时,监视器代表属性监视器。
可以为适合的监视器添加任何存储型属性,也可以通过重写子类属性的方式为适合的监视器添加任何继承的属性(无论是存储型还是计算型的)。
初始化表达式在类或者结构体的声明中是可选的,但是在其他地方是必需的。无论在什么地方声明,所有包含监视器的变量声明都必须有类型注释(Type Annotation)。
当改变变量或属性的值时,willset和didset监视器提供了一个监视的方法(适当的回应)。监视器不会在变量或属性第一次初始化时运行,只有在值被外部初始化语句改变时才会被运行。
willset监视器只有在变量或属性值被改变之前运行。新的值作为一个常量经过willset监视器,因此不可以在willset语句中改变它。didset监视器在变量或属性值被改变后立即运行。和willset监视器相反,为了以防止仍然需要获得旧的数据,旧变量值或者属性会经过didset监视器。这表示如果在变量或属性自身的didiset监视器语句中设置了一个值,设置的新值会取代在willset监视器中经过的那个值。
在willset和didset语句中,setter名和圆括号的语句是可选的。如果设置了一个setter名,它就会作为willset和didset的参数被使用。如果不设置setter名,willset监视器初始名为newvalue,didset监视器初始名为oldvalue。
当提供一个willset语句时,didset语句是可选的。同样道理,当提供一个didset语句时,willset语句是可选的。
(4)类和静态变量属性
在Swift语言中,使用class关键字用来声明类的计算型属性,使用static关键字用来声明类的静态变量属性。
注意:在大多数静态语言中,指定整数或浮点数会确认默认的类型。例如,在Java语言中,如果直接写23,会认为23是int类型;如果要让23变成long类型,则需要使用23L。如果直接写23.12,编译器会认为23.12是double类型,如果要将23.12变成float类型,需要使用23.12f。而对于float v=23.12;,Java编译器会认为是错误的,因为23.12是double,而不是float,正确的写法是float v=23.12f。而对于short value=23是正确的。因为Java编译器会自动将23转换为short类型,但23必须在short类型的范围内。这么做是因为在byte code中byte、short、int都是使用同一个指令,而float和double使用了不同的指令,所以浮点数没有自动转换(理论上是可以的)。在Swift语言中,浮点数会自动转换,所以var v:Float=20.12是没问题的。
2.4.3 输出常量和变量
在Swift语言中,可以用函数println来输出当前常量或变量的值。例如如下所示的演示代码。
在Swift语言中,println是一个用来输出的全局函数,输出的内容会在最后换行。如果使用Xcode,println将会输出内容到“console”面板上。另一种函数称为print,唯一区别是在输出内容最后不会换行。
例如在下面的演示代码中,println函数会输出传入的String值。
print("This is a string")
//输出"This is a string"
与Cocoa中的函数NSLog类似,函数println可以输出更复杂的信息,这些信息可以包含当前常量和变量的值。
在Swift语言中,使用字符串插值(string interpolation)的方式将常量名或者变量名当作占位符加入长字符串中。Swift会用当前常量或变量的值替换这些占位符,将常量或变量名放入圆括号中,并在括号前使用反斜杠将其转义。例如如下所示的演示代码。
注意:字符串插值所有可用的选项,请参考字符串插值。
实例文件main.swift的具体实现代码如下所示。
本实例执行后的效果如图2-3所示。
图2-3 执行效果
2.4.4 标注类型
在Swift语言中,在声明常量或者变量时可以添加类型标注(Type annotation),用以说明常量或者变量中要存储的值的类型。当在程序中添加类型标注时,需要在常量或者变量名后添加一个冒号和空格,然后添加类型名称。
例如在下面的代码中,为变量welcomeMessage标注了类型,表示这个变量可以存储String类型的值。
var welcomeMessage:String
声明中的冒号代表着“是…类型”,所以这行代码可以被理解为:
“声明一个类型为String,名字为welcomeMessage的变量。”
“类型为String”是指“可以存储任意String类型的值。”
此时变量welcomeMessage可以被设置成任意字符串,例如如下所示的演示代码。
welcomeMessage="Hello"
在Swift编程应用中,一般很少需要用到类型标注。如果在声明常量或者变量时赋了一个初始值,Swift可以推断出这个常量或者变量的类型,具体方法请参考本书后面的类型安全和类型推断章节的内容。在上面的演示代码中,因为没有为变量welcomeMessage赋初始值,所以变量welcomeMessage的类型是通过一个类型标注指定的,而不是通过初始值推断的。
注意:从Xcode6 Beta4版本开始,多个相关变量可以用“类型标注”(type annotaion)在同一行中声明为同一类型。
实例文件main.swift的具体实现代码如下所示。
本实例执行后的效果如图2-4所示。
图2-4 执行效果
2.4.5 常量和变量的命名规则
在Swift语言中,可以用任何喜欢的字符作为常量和变量名,这其中也包括Unicode字符。例如如下所示的演示代码。
在Swift语言中,常量与变量名不能包含如下所示的元素。
数学符号。
箭头。
保留的(或者非法的)Unicode码位。
连线。
制表符。
不能以数字开头,但是可以在常量与变量名的其他地方包含数字。
一旦将常量或者变量声明为确定的类型,那么就不能使用相同的名字再次进行声明,或者改变其存储的值的类型。另外,也不能将常量与变量进行互转。
注意:如果需要使用与Swift保留关键字相同的名称作为常量或者变量名,必须使用反引号“`”将关键字包围并将其作为名字使用。尽管如此,建议读者开始应当避免使用关键字作为常量或变量名,除非别无选择。
在Swift语言中,可以修改现有的变量值为其他同类型的值,例如在下面的演示代码中,将变量friendlyWelcome的值从“Hello!”改为了“mm!”。
实例文件main.swift的具体实现代码如下所示。
本实例执行后的效果如图2-5所示。
图2-5 执行效果
与变量不同,常量的值一旦被确定就不能更改了。如果对常量尝试进行上述修改操作,就会发生编译报错的情形。例如如下所示的演示代码。