2.Swift教程翻译系列——Swift概览
来源:互联网 发布:雷洋死亡真相 知乎 编辑:程序博客网 时间:2024/05/20 13:18
英文版PDF下载地址http://download.csdn.net/detail/tsingheng/7480427
按照传统学习程序语言都是从hello,world开始,在Swfit里面只需要一行代码就行了
println("Hello, world")
你要是学过C语言或者OC,这种语法应该很熟悉。但是在Swfit里面这单独一行代码就是一个完整的程序。你都不需要再去导入什么库,比如input/output或者string之类的。全局作用范围内(global scope)的代码用来作为程序的入口,所以连main方法都不需要了,甚至语句结尾连分号都不用写了。
这份观光手册展示了怎么用swift去完成各种各样的编程任务。要是有什么看不懂的也不要怕,这里介绍的东西到后面都还会有详细介绍。
NOTE:为了获得最好的学习体验,建议打开XCode建个playgrounds,写完代码立马就能看到结果了。
1.简单值
let用来定义常量,var用来定义变量。常亮的值在编译的时候可以是未知的,但是你只能赋值一次。你可以定义一个常亮给他赋个值,然后其他地方随便用但是不改他的值。
var myVariable = 42myVariable = 5let myConstant = 42
不管是常量还是变量,赋值的时候值类型跟常量或者变量的类型必须相同。但是我们不必每次都把类型明确写出来。当你声明的时候就给他赋个值,编译器就能推断出来是什么类型了。上面这个例子中,编译器能推断出来myVarible是integer因为初始化的值是integer。要是根据初始化值不能推断出来是什么类型,或者没有初始化值,拿在变量名称后面就必须加上: 类型。比如下面的例子
let implicitInteger = 70let implicitDouble = 70.0let explicitDouble: Double = 70
值从来不会自动转换成其他类型,如果你要转换,就用值再创建一个你需要的类型,比如
let label = "The width is "let width = 94let widthLabel = label + String(width)
其实有更简单的方法能在字符串里加其他类型,把变量名放在括号里,再在括号前面加个\就行了。比如
let apples = 3let oranges = 5let appleSummary = "I have \(apples) apples"left fruitSummary = "I have \(apples+oranges) pieces of fruit"
如要要创建数组类型或者字典类型就用中括号[],如果要获取其中的节点就在[]中写节点的下标或者key值
var shoppingList = ["catfish", "water", "tulips", "blue paint"]shoppingList[1] = "bottle of water" var occupations = [ "Malcolm": "Captain", "Kaylee": "Mechanic",]occupations["Jayne"] = "Public Relations”
要创建空数组或者空字典就要用实例化的语法了。
let emptyArray = String[]()let emptyDictionary = Dictionary<String, Float>()
要是类型可以推断出来,就可以用[]和[:]来替换了。比如说上面已经定义过shoppingList数组了,编译器可以推断出shoppingList是一个字符串数组,这个时候我想让他变成空数组,就只需要写shoppingList = []就可以了,字典类型就不用说了吧。
2.控制流
if和switch做条件控制,for-in,for,while,和do-while做循环控制。条件或者循环变量外面的括号可有可无,但是代码段的大括号是必须的。
let individualScores = [75, 43, 103, 87, 12]var teamScore = 0for score in individualScores { if score > 50 { teamScore += 3 } else { teamScore += 1 }}teamScore
在if语句中,条件表达式必须是一个Boolean表达式,所以上面例子中如果写成if score {...}就是错的。
对于有些可能丢失的值,你可以使用If和let的组合。这种值后面会介绍,叫做可选类型(optional),一个可选类型值要么有一个确切的值,要么就是nil也就是值丢失了。如果要定义可选类型的值,就在变量类型后面加一个问号。
var optionalString: String? = "Hello"optionalString == nil var optionalName: String? = "John Appleseed"var greeting = "Hello!"if let name = optionalName { greeting = "Hello, \(name)"}
EXPERIMENT 试试把optionalName的值换成nil,给if语句加一个else分支,else里面给greeting另一个字符串,看看最后的greeting是什么。
注意条件语句let name = optionalName,如果optionalName的值是nil,那这个条件语句返回的就是false,大括号里的语句也就不会执行了。否则optionalName的值就被赋给常量name,并且在后面大括号里面可以使用name的值。
switch语句支持任何类型的数据和各种各样的比较运算,不仅仅是比较integer的值是不是相等。
let vegetable = "red pepper"switch vegetable {case "celery": let vegetableComment = "Add some raisins and make ants on a log."case "cucumber", "watercress": let vegetableComment = "That would make a good tea sandwich."case let x where x.hasSuffix("pepper"): let vegetableComment = "Is it a spicy \(x)?"default: let vegetableComment = "Everything tastes good in soup."}
EXPERIMENT 试试去掉default,看看会得到什么错误。
如果在switch里面匹配到某个case,执行完case的这段代码以后程序就会跳出这个switch。这里可不会像其他语言一样继续向后匹配,所以前面例子中都找不到一个breakl;。
for-in可以用来遍历字典里面的各个项目
let interestingNumbers = [ "Prime": [2, 3, 5, 7, 11, 13], "Fibonacci": [1, 1, 2, 3, 5, 8], "Square": [1, 4, 9, 16, 25],]var largest = 0for (kind, numbers) in interestingNumbers { for number in numbers { if number > largest { largest = number } }}largest
EXPERIMENT 添加另外一个变量来记录最大数字的kind,也就是最大的那个数字是什么。(拿上面这个例子来说结果应该是Square)
使用while可以重复执行一段代码,知道判断条件改变。循环条件语句也可以放在循环体后面,这样能保证循环体至少被执行一次。
var n = 2while n < 100 { n = n * 2}n var m = 2do { m = m * 2} while m < 100m
在for循环中可以有两种方式使用下标,一种是使用..(两个点)来创建一个下标的范围,或者明确地写初始值,条件表达式,还有增长表达式(很经典的for(int i = 0; i <= n; i++){})。两种方式效果一样
var firstForLoop = 0for i in 0..3 { firstForLoop += i}firstForLoop var secondForLoop = 0for var i = 0; i < 3; ++i { secondForLoop += 1}secondForLoop
用..(两点儿)的范围是半开半闭区间的,用...(三点儿)的范围两边儿都是闭区间。
3.函数和闭包
函数使用关键字func来声明。调用函数的时候在方法名称后面加个括号,括号里加上参数值列表。函数返回值类型和参数列表中间用->隔开。
func greet(name: String, day: String) -> String { return "Hello \(name), today is \(day)."}greet("Bob", "Tuesday")EXPERIMENT 把参数day去掉,添加另外一个参数在返回的问候语里加上今天的午餐。
可以使用元组返回多个值
func getGasPrices() -> (Double, Double, Double){ return (3.59, 3.69, 3.79)}getGasPrices()
方法也可以使用变长参数,参数存储在数组中。
func sumOf(numbers: Int...) -> Int { var sum = 0 for number in numbers { sum += number } return sum}sumOf()sumOf(42, 597, 12)
EXPERIMENT 写个函数求参数平均值。
在Swift里函数可以嵌套。内部函数可以访问外层函数定义的变量。如果有些代码比较长或者很复杂,你可以使用内部函数来简化。
func returnFifteen() -> Int { var y = 10 func add() { y += 5 } add() return y}returnFifteen()
函数也是一种类型,所以函数的返回值也可以是一个函数。
func makeIncrementer() -> (Int -> Int) { func addOne(number: Int) -> Int { return 1 + number } return addOne}var increment = makeIncrementer()increment(7)
当然,函数也可以作为另一个函数的参数。
func hasAnyMatches(list: Int[], condition: Int -> Bool) -> Bool { for item in list { if condition(item) { return true } } return false}func lessThanTen(number: Int) -> Bool { return number < 10}var numbers = [20, 19, 7, 12]hasAnyMatches(numbers, lessThanTen)
函数实际上是闭包的一种特殊情况。你可以写个闭包不要名字,只需要把代码放到大括号里面。在代码里面用in分隔参数与返回类型跟函数体。
numbers.map({ (number: Int) -> Int in let result = 3 * number return result})
EXPERIMENT 写个闭包,对于所有的奇数都返回0。
另外还有几个选项可以让你更方便的来写闭包。如果说一个闭包的类型已经知道了,比如是委托的一个回调函数,那你可以省略他的参数类型或者返回类型,都省略了也行。只有单独一行语句的闭包返回的是那条语句的计算结果。
numbers.map({number in 3*number})
你要是不想用参数名来使用参数,也可以用数字,这玩意儿在超短闭包里面很实用。一个闭包作为函数的最后一个参数可以放在括号后面。
sort([1, 5, 3, 12, 2]){$0 > $1}
4.对象和类
使用关键字class后面加类名,就能创建一个类了。类里面的属性声明跟前面提到的常量或者变量的声明方式一样。除非是类变量。类似的,类里面的函数跟方法的声明也是一样的。
class Shape { var numberOfSides = 0 func simpleDescription() -> String { return "A shape with \(numberOfSides) sides." }}
EXPERIMENT 用let添加一个常量属性,再添加一个带参数的方法。
如果要创建类的实例,在类名后面加上括号就行了。用点操作符来获取实例的属性或者调用实例的方法。
var shape = Shape()shape.numberOfSides = 7var shapeDescription = shape.simpleDescription()
上面定义的Shape类缺了个很重要的东东,就是初始化,当创建类的实例的时候需要初始化的。初始化方法的名字是init
class NamedShape { var numberOfSides: Int = 0 var name: String init(name: String) { self.name = name } func simpleDescription() -> String { return "A shape with \(numberOfSides) sides." }}
注意上面init里面有个self,跟java里面的this应该是一个意思,这里是用来区分等号左边的name是对象的name属性,右边的name是初始化方法的参数name。实例化方法参数传递跟普通方法传递是一样的。对象的每一个属性都必须要实例化,要么实在声明的地方赋值(比如numberOfSide),要么就在init里面(比如name)。
如果想在对象被回收的时候做一些其他操作,可以用deinit方法来创建析构函数。
类继承的方法是在子类后面加上冒号,再加上父类。Swift没有规定说一个类必须要继承哪个类,所以不需要继承的时候冒号跟父类就可以省略了。(好像OC必须至少要继承NSObject)
子类如果要重写父类的方法就要在子类方法前面加override,为了防止你不小心重新了哪个方法。如果你不写,编译器是要报错的。如果子类的方法父类里面没有,你还加个override,编译器照样报错。
class Square: NamedShape { var sideLength: Double init(sideLength: Double, name: String) { self.sideLength = sideLength super.init(name: name) numberOfSides = 4 } func area() -> Double { return sideLength * sideLength } override func simpleDescription() -> String { return "A square with sides of length \(sideLength)." }}let test = Square(sideLength: 5.2, name: "my test square")test.area()test.simpleDescription()
EXPERIMENT 创建一个Circle类,继承NameShape,初始化方法参数有半径跟名称。实现Circle的area和describe方法。
属性除了像上面那样的以外,还可以有getter和setter。
class EquilateralTriangle: NamedShape { var sideLength: Double = 0.0 init(sideLength: Double, name: String) { self.sideLength = sideLength super.init(name: name) numberOfSides = 3 } var perimeter: Double { get { return 3.0 * sideLength } set { sideLength = newValue / 3.0 } } override func simpleDescription() -> String { return "An equilateral triagle with sides of length \(sideLength)." }}var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")triangle.perimetertriangle.perimeter = 9.9triangle.sideLength
在perimeter的setter方法里面,参数名称默认是newValue,你也可以在set后面加上括号指定参数的名称。
上面例子中的EquilateralTriangle的初始化方法做了三件事情
1.给子类定义的属性赋值。
2.调用父类的初始化方法。
3.改变父类中定义的属性的值。这里就可以调用对象的各种方法了,包括getter,setter。
如果你不需要去计算某个属性的值,但是想在属性赋值之前和之后做点儿事情,那你可以用willSet和didSet。有点儿像java里面的回调方法。比如下面的例子能确保三角形周长跟正方形周长相等。
class TriangleAndSquare { 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")triangleAndSquare.square.sideLengthtriangleAndSquare.triangle.sideLengthtriangleAndSquare.square = Square(sideLength: 50, name: "larger square")triangleAndSquare.triangle.sideLength
函数跟类里面的方法是有个很重要的区别的。函数的参数名只能在函数里面使用,但是方法的参数名在方法调用的时候也要用到(除了第一个参数)。当你调用方法的时候,默认调用的参数名跟方法里的参数名是一样的。你也可以给参数再额外定义一个名字,这个名字只在方法里面可以用。
class Counter { var count: Int = 0 func incrementBy(amount: Int, numberOfTimes times: Int) { count += amount * times }}var counter = Counter()counter.incrementBy(2, numberOfTimes: 7)
如果你在用可选值,问号后面可以加方法操作或者属性或者下标操作。如果问号前面的值是nil,那么问号后面的表达式就不会被执行,整个表达式返回的也是nil,如果是另外一种情况,可选值是个展开的值(unwrapped value不知道怎么翻译),那跟在问号后面的也是个展开的值。两种情况下整个表达式返回的都是个可选值。
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")let sideLength = optionalSquare?.sideLength
5.枚举和结构类型
enum用来创建枚举类型。跟类一样,枚举也可以定义方法。
enum Rank: Int { case Ace = 1 case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten case Jack, Queen, King func simpleDescription() -> String { switch self { case .Ace: return "ace" case .Jack: return "jack" case .Queen: return "queen" case .King: return "king" default: return String(self.toRaw()) } }}let ace = Rank.Acelet aceRawValue = ace.toRaw()
EXPERIMENT 写个方法根据原始数据比较两个Rank类型的大小。
上面例子中,枚举类型的原始类型是Int,这种类型你可以只指出第一个枚举的原始值。剩下的将会按顺序递增。当然原始值类型也可以用浮点数或者字符串。
toRaw和fromRaw两个函数可以分别获取原始值和枚举值。
if let convertedRank = Rank.fromRaw(3) { let threeDescription = convertedRank.simpleDescription()}
成员值是枚举的实际的值,而不是枚举的原始值。实际上要是枚举没有什么实际有意义的原始值,大可不要了。
enum Suit { case Spades, Hearts, Diamonds, Clubs func simpleDescription() -> String { switch self { case .Spades: return "spades" case .Hearts: return "hearts" case .Diamonds: return "diamonds" case .Clubs: return "clubs" } }}let hearts = Suit.Heartslet heartsDescription = hearts.simpleDescription()
EXPERIMENT 添加一个方法color,如果是黑桃或者梅花,返回"这牌是黑色的",如果是红桃儿或者方片儿返回“这牌是红色儿的”。
注意到上面例子中有两种方式来获取枚举值Hearts。当要赋值给常量hearts的时候是要用Suit.Heart的,因为这个时候常亮还不知道自己究竟是什么类型。但是在switch里面,就省略了点前面的Suit,因为self知道自己就是个Suit。只要变量或者常量知道自己是那种枚举类型,就能用这种缩写形式。
struct用来创建结构类型。估计做行政的都知道。struct支持很多类的特性,包括方法,初始化器。他跟类最大得区别就是在赋值的时候,class对象是传递引用,结构类型是值传递。如果你是做JAVA的,可以认为结构类型就好比java里面的基本数据类型一样。
struct Card { var rank: Rank var suit: Suit func simpleDescription() -> String { return "The \(rank.simpleDescription()) of \(suit.simpleDescription())" }}let threeOfSpades = Card(rank: .Three, suit: .Spades)let threeOfSpadesDescription = threeOfSpades.simpleDescription()
EXPERIMENT 给Card添加一个方法,创建一副牌,不要俩王的。
下面这两句有点儿难理解,对着后面的例子看看就能明白了。枚举的一个成员的可以对应很多个实例。而这很多个实例又可以对应不同的值。怎么做到呢,要在你创建枚举成员实例的时候给他提供对应的值。这个值跟原始值还是不一样的。对已同一个成员的多个实例来说他们的原始值都是一样的,都是在定义枚举成员的时候指定的。
比如说要从一台服务器获取日出和日落的时间。服务端要么返回一段日落或者日出时间的信息,要么返回一个错误消息。
enum ServerResponse { case Result(String, String) case Error(String)} let success = ServerResponse.Result("6:00 am", "8:09 pm")let failure = ServerResponse.Error("Out of cheese.") switch success {case let .Result(sunrise, sunset): let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."case let .Error(error): let serverResponse = "Failure... \(error)"}
EXPERIMENT 给ServerResponse和后面的switch再添加一个case,比如表示今儿没出太阳。
注意上面日出跟日落时间是怎么从ServerResponse中取出来并且在switch里面匹配的。
6.接口和拓展
protocol用来声明接口。
protocol ExampleProtocol { var simpleDescription: String { get } mutating func adjust()}
类,枚举,结构,都能实现接口。
class SimpleClass: ExampleProtocol { var simpleDescription: String = "A very simple class." var anotherProperty: Int = 69105 func adjust() { simpleDescription += " Now 100% adjusted." }}var a = SimpleClass()a.adjust()let aDescription = a.simpleDescription struct SimpleStructure: ExampleProtocol { var simpleDescription: String = "A simple structure" mutating func adjust() { simpleDescription += " (adjusted)" }}var b = SimpleStructure()b.adjust()let bDescription = b.simpleDescription
EXPERIMENT 写个实现上面接口的枚举。
注意上面SimpleStructure里面有个方法前面加了个mutating,表明这个方法要修改结构体。那为什么上面的class里面的方法没加呢,因为类里面的方法总是会改变类的类型。
extension用来给已有的类型添加新的功能。比如添加方法,属性啊之类的。你还可以用extension给其他任意地方定义的类型添加接口,甚至是导入的第三方库或者框架都行。
extension Int: ExampleProtocol { var simpleDescription: String { return "The number \(self)" } mutating func adjust() { self += 42 }}7.simpleDescription
EXPERIMENT 用extension给Double类型加个求绝对值的方法。
使用接口名称的方式跟使用其他类型方式差不多的。比如说创建一堆对象,对象类型不一样,但是都实现了同一个接口。当你使用类型是接口的对象时,在接口里面没有定义的方法是不能调用的。
let protocolValue: ExampleProtocol = aprotocolValue.simpleDescription// protocolValue.anotherProperty // Uncomment to see the error
上面例子中即使protocolValue实际上是一种SimpleClass的对象,但是编译器只能把他当成ExampleProtocol。所以说没在接口里面定义的方法你是调用不到的。(话说JAVA不是可以强制类型转换吗)。
7.泛型
泛型的使用方法是在尖括号里加上类型名称。
func repeat<ItemType>(item: ItemType, times: Int) -> ItemType[] { var result = ItemType[]() for i in 0..times { result += item } return result}repeat("knock", 4)
函数,方法,类,枚举,结构,都能用到泛型。
// Reimplement the Swift standard library's optional typeenum OptionalValue<T> { case None case Some(T)}var possibleInteger: OptionalValue<Int> = .NonepossibleInteger = .Some(100)
where关键字放在类型名称后面用来声明一些条件。比如类型必须实现某个接口,或者两个类型必须相同,或者类型必须是某个类的子类。跟java里面的概念一样的。
func anyCommonElements <T, U where T: Sequence, U: Sequence, T.GeneratorType.Element: Equatable, T.GeneratorType.Element == U.GeneratorType.Element> (lhs: T, rhs: U) -> Bool { for lhsItem in lhs { for rhsItem in rhs { if lhsItem == rhsItem { return true } } } return false}anyCommonElements([1, 2, 3], [3])
EXPERIMENT 修改上面的anyCommonElements函数,返回两个序列里面相同元素组成的数组。
一般简单情况下可以省略where,只在类型名后面加上冒号和父类或者接口的名称。比如<T: Equatable>跟<T where T : Equatable>效果是一样的。
本章完。下章地址 3.Swift基础知识
- 2.Swift教程翻译系列——Swift概览
- 1.Swift教程翻译系列——关于Swift
- 3.Swift教程翻译系列——Swift基础知识
- 6.Swift教程翻译系列——Swift集合类型
- Swift教程翻译系列——控制流之循环
- 4.Swift教程翻译系列——Swift基本运算符
- 5.Swift教程翻译系列——Swift字符串和字符
- 7.Swift教程翻译系列——控制流之循环
- 8.Swift教程翻译系列——控制流之条件
- 7.Swift教程翻译系列——控制流之循环
- Apple Swift语言基础入门 —— Swift概览1
- Apple Swift语言基础入门 —— Swift概览2
- Apple Swift语言基础入门 —— Swift概览3
- Apple Swift语言基础入门 —— Swift概览4
- Apple Swift语言基础入门 —— Swift概览5
- swift string概览——boolean
- Swift概览
- Swift-概览
- 积累(三)
- 数据库存储
- 获取联系人【自定义布局文件与主布局文件相连,数据库内容查找并显示】
- 深入浅出面向对象分析与设计笔记
- TableView的添加删除执行顺序
- 2.Swift教程翻译系列——Swift概览
- WRTNode(MT7620) UBoot 中实现USB功能过程记录
- Oracle SQL性能优化
- 【读书笔记】Windows CE嵌入式系统_【2】_操作系统层
- openSession和getCurrentSession的比较
- 11gR2 grid安装执行root.sh报错
- Question
- 剑指offer面试题34
- 用C语言解决迷宫问题