常量与变量
常量和变量是某个特定类型的值的名字,如果在程序运行时值不能被修改的是一个常量,反之是一个变量。
一、常量和变量的声明
Swift中的常量和变量在使用前必须先声明。其中let
关键字声明常量,var
关键字声明变量:
//声明一个名为maximumNumberOfLoginAttempts的整型常量,并且值为10letmaximumNumberOfLoginAttempts=10//声明一个名为currentLoginAttempt的整型变量,并且值为0varcurrentLoginAttempt=0
可以在同一行声明多个变量,中间用逗号,
隔开:
varx=0.0,y=0.0,z=0.0
提示
如果在程序运行的时候值不需要发生改变,应该将它们声明为常量,否则声明为变量
变量的值可以进行修改:
varfriendlyWelcome="Hello!"friendlyWelcome="Bonjour!"//friendlyWelcome的值发生改变
常量的值一旦设置后就不能在修改:
letlanguageName="Swift"languageName="Swift++"//编译时出错
类型说明
在Swift中声明常量或者变量可以在后面用冒号:
指定它们的数据类型。
//声明一个String类型的变量,可以存放String类型的值varwelcomeMessage:String
提示
实际应用中很少需要指定变量数据类型,Swift会根据所设置的值的类型进行推导。
命名规则
Swift中可以使用任意字符给常量和变量命名,包括Unicode编码,比如中文、Emoji等:
letπ=3.14159let你好="你好世界"letdog="dogcow"
名字里面不能包含数学运算符、箭头、非法的Unicode字符以及不能识别的字符等,并且不能以数字开头。同一个作用域的变量或者常量不能同名。
提示
如果想用关键字作为变量的名字,要用(`)包裹起来。为了方便理解,如果不是万不得已,不应该使用关键字作为变量的名字。
打印变量的值
println
函数可以打印常量或者变量的值:
println(friendlyWelcome)//打印出“Bonjour!”
println是一个全局函数,输出时自动在结尾添加换行。print
函数与它的功能类似,只是不会在结尾添加换行符。println
与NSLog
类似,可以用来输出复杂的日志信息。Swift使用字符串插值来输出变量和常量。
println("ThecurrentvalueoffriendlyWelcomeis\(friendlyWelcome)")//打印“ThecurrentvalueoffriendlyWelcomeisBonjour!”
注释
注释是用来帮助理解和记忆代码功能的,并不会参与编译。Swift有两种注释形式,单行注释和多行注释:
//这是单行注释,用两个斜线开头,直到改行的结尾/*这是多行注释, 可以横跨很多行, /*比C语言更加NB的是,*/它竟然还支持嵌套的注释!*/
分号
Swift中语句结尾的分号;
不是必须的,不过如果想要在同一行中写多个语句,则需要使用;
进行分隔。
letcat="cat";println(cat)//打印“cat”
二、属性
属性是依赖于某个特定的类、结构体或者枚举类型的值。Swift有两种属性:存储类型和计算类型。其中存储类型可以作为实例的一部分存放变量或者常量的值,而计算类型的属性值是通过运算的来的。计算类型的属性可以在类、结构体和枚举类型中出现,但存储类型只可能出现在类和结构体类型中。
属性一般依赖于一个特定类型的实例,但是也可以依赖于类本身。依赖于类型本身的属性称为类型属性。
存储属性
常量属性let
的值在初始化后不能在改变,而变量属性var
的值可以随时更改。
structFixedLengthRange{varfirstValue:Int letlength:Int }varrangeOfThreeItems=FixedLengthRange(firstValue:0,length:3) rangeOfThreeItems.firstValue=6
结构体常量的存储属性
如果一个结构体实例被赋值给一个常量,则这个实例所拥有的存储类型的属性都不能在改变,包括变量属性在内。
letrangeOfFourItems=FixedLengthRange(firstValue:0,length:4) rangeOfFourItems.firstValue=6//编译错误,firstValue的值不能改变
延时存储属性
延时存储属性的初始值直到第一次使用的时候才进行计算,在声明时通过@lazy
进行标记。
提示
一定要将延时存储属性声明为变量(var
),因为它的初始值可能会在实例初始化完成后才有,而常量属性一般在实例初始化完成之前就会有值。
当属性的初始值依赖于外部银子,并且该因子的值在实例初始化完成之前不确定时,延时属性非常有用。如果属性初始化时需要进行大量的计算,也可以考虑使用延时属性。
下面是延时属性的示例:
classDataImporter{/* DataImporter是一个从外部文件导入数据的类。假设它需要花费较多的时间进行初始化 */ varfileName="data.txt" //DataImporter类的数据导入功能} classDataManager{ @lazyvarimporter=DataImporter()vardata=String[]()//DataManager类提供数据管理功能} letmanager=DataManager() manager.data+="Somedata"manager.data+="Somemoredata"//DataImporter实例还没有创建
只有访问DataManager
的importer
属性时才会去创建这个对象。
println(manager.importer.filename)//创建importer属性并打印“data.txt"
存储属性与实例变量
Objective-C类的对象可以使用属性或者实例变量来存储值。Swift中并没有所谓的实例变量,而是将它们统一为属性了,这样使得属性的声明更加简化。
计算属性
除了存储属性外,类、结构体和枚举类型还可以定义计算属性。这些计算属性并不能够存储值,而是通过getter
方法和可选的setter
方法来间接的获取和设置其它属性和值。
structPoint{varx=0.0,y=0.0}structSize{varwidth=0.0,height=0.0}structRect{varorigin=Point()varsize=Size()varcenter:Point{ get{ letcenterX=origin.x+(size.width/2) letcenterY=origin.y+(size.height/2)returnPoint(x:centerX,y:centerY) } set(newCenter){ origin.x=newCenter.x-(size.width/2) origin.y=newCenter.y-(size.height/2) } } }varsquare=Rect(origin:Point(x:0.0,y:0.0),size:Size(width:10.0,height:10.0)) letinitialSquareCenter=square.center square.center=Point(x:15.0,y:15.0)println("square.originisnowat(\(square.origin.x),\(square.origin.y))")//打印”square.originisnowat(10.0,10.0)“
这个例子定义了三个结构体来表示几何形状:
Point
封装了(x,y)坐标。Size
封装了宽度和高度。Rect
用坐标原点和大小定义一个矩形。
其中Rect
结构体还提供了一个center
的计算属性。这个属性的值是由矩形的origin
和size
属性决定的,它本身并不需要存储信息。但是改变center
的值,会间接的修改矩形的其它属性。
三、简化setter
的声明
如果没有为计算属性的setter
的新值指定名字,则默认使用newValue
。下面是Rect
结构体的另外一种写法:
structAlternativeRect{varorigin=Point()varsize=Size()varcenter:Point{ get{ letcenterX=origin.x+(size.width/2) letcenterY=origin.y+(size.height/2)returnPoint(x:centerX,y:centerY) } set{ origin.x=newValue.x-(size.width/2) origin.y=newValue.y-(size.height/2) } } }
只读的计算属性
如果一个计算属性只有getter
而没有声明setter
,则它是一个只读的计算属性。只读属性只能通过点语法返回一个值,而不能对它进行设置。
提示
必须使用var
声明计算属性,包括只读的计算属性在内,因为它们的值是可能改变的。而let
只能用于常量的声明,表示它们的值不能发生改变。
还可以省略只读计算属性声明中的get
关键字。
structCuboid{varwidth=0.0,height=0.0,depth=0.0 varvolume:Double{returnwidth*height*depth } } letfourByFiveByTwo=Cuboid(width:4.0,height:5.0,depth:2.0)println("thevolumeoffourByFiveByTwois\(fourByFiveByTwo.volume)")//打印“thevolumeoffourByFiveByTwois40.0”
属性观察者
属性观察者用来观察和响应属性值的变化。每次设置属性的值都会调用相应的观察者,哪怕是设置相同的值。
可以给除延时存储属性以外的任何存储属性添加观察者。通过重写属性,可以在子类中给父类的属性(包括存储属性和计算属性)添加观察者。
提示
不需要给类本身定义的计算属性添加观察者,完全可以在计算属性的setter
中完成对值的观察。
如果没有给willSet
指定参数的话,编译器默认提供一个newValue
做为参数。同样,在didSet
中如果没有提供参数的话,默认为oldValue
。
提示willSet
和didSet
观察者在属性进行初始化的时候不会被调用。
classStepCounter{vartotalSteps:Int=0{ willSet(newTotalSteps){println("AbouttosettotalStepsto\(newTotalSteps)") } didSet{iftotalSteps>oldValue{println("Added\(totalSteps-oldValue)steps") } } } } letstepCounter=StepCounter() stepCounter.totalSteps=200//AbouttosettotalStepsto200//Added200stepsstepCounter.totalSteps=360//AbouttosettotalStepsto360//Added160stepsstepCounter.totalSteps=896//AbouttosettotalStepsto896//Added536steps
提示
如果在didSet
中给属性设置新值,最终结果就是最后设置的这个值。
全局变量与局部变量
上面关于计算属性和属性观察对全局变量和局部变量同样成立。全局变量定义在任意的方法、函数、闭包或者类型定义之外。而局部变量则定义在方法、函数或闭包之内。
之前遇到的全局变量或者局部变量都跟存储属性类型,都是用来存储值的。但实际上它们也能像计算属性有计算变量。
提示
全局变量和常量与延时属性类似,总是延时进行计算。但是它们并不需要使用@lazy
标记。
局部常量和变量一定不是延时计算的。
类型属性
实例属性属于某个特定类型的实例。每次创建的实例,它都拥有自己的一组独立的属性值,不受其它实例对象影响。
你还可以定义属于类型本身的属性。这些属性是与具体的实例无关的,不管创建多少个实例都只有一份。这种属性称之为类型属性。
可以给值类型(结构体和枚举类型)定义存储和计算类型的类属性,但是只能给类定义计算类型的类属性。值类型的存储属性可以是变量或常量。
提示
一定要给存储类型的类属性设置初始值。
类型属性语法
在C/Objective-C中只能使用全局静态变量来定义依赖与某个属性的变量或常量。但是在Swift中可以直接将它们定义为类型的一部分。其中结构体和枚举类型中使用static
关键字,而在类类型中则使用class
关键字。
structSomeStructure{ staticvarstoredTypeProperty="Somevalue." staticvarcomputedTypeProperty:Int{//returnanIntvaluehere } } enumSomeEnumeration{ staticvarstoredTypeProperty="Somevalue." staticvarcomputedTypeProperty:Int{//returnanIntvaluehere } } classSomeClass{ classvarcomputedTypeProperty:Int{//returnanIntvaluehere } }
提示
上面的计算属性都是只读的,但实际上可以定义为可读可写
使用类型属性
类型属性通过类型名字和点操作符进行访问和设置,而不是通过实例对象:
println(SomeClass.computedTypeProperty)//print"42"println(SomeStructure.storedTypeProperty)//prints"Somevalue"SomeStructure.storedTypeProperty="Anothervalue."println(SomeStructure.storedTypeProperty)//prints"Anothervalue."
下面演示了如何使用一个结构体来对声道音量进行建模,其中每个声道音量范围为0-10。
structAudioChannel{ staticletthresholdLevel=10 staticvarmaxInputLevelForAllChannel=0 varcurrentLevel:Int=0{ didSet{ifcurrentLevel>AudioChannel.thresholdLevel{ currentLevel=AudioChannel.thresholdLevel }ifcurrentLevel>AudioChannel.maxInputLevelForAllChannels{ AudioChannel.maxInputLevelForAllChannels=currentLevel } } }varleftChannel=AudioChannel()varrightChannel=AudioChannel() leftChannel.currentLevel=7println(leftChannel.currentLevel)//prints"7"println(AudioChannel.maxInputLevelForAllChannels)//prints"7"
当修改其中一个声道的值时,整个声道的音量最大值就可能发生改变。而每个声道都有自己的当前音量水平。