使用 class
和类名来创建一个类。类中属性的声明和常量、变量声明一样,唯一的区别就是它们的上下文是类。同样,方法和函数声明也一样。
classShape { var numberOfSides =0 funcsimpleDescription() ->String { return"A shape with \(numberOfSides) sides." } }
练习
使用
let
添加一个常量属性,再添加一个接收一个参数的方法。
要创建一个类的实例,在类名后面加上括号。使用点语法来访问实例的属性和方法。
var shape =Shape() shape.numberOfSides =7 var shapeDescription = shape.simpleDescription()
这个版本的 Shape
类缺少了一些重要的东西:一个构造函数来初始化类实例。使用 init
来创建一个构造器。
classNamedShape { var numberOfSides: Int=0 var name: String init(name: String) { self.name = name } funcsimpleDescription() ->String { return"A shape with \(numberOfSides) sides." } }
注意 self
被用来区别实例变量 name
和构造器的参数 name
。当你创建实例的时候,像传入函数参数一样给类传入构造器的参数。每个属性都需要赋值——无论是通过声明(就像 numberOfSides
)还是通过构造器(就像 name
)。
如果你需要在对象释放之前进行一些清理工作,使用 deinit
创建一个析构函数。
子类的定义方法是在它们的类名后面加上父类的名字,用冒号分割。创建类的时候并不需要一个标准的根类,所以你可以根据需要添加或者忽略父类。
子类如果要重写父类的方法的话,需要用 override
标记——如果没有添加 override
就重写父类方法的话编译器会报错。编译器同样会检测 override
标记的方法是否确实在父类中。
classSquare:NamedShape { var sideLength: Double init(sideLength: Double, name: String) { self.sideLength = sideLength super.init(name: name) numberOfSides =4 } funcarea() ->Double { return sideLength * sideLength } overridefuncsimpleDescription() ->String { return"A square with sides of length \(sideLength)." } } let test =Square(sideLength:5.2, name:"my test square") test.area() test.simpleDescription()
练习
创建
NamedShape
的另一个子类Circle
,构造器接收两个参数,一个是半径一个是名称,在子类Circle
中实现area()
和simpleDescription()
方法。
除了简单的存储属性,还有使用 getter 和 setter 的计算属性。
classEquilateralTriangle:NamedShape { var sideLength: Double=0.0 init(sideLength: Double, name: String) { self.sideLength = sideLength super.init(name: name) numberOfSides =3 } var perimeter: Double { get { return3.0* sideLength } set { sideLength = newValue /3.0 } } overridefuncsimpleDescription() ->String { return"An equilateral triangle with sides of length \(sideLength)." } } var triangle =EquilateralTriangle(sideLength:3.1, name:"a triangle") print(triangle.perimeter) triangle.perimeter =9.9 print(triangle.sideLength)
在 perimeter
的 setter 中,新值的名字是 newValue
。你可以在 set
之后的圆括号中显式地设置一个名字。
注意 EquilateralTriangle
类的构造器执行了三步:
- 设置子类声明的属性值
- 调用父类的构造器
- 改变父类定义的属性值。其他的工作比如调用方法、getters 和 setters 也可以在这个阶段完成。
如果你不需要计算属性,但是仍然需要在设置一个新值之前或者之后运行代码,使用 willSet
和 didSet
。写入的代码会在属性值发生改变时调用,但不包含构造器中发生值改变的情况。比如,下面的类确保三角形的边长总是和正方形的边长相同。
classTriangleAndSquare { var triangle: EquilateralTriangle { willSet { square.sideLength = newValue.sideLength } } var square: Square { willSet { triangle.sideLength = newValue.sideLength } } init(size: Double, name: String) { square =Square(sideLength: size, name: name) triangle =EquilateralTriangle(sideLength: size, name: name) } } var triangleAndSquare =TriangleAndSquare(size:10, name:"another test shape") print(triangleAndSquare.square.sideLength) print(triangleAndSquare.triangle.sideLength) triangleAndSquare.square =Square(sideLength:50, name:"larger square") print(triangleAndSquare.triangle.sideLength)
处理变量的可选值时,你可以在操作(比如方法、属性和子脚本)之前加 ?
。如果 ?
之前的值是 nil
,?
后面的东西都会被忽略,并且整个表达式返回 nil
。否则,可选值会被解包,之后的所有代码都会按照解包后的值运行。在这两种情况下,整个表达式的值也是一个可选值。
let optionalSquare: Square?=Square(sideLength:2.5, name:"optional square") let sideLength = optionalSquare?.sideLength