譯文:Swift Interview Questions and Answers
Swift面試問題及答案
原文鏈接 : Swift Interview Questions and Answers
譯文發(fā)布地址:
part1: http://www.lxweimin.com/p/e98d7dc625ff
part2:http://www.lxweimin.com/p/0b9bdffc2523
原文作者 : Antonio Bello
譯者 : lfb_CD 歡迎關注我的微博喲:http://weibo.com/lfbWb
雖然swift才發(fā)布不到一年的時間,但它已經(jīng)成為最流行的開發(fā)語言之一了。
事實上,Swift,是一種復雜的語言,同時面向?qū)ο蠛兔嫦蚝瘮?shù),并且它仍然還在不斷推出新的版本。
Swift有很多東西,但是你怎么能測試你學了多少?在這篇文章中,raywenderlich.com團隊和我一起列了一個列表-有關swift的面試問題。
你可以用這些問題來測試面試者的Swift知識,或者測試你自己的!如果你不知道答案,不要擔心,每一個問題都有一個答案。
問題分成了兩部分:
1.筆試題目(Written questions):可以通過電子郵件來進行編程測試,需要寫一些代碼。
2.口頭提問(Verbal questions):可以很好的在電話或面對面的面試中詢問,適用于口頭回答。
另外,每部分都分為三個等級:
- 初級:適合初學者閱讀,已經(jīng)讀過一兩本有關Swift的書籍,并已經(jīng)在自己的應用程序使用了Swift。
- 中級:適合某些對語言有濃厚興趣的人,閱讀過很多有關Swift的博客文章,并想進一步進行嘗試。
- 高級:適合頂尖的-專注同時享受在程序語言里探索,挑戰(zhàn)自己,并使用尖端技術(shù)的人。
如果你想試著回答這些問題,建議你打開Playground。所有答案都已經(jīng)在Xcode 7 Beta 6中測試過。
準備好了嗎?Buckle up(系好安全帶?),開始了!
注:特別感謝團隊成員 Warren Burton, Greg Heo, Mikael Konutgan, Tim Mitra, Luke Parham, Rui Peres, and Ray Wenderlich
Written Questions(書面的問題)
Beginners(初學者)
你好,padowan(應該是指學徒,也就是我們。。)。我將開始檢測你的基本知識。
問題#1-Swift 1.0 or later
想有一個更好的方式來寫這里的循環(huán)范圍?
for var i = 0; i < 5; i++ {
print("Hello!")
}
答案:
for i in 0...4 {
print("Hello!")
}
Swift實現(xiàn)了二元操作符、閉區(qū)間操作符(...)和半閉區(qū)間操作符(..<)。第一個的范圍(...)包括所有的值。例如,下面包含所有的整數(shù),從0到4:
0...4
半閉區(qū)間操作符(..<)不包含最后一個元素。以下產(chǎn)生相同的0至4整數(shù)的結(jié)果:
0..<5
問題#2-Swift 1.0 or later
思考如下代碼:
struct Tutorial {
var difficulty: Int = 1
}
var tutorial1 = Tutorial()
var tutorial2 = tutorial1
tutorial2.difficulty = 2
tutorial1.difficulty和tutorial2.difficulty的值是什么呢?如果是一個類,又是什么呢?為什么?
答案:
tutorial1.difficulty is 1,
tutorial2.difficulty is 2.
Swift的struct是值類型,它們被復制的是值,而不是引用。下面的代碼是創(chuàng)建一份tutorial1的拷貝并將其分配到tutorial2:
var tutorial2 = tutorial1
從這段代碼開始,對tutorial2任何改變都不會影響到tutorial1。
如果Tutorial是類,tutorial1.difficulty和tutorial2.difficulty都會是2。Swift的類是引用類型。對tutorial1屬性的任何變化會反映到tutorial2,反之亦然。
問題#3-Swift 1.0 or later
view1是用var聲明的,而view2是用let聲明的。這里的區(qū)別是什么,最后一行又可以編譯通過么?
import UIKit
var view1 = UIView()
view1.alpha = 0.5
let view2 = UIView()
view2.alpha = 0.5 // Will this line compile?
答案:
view1是一個變量,可以被重新分配到一個新的UIView的實例。用let你只可以賦值一次,所以這段的代碼不能編譯通過:
view2 = view1 // Error: view2 is immutable
但是,UIView是一個類的引用,所以你可以改變View2屬性(這意味著最后一行可以編譯通過):
let view2 = UIView()
view2.alpha = 0.5 // Yes!
問題#4-Swift 1.0 or later
下面這段代碼將數(shù)組按字母進行了排序,看起來很復雜,請盡可能的簡化它。
let animals = ["fish", "cat", "chicken", "dog"]
let sortedAnimals = animals.sort { (one: String, two: String) -> Bool in
return one < two
}
答案:
先簡化參數(shù)。類型推導系統(tǒng)可以自動計算出閉包中的參數(shù)類型,所以我們可以去掉參數(shù):
let sortedAnimals = animals.sort { (one, two) -> Bool in return one < two }
返回值的類型也可以被推斷,所以扔掉它:
let sortedAnimals = animals.sort { (one, two) in return one < two }
符號$i可以代替參數(shù)名稱:
let sortedAnimals = animals.sort { return $0 < $1 }
在單語句閉包中,返回關鍵字可以省略。最后一個語句的返回值就是閉包的返回值:
let sortedAnimals = animals.sort { $0 < $1 }
這已經(jīng)很簡單了,但還不夠,不要停!
對于字符串,有一個比較函數(shù)定義如下:
func <(lhs: String, rhs: String) -> Bool
這個小功能使你的代碼更整潔:
let sortedAnimals = animals.sort(<)
注意,這里的每一步都是能編譯和輸出相同的結(jié)果的,并且你做了一個字符的閉包!
問題#5-Swift 1.0 or later
這段代碼創(chuàng)建了2個類、Address和Person,并且創(chuàng)建了2個實例來表示Ray和Brian。
class Address {
var fullAddress: String
var city: String
init(fullAddress: String, city: String) {
self.fullAddress = fullAddress
self.city = city
}
}
class Person {
var name: String
var address: Address
init(name: String, address: Address) {
self.name = name
self.address = address
}
}
var headquarters = Address(fullAddress: "123 Tutorial Street", city: "Appletown")
var ray = Person(name: "Ray", address: headquarters)
var brian = Person(name: "Brian", address: headquarters)
假設brian搬到街對面的新大樓,所以你更新了他的地址:
brian.address.fullAddress = "148 Tutorial Street"
但是,仔細想一下這兒有什么問題,發(fā)生了什么?這是怎么回事?
答案:
Ray的address.fullAddress也跟著改變了!Address是一個類,并且具有引用的語義。headquarters是同一個實例,無論您訪問它通過Ray或Brian,改變headquarters的Address將改變它的兩個。
解決方案是重新分配給Brian一個新的Address,或設置Address為struct而不是class。
Intermediate(中級)
現(xiàn)在,開始挑戰(zhàn)困難點的問題。你準備好了嗎?
問題#1-Swift 2.0 or later
思考下列代碼:
var optional1: String? = nil
var optional2: String? = .None
nil和.None有什么區(qū)別?optional1和optional1變量有何不同?
答案:
其實沒有區(qū)別。Optional.None(.None是縮寫)是初始化一個可選變量(沒有值)的正確寫法,而不只是語法糖.None。
下面的驗證代碼的輸出是true:
nil == .None // On Swift 1.x this doesn't compile. You need Optional<Int>.None
請牢記,在底層下,optional是枚舉類型:
enum Optional<T> {
case None
case Some(T)
}
問題#2-Swift 1.0 or later
這是溫度計的模型,一個class和一個struct:
public class ThermometerClass {
private(set) var temperature: Double = 0.0
public func registerTemperature(temperature: Double) {
self.temperature = temperature
}
}
let thermometerClass = ThermometerClass()
thermometerClass.registerTemperature(56.0)
public struct ThermometerStruct {
private(set) var temperature: Double = 0.0
public mutating func registerTemperature(temperature: Double) {
self.temperature = temperature
}
}
let thermometerStruct = ThermometerStruct()
thermometerStruct.registerTemperature(56.0)
這段代碼編譯出錯,請問哪里錯了?為什么錯了?
提示:仔細閱讀并思考一下,在Playground上測試它。
答案:
編譯器會在最后一行報錯。正確的thermometerstruct聲明與變異函數(shù)來改變其內(nèi)部的溫度變化,但編譯器報錯是因為registertemperature創(chuàng)建實例是通過let的,因此它是不變的。
在結(jié)構(gòu)體中,函數(shù)改變內(nèi)部狀態(tài)必須得是可變數(shù)據(jù)類型,即用var聲明的。
問題#3-Swift 1.0 or later
下面這段代碼會print出什么?為什么?
var thing = "cars"
let closure = { [thing] in
print("I love \\(thing)")
}
thing = "airplanes"
closure()
答案:
它會打印“I love cars”。當閉包被聲明的時候,參數(shù)列表也會創(chuàng)建一份拷貝,所以參數(shù)的值不會改變,即使將一個新值賦值給一個。
如果在閉包中省略了參數(shù)列表([thing]),則編譯器將使用一個引用而不是復制。在這種情況下,當被調(diào)用時該變量的任何變化都會產(chǎn)生變化。如下面的代碼所示:
看不太懂就直接看代碼吧= =
var thing = "cars"
let closure = {
print("I love \\(thing)")
}
thing = "airplanes"
closure() // Prints "I love airplanes"
問題#4-Swift 2.0 or later
這里有一個全局函數(shù)來計算數(shù)組中 “值是唯一” 的數(shù)目:
func countUniques<T: Comparable>(array: Array<T>) -> Int {
let sorted = array.sort(<)
let initial: (T?, Int) = (.None, 0)
let reduced = sorted.reduce(initial) { ($1, $0.0 == $1 ? $0.1 : $0.1 + 1) }
return reduced.1
}
你可以這樣使用這個方法:
countUniques([1, 2, 3, 3]) // result is 3
請把這個函數(shù)重寫為數(shù)組的擴展方法,使得你可以按照下面代碼那樣使用:
[1, 2, 3, 3].countUniques() // should print 3
答案:
在Swift2.0中,泛型可以通過強制類型約束來進行擴展。如果泛型不滿足約束,則該擴展既不可見也不可訪問。
extension Array where Element: Comparable {
func countUniques() -> Int {
let sorted = sort(<)
let initial: (Element?, Int) = (.None, 0)
let reduced = sorted.reduce(initial) { ($1, $0.0 == $1 ? $0.1 : $0.1 + 1) }
return reduced.1
}
}
注意新方法只有在當前的數(shù)據(jù)類型實現(xiàn)了Comparable的協(xié)議時才可用。比如,如果你這樣調(diào)用的話編譯器會報錯:
import UIKit
let a = [UIView(), UIView()]
a.countUniques() // compiler error here because UIView doesn't implement Comparable
問題#5-Swift 2.0 or later
這里有一個函數(shù)來計算給定的兩個double的可選數(shù)據(jù)類型的除法。在執(zhí)行除法之前,需要滿足三個條件:
- dividend不為空
- divisor不為空
- divisor不為0
func divide(dividend: Double?, by divisor: Double?) -> Double? {
if dividend == .None {
return .None
}
if divisor == .None {
return .None
}
if divisor == 0 {
return .None
}
return dividend! / divisor!
}
代碼雖然能工作,但有2個問題:
- 它的條件可以guard語句
- 它使用了強制拆包(那個!)不安全
請使用guard語句改進這個函數(shù),避免使用強制拆包。
答案:
如果條件不符合,新的guard語句在Swift2.0提供了返回值。檢查條件是非常有用的,因為它提供了一個清晰的方式表達方式--如果不需要對語句進行嵌套的話。這里就是一個例子:
guard dividend != .None else { return .None }
也可以結(jié)合可選數(shù)據(jù)類型,使得訪問變量在guard檢查之后:
guard let dividend = dividend else { return .None }
所以divide函數(shù)可以這樣寫:
func divide(dividend: Double?, by divisor: Double?) -> Double? {
guard let dividend = dividend else { return .None }
guard let divisor = divisor else { return .None }
guard divisor != 0 else { return .None }
return dividend / divisor
}
另外,你還可以合并guard語句,使函數(shù)看起來更簡單:
func divide(dividend: Double?, by divisor: Double?) -> Double? {
guard let dividend = dividend, divisor = divisor where divisor != 0 else { return .None }
return dividend / divisor
}
Advanced(高級)
問題#1-Swift 1.0 or later
思考下面的結(jié)構(gòu)體,一個溫度計的模型:
public struct Thermometer {
public var temperature: Double
public init(temperature: Double) {
self.temperature = temperature
}
}
創(chuàng)建一個實例,可以很容易地使用這個代碼:
var t: Thermometer = Thermometer(temperature:56.8)
但它有更好地初始化方式:
var thermometer: Thermometer = 56.8
怎么實現(xiàn)?
答案:
Swift定義了下面的協(xié)議,使用賦值操作符將一個類型的類型進行初始化:
- NilLiteralConvertible
- BooleanLiteralConvertible
- IntegerLiteralConvertible
- FloatLiteralConvertible
- UnicodeScalarLiteralConvertible
- ExtendedGraphemeClusterLiteralConvertible
- StringLiteralConvertible
- ArrayLiteralConvertible
- DictionaryLiteralConvertible
采用相應的協(xié)議,提供一個公共的初始化方法用來實現(xiàn)特定類型的文字初始化。在Thermometer下,實現(xiàn)FloatLiteralConvertible協(xié)議如下:
extension Thermometer : FloatLiteralConvertible {
public init(floatLiteral value: FloatLiteralType) {
self.init(temperature: value)
}
}
現(xiàn)在你可以用一個簡單的浮點數(shù)據(jù)來創(chuàng)建一個實例。
var thermometer: Thermometer = 56.8
問題#2-Swift 1.0 or later
Swift擁有一組預定義的運算符,執(zhí)行不同類型的操作,例如算術(shù)或邏輯。它還允許創(chuàng)建自定義操作,一元或二元。
按照以下要求自定義并實現(xiàn)一個 ^^ 冪運算符:
- 以兩個整數(shù)作為參數(shù)
- 返回第一個參數(shù)與第二個參數(shù)的冪運算
- 不用考慮潛在溢出錯誤
答案:
創(chuàng)建一個新的自定義操作符需要兩步:聲明和實現(xiàn)。
這部分我不太會翻譯,是關于自定義操作符的,這里有Swift的相關資料(http://www.yiibai.com/swift/custom_operators.html)
這是聲明:
infix operator ^^ { associativity left precedence 155 }
實現(xiàn)的代碼如下:
import Foundation
func ^^(lhs: Int, rhs: Int) -> Int {
let l = Double(lhs)
let r = Double(rhs)
let p = pow(l, r)
return Int(p)
}
請注意,它沒有考慮溢出情況;如果操作產(chǎn)生的結(jié)果不能用int代表。比如大于int.max,會發(fā)生運行時錯誤。
問題#3-Swift 1.0 or later
你能用這樣的原始值來定義一個枚舉嗎?為什么?
enum Edges : (Double, Double) {
case TopLeft = (0.0, 0.0)
case TopRight = (1.0, 0.0)
case BottomLeft = (0.0, 1.0)
case BottomRight = (1.0, 1.0)
}
答案:
不能。一個原始值類型必須:
- 符合合理的協(xié)議
- 從下列任一個類型中轉(zhuǎn)換的:
- Int
- String
- Character
在上面的代碼中,原始類型是一個元組,是不符合條件的。
問題#4-Swift 1.0 or later
考慮下面的代碼定義的Pizza結(jié)構(gòu)體和Pizza協(xié)議,以及擴展,包含有makemargherita()默認實現(xiàn)的一個方法:
struct Pizza {
let ingredients: [String]
}
protocol Pizzeria {
func makePizza(ingredients: [String]) -> Pizza
func makeMargherita() -> Pizza
}
extension Pizzeria {
func makeMargherita() -> Pizza {
return makePizza(["tomato", "mozzarella"])
}
}
現(xiàn)在餐廳Lombardis 的定義如下:
struct Lombardis: Pizzeria {
func makePizza(ingredients: [String]) -> Pizza {
return Pizza(ingredients: ingredients)
}
func makeMargherita() -> Pizza {
return makePizza(["tomato", "basil", "mozzarella"])
}
}
下面的代碼創(chuàng)建兩個Lombardis的實例。哪一個會成功調(diào)用margherita做出basil(一種面)?
let lombardis1: Pizzeria = Lombardis()
let lombardis2: Lombardis = Lombardis()
lombardis1.makeMargherita()
lombardis2.makeMargherita()
答案:
都能。Pizzeria協(xié)議聲明makemargherita()方法提供了一個默認的實現(xiàn)。該方法是在lombardis實現(xiàn)重寫。在這兩種情況下聲明的方法,在運行時都能正確執(zhí)行。
如果協(xié)議沒有聲明makemargherita()方法,但在擴展中還提供了一個默認的實現(xiàn)呢?
protocol Pizzeria {
func makePizza(ingredients: [String]) -> Pizza
}
extension Pizzeria {
func makeMargherita() -> Pizza {
return makePizza(["tomato", "mozzarella"])
}
}
在這種情況下,只有l(wèi)ombardis2做出Pizza,而lombardis1沒有做出Pizza,因為lombardis1會去使用在擴展中定義的makeMargherita方法方法。它沒有聲明makeMargherita方法,就不會去調(diào)用結(jié)構(gòu)體里面的那個makeMargherita方法。
問題#5-Swift 2.0 or later
以下代碼編譯時有錯誤。你能發(fā)現(xiàn)哪里出錯了么,為什么會發(fā)生錯誤?
struct Kitten {
}
func showKitten(kitten: Kitten?) {
guard let k = kitten else {
print("There is no kitten")
}
print(k)
}
Hint: There are three ways to fix it.
答案:
else里面需要退出路徑,使用return,或者拋出一個異常或聲明這是一個@noreturn的方法。最簡單的解決方案是添加返回語句。
func showKitten(kitten: Kitten?) {
guard let k = kitten else {
print("There is no kitten")
return
}
print(k)
}
添加拋出異常的方法。
enum KittenError: ErrorType {
case NoKitten
}
struct Kitten {
}
func showKitten(kitten: Kitten?) throws {
guard let k = kitten else {
print("There is no kitten")
throw KittenError.NoKitten
}
print(k)
}
try showKitten(nil)
最后一個方法,調(diào)用fatalerror()方法,表明這是一個@noreturn方法。
struct Kitten {
}
func showKitten(kitten: Kitten?) {
guard let k = kitten else {
print("There is no kitten")
fatalError()
}
print(k)
}
Verbal questions part2部分待譯。
第一次翻譯這么長的文章,也許會有翻譯錯誤的地方,大家可以在評論中幫忙指出。
另外,我在管理一個微信公眾號SwiftTips,每天發(fā)布一些Swift的文章什么的,歡迎關注