Swift學(xué)習(xí) - 第四周知識整理

協(xié)議

協(xié)議基本定義

  • 協(xié)議也能夠被繼承姑尺,但類同時繼承父類和協(xié)議時,父類必須寫在協(xié)議前面
  • 協(xié)議是方法的集合(計算屬性相當(dāng)于就是方法)
  • 可以把看似不相關(guān)的對象的公共行為放到一個協(xié)議中
  • 協(xié)議在Swift開發(fā)中大致有三種作用:
      1. 能力 - 遵循了協(xié)議就意味著具備了某種能力
      1. 約定 - 遵循了協(xié)議就一定要實現(xiàn)協(xié)議中的方法
      1. 角色 - 一個類可以遵循多個協(xié)議, 一個協(xié)議可以被多個類遵循, 遵循協(xié)議就意味著扮演了某種角色, 遵循多個協(xié)議就意味著可以扮演多種角色

Swift中的繼承是單一繼承(一個類只能有一個父類), 如果希望讓一個類具備多重能力可以使用協(xié)議來實現(xiàn)(C++里面是通過多重繼承來實現(xiàn)的, 這是一種非常狗血的做法)

協(xié)議擴展

  • 可以在協(xié)議擴展中給協(xié)議中的方法提供默認實現(xiàn)可帽,也就是說如果某個類遵循了協(xié)議但是沒有實現(xiàn)這個方法就直接使用默認實現(xiàn),那么這個方法也就相當(dāng)于是一個可選方法(可以實現(xiàn)也可以不實現(xiàn))
// 協(xié)議的繼承
protocol NiuBi: Flyable, Fightable {
    func dive()
}
// 協(xié)議的擴展
protocol Fightable {
    func fight()
}

extension Fightable {
    func fight() {
        print("正在打架")
    }
}

依賴倒轉(zhuǎn)原則(面向協(xié)議編程)

  1. 聲明變量的類型時應(yīng)該盡可能使用協(xié)議類型
  2. 聲明方法參數(shù)類型時應(yīng)該盡可能使用協(xié)議類型
  3. 聲明方法返回類型時應(yīng)該盡可能使用協(xié)議類型
protocol NiuBi: Flyable, Fightable {
}
class Superman: NiuBi {
}
// 這里的x能接受遵循Flyable, Fightable類型的協(xié)議的類,范圍較廣泛
var x: protocol<Flyable, Fightable> = Superman()
// 這里的y只能接受遵循Supanman()類型的協(xié)議的類
var y: NiuBi = Superman()    

// 協(xié)議的組合
let array: [protocol<Flyable, Fightable>] = [
    Superman(),
]

協(xié)議擴充

協(xié)議中全是抽象概念(只有聲明沒有實現(xiàn)) 遵循協(xié)議的類可以各自對協(xié)議中的計算屬性和方法給出自己的實現(xiàn)版本 這樣當(dāng)我們面向協(xié)議編程時就可以把多態(tài)的優(yōu)勢發(fā)揮到淋漓盡致 可以寫出更通用更靈活的代碼(符合開閉原則)

  • 實現(xiàn)開閉原則最關(guān)鍵有兩點:

    1. 抽象是關(guān)鍵(在設(shè)計系統(tǒng)的時候一定要設(shè)計好的協(xié)議);
    2. 封裝可變性(橋梁模式 - 將不同的可變因素封裝到不同的繼承結(jié)構(gòu)中)
  • 接口(協(xié)議)隔離原則:

    1. 協(xié)議的設(shè)計要小而專不要大而全
    2. 協(xié)議的設(shè)計也要高度內(nèi)聚

四人幫設(shè)計模式 - 策略模式

/// 圖書
class Book {
    var name: String
    var price: Double
    var type: String
    
    // 四人幫設(shè)計模式 - 策略模式
    var strategy: DiscountStrategy?
    
    /**
     初始化方法
     - parameter name:  書名
     - parameter price: 價格
     - parameter type:  類型
     */
    init(name: String, price: Double, type: String) {
        self.name = name
        self.price = price
        self.type = type
    }
    
    /// 減多少錢
    var discountValue: Double {
        get {
            if let s = strategy {
                return s.discount(price)
            }
            else {
                return 0
            }
        }
    }
    
    /// 折后價格
    var discountedPrice: Double {
        get { return price - discountValue }
    }
}



/**
 *  打折策略協(xié)議
 */
protocol DiscountStrategy { 
    /**
     計算折扣
     - parameter price: 原價
     - returns: 折扣的金額
     */
    func discount(price: Double) -> Double
}
/// 百分比折扣策略
class PercentageDiscount: DiscountStrategy {
    var percentage: Double
    
    init(percentage: Double) {
        self.percentage = percentage
    }
    
    func discount(price: Double) -> Double {
        return price * (1 - percentage)
    }
}
// 固定金額折扣策略
class FixedDiscount: DiscountStrategy {
    var fixedMoney: Double
    
    init(fixedMoney: Double) {
        self.fixedMoney = fixedMoney
    }
    
    func discount(price: Double) -> Double {
        return price >= fixedMoney ? fixedMoney : 0
    }
}
// 分段折后策略
class SegmentedDiscount: DiscountStrategy {
    
    func discount(price: Double) -> Double {
        if price < 20 {
            return 0
        }
        else if price < 50 {
            return 3
        }
        else if price < 100 {
            return 10
        }
        else {
            return 30
        }
    }
}


/// 主方法調(diào)用
let booksArray = [
    Book(name: "C語言程序設(shè)計", price: 24.0, type: "計算機"),
    Book(name: "名偵探柯南", price: 98.5, type: "漫畫"),
    Book(name: "Swift從入門到住院", price: 35.8, type: "計算機"),
    Book(name: "黃岡數(shù)學(xué)密卷", price: 34.2, type: "教材"),
    Book(name: "中國股市探秘", price: 58.5, type: "金融")
]
let discountDict: [String: DiscountStrategy] = [
    "計算機": PercentageDiscount(percentage: 0.78),
    "教材": PercentageDiscount(percentage: 0.85),
    "漫畫": SegmentedDiscount(),
    "科普": FixedDiscount(fixedMoney: 2)
]
var totalPrice = 0.0
var totalDiscount = 0.0
for book in booksArray {
    if let strategy = discountDict[book.type] {
        book.strategy = strategy
    }
    print("《\(book.name)》原價: ¥\(book.price)元")
    print("《\(book.name)》折后價: ¥\(book.discountedPrice)元")
    totalPrice += book.discountedPrice
    totalDiscount += book.discountValue
}
print(String(format: "總計: ¥%.1f元", totalPrice))
print(String(format: "為您節(jié)省了: ¥%.1f元", totalDiscount))

我想做燕子

/*
我想做燕子 只需簡單思想 只求風(fēng)中流浪
我想做樹 不長六腑五臟 不會寸斷肝腸

我做不成燕子 所以我躲不過感情的墻
我做不成樹 因此也撐不破傷心的網(wǎng)

來生做燕子吧 隨意找棵樹休息翅膀 然后淡然飛向遠方
來生做樹吧 當(dāng)枝頭燕子飛走時 再不去留戀張望
*/

/// 生物
class Creature {
}

// 來生

// 將燕子和樹設(shè)計成兩個協(xié)議
protocol Swallow {
    
    func thinkSimply()
    
    func wanderInWind()
}

protocol Tree {
    
    func beHeartless()
}

class Person: Creature {
    var name: String
    
    init(name: String) {
        self.name = name
    }
}

// 小雨康橋除了繼承人之外還遵循了燕子和樹的協(xié)議
class Xiaoyu: Person, Swallow, Tree {
    
    func thinkSimply() {
        print("隨意找棵樹休息翅膀")
    }
    
    func wanderInWind() {
        print("然后淡然飛向遠方")
    }
    
    func beHeartless() {
        print("當(dāng)枝頭燕子飛走時 再不用留戀張望")
    }
}

// 今生

//class Swallow: Creature {
//    
//    func thinkSimply() {
//        print("簡單思想")
//    }
//    
//    func wanderInWind() {
//        print("風(fēng)中流浪")
//    }
//}
//
//class Tree: Creature {
//    
//    func beHeartless() {
//        print("不長六腑五臟")
//        print("不會寸斷肝腸")
//    }
//}
//
//class Person: Creature {
//    var name: String
//    
//    init(name: String) {
//        self.name = name
//    }
//}
// 今生的小雨康橋(普通人 變不成燕子和樹)
//let xiaoyu: Creature = Person(name: "小雨康橋")

// 來生的小雨康橋(遵循了燕子和樹協(xié)議的人)
let xiaoyu: Creature = Xiaoyu(name: "小雨康橋")
// 判定小雨康橋可不可以變成燕子
if let swallow = xiaoyu as? Swallow {
    swallow.thinkSimply()
    swallow.wanderInWind()
}
else {
    if let person = xiaoyu as? Person {
        print("\(person.name)躲不過感情的墻")
    }
}
// 判定小雨康橋可不可以變成樹
if let tree = xiaoyu as? Tree {
    tree.beHeartless()
}
else {
    if let person = xiaoyu as? Person {
        print("\(person.name)撐不破傷心的網(wǎng)")
    }
}

21點撲克游戲

/**
 花色的枚舉
 
 - Spade:   黑桃
 - Heart:   紅心
 - Club:    草花
 - Diamond: 方塊
 */
enum Suite: String {
    case Spade = "??"
    case Heart = "??"
    case Club = "??"
    case Diamond = "??"
}

/// 一張牌
class Card {
    var suite: Suite
    var face: Int
    
    /**
     初始化方法
     */
    init(suite: Suite, face: Int) {
        self.suite = suite
        self.face = face
    }
    
    /// 牌的信息
    var info: String {
        get {
            var str = suite.rawValue
            switch face {
            case 1: str += "A"
            case 11: str += "J"
            case 12: str += "Q"
            case 13: str += "K"
            default: str += "\(face)"
            }
            return str
        }
    }
}



/// 一副牌
class Poker {
    var cardsArray: [Card] = []
    var currentIndex = 0
    
    init() {
        let suitesArray = [Suite.Spade, .Heart, .Club, .Diamond]
        for suite in suitesArray {
            for face in 1...13 {
                let card = Card(suite: suite, face: face)
                cardsArray.append(card)
            }
        }
    }
    
    /**
     洗牌
     */
    func shuffle() {
        currentIndex = 0
        for i in 0..<cardsArray.count {
            let j = Int(arc4random()) % cardsArray.count
            (cardsArray[i], cardsArray[j]) = (cardsArray[j], cardsArray[i])
        }
    }
    
    /**
     發(fā)牌
     - returns: 返回一張牌或nil
     */
    func deal() -> Card? {
        if hasMoreCards {
            let currentCard = cardsArray[currentIndex]
            currentIndex += 1
            return currentCard
        }
        return nil
    }
    
    /// 還有沒有牌
    var hasMoreCards: Bool {
        get { return currentIndex < cardsArray.count }
    }
}



/// 玩家
class Player {
    var name: String
    var money: Int
    var isBanker: Bool
    var cardsOnHand: [Card] = []
    
    /**
     初始化方法
     - parameter name:  姓名
     - parameter money: 金額
     - parameter isBanker: 是不是莊家(默認不是莊家false)
     */
    init(name: String, money: Int, isBanker: Bool = false) {
        self.name = name
        self.money = money
        self.isBanker = isBanker
    }
    
    /**
     得到一張牌
     - parameter card: 牌
     */
    func getOneCard(card: Card) {
        cardsOnHand.append(card)
    }
    
    /**
     棄牌
     */
    func drop() {
        cardsOnHand.removeAll()
    }
    
    /**
     贏
     - parameter bet: 贏得的金額
     */
    func win(bet: Int) {
        money += bet
    }
    
    /**
     輸
     - parameter bet: 輸?shù)舻慕痤~
     */
    func lose(bet: Int) {
        money -= bet
    }
    
    /// 玩家手上牌的點數(shù)
    var totalPoints: Int {
        get {
            // 對玩家手上的牌按點數(shù)的降序進行排列(方便計算點數(shù)總和)
            let newArray = cardsOnHand.sort { $0.face > $1.face }
            var sum = 0
            for card in newArray {
                switch card.face {
                case 2...9: sum += card.face
                case 10...13: sum += 10
                default: sum += (sum + 11) > 21 ? 1 : 11
                }
            }
            return sum
        }
    }
    
    /// 是否爆炸
    var isBust: Bool {
        get { return totalPoints > 21 }
    }
}



/// 主方法調(diào)用
// 顯示玩家手上的牌
func showPlayerCards(player: Player, showFirst: Bool = false) {
    print(player.name, terminator: ": ")
    for (index, card) in player.cardsOnHand.enumerate() {
        // 莊家手上的第一張牌只顯示背面除非將showFirst設(shè)置為true
        if index == 0 && player.isBanker && !showFirst {
            print("??", terminator: " ")
        }
        else {
            print(card.info, terminator: "")
        }
    }
    print("")
}

// 根據(jù)勝負結(jié)算金額
func changeMoney(player: Player, banker: Player, isPlayerWin: Bool, bet: Int) {
    var finalBet = bet
    if isPlayerWin {
        if player.totalPoints == 21 {
            finalBet *= 2
        }
        player.win(finalBet)
        banker.lose(finalBet)
    }
    else {
        player.lose(finalBet)
        banker.win(finalBet)
    }
}

// 創(chuàng)建兩個玩家
let player = Player(name: "駱昊", money: 1000)
let banker = Player(name: "陽堅", money: 5000, isBanker: true)
// 創(chuàng)建一副牌
let p = Poker()

repeat {
    print("莊家總資產(chǎn)為: ¥\(banker.money)元")
    print("玩家總資產(chǎn)為: ¥\(player.money)元")

    // 每局開始前玩家和莊家先扔掉手中的牌
    player.drop()
    banker.drop()
    
    // 洗牌
    p.shuffle()

    // 玩家和莊家各獲得兩張底牌(一明一暗)
    player.getOneCard(p.deal()!)
    player.getOneCard(p.deal()!)
    banker.getOneCard(p.deal()!)
    banker.getOneCard(p.deal()!)

    // 顯示玩家和莊家手中的牌
    showPlayerCards(banker)
    showPlayerCards(player)

    // 玩家下注
    var bet: Int
    repeat {
        print("請下注: ", terminator: "")
        bet = inputInt()
    } while bet <= 0 || bet > player.money

    // 選擇是否要牌(如果手上的牌已經(jīng)爆炸了就不能要牌了)
    var choice: Int
    repeat {
        print("是否要牌(1. 要牌; 2. 不要): ", terminator: "")
        choice = inputInt()
        if choice == 1 {
            player.getOneCard(p.deal()!)
            // 每要一張牌就顯示一次玩家手上的牌
            showPlayerCards(player)
        }
    } while choice == 1 && !player.isBust

    if player.isBust {  // 玩家爆炸了莊家直接開牌
        print("\(player.name)爆炸了!!!")
        showPlayerCards(banker, showFirst: true)
        changeMoney(player, banker: banker, isPlayerWin: false, bet: bet)
    }
    else {              // 玩家沒有爆炸莊家決定是否要牌
        // 產(chǎn)生一個15-20的點數(shù)作為莊家要牌的期望值
        let expectPoints = Int(arc4random_uniform(6)) + 15
        // 當(dāng)莊家手上的牌沒有達到期望值也沒有爆炸就繼續(xù)要牌
        while banker.totalPoints < expectPoints && !banker.isBust {
            banker.getOneCard(p.deal()!)
        }
        // 莊家要牌結(jié)束后顯示莊家手上的牌
        showPlayerCards(banker, showFirst: true)
        if banker.isBust { // 莊家爆了 玩家沒爆 玩家勝
            print("\(banker.name)爆炸了!!!")
            changeMoney(player, banker: banker, isPlayerWin: true, bet: bet)
        }
        else {              // 莊家沒爆 玩家沒爆 比大小
            // 玩家和莊家點數(shù)相同莊家勝
            if player.totalPoints > banker.totalPoints {
                print("\(player.name)獲得勝利!!!")
                changeMoney(player, banker: banker, isPlayerWin: true, bet: bet)
            }
            else {
                print("\(banker.name)獲得勝利!!!")
                changeMoney(player, banker: banker, isPlayerWin: false, bet: bet)
            }
        }
    }
    print("")
} while player.money > 0 && banker.money > 0

// 有一方錢輸光了游戲就結(jié)束 顯示誰獲得了勝利
print("莊家總資產(chǎn)為: ¥\(banker.money)元")
print("玩家總資產(chǎn)為: ¥\(player.money)元")
if player.money > 0 {
    print("玩家\(player.name)戰(zhàn)勝了莊家\(banker.name)")
}
else {
    print("玩家\(player.name)輸給了莊家\(banker.name)")
}

類和結(jié)構(gòu)體的區(qū)別

  • 區(qū)別1: 結(jié)構(gòu)的對象是值類型, 類的對象是引用類型,值類型在賦值的時候會在內(nèi)存中進行對象的拷貝,引用類型在賦值的時候不會進行對象拷貝只是增加了一個引用
    結(jié)論: 我們自定義新類型時優(yōu)先考慮使用類而不是結(jié)構(gòu)除非我們要定義的是一種底層的數(shù)據(jù)結(jié)構(gòu)(保存其他數(shù)據(jù)的類型)
  • 區(qū)別2: 結(jié)構(gòu)會自動生成初始化方法
  • 區(qū)別3: 結(jié)構(gòu)中的方法在默認情況下是不允許修改結(jié)構(gòu)中的屬性除非加上mutating關(guān)鍵字
var age: Int
mutating func getOlder() {
        age += 1
    }

計算機的硬件由五大部件構(gòu)成:

  • 運算器只祠、控制器兜蠕、存儲器扰肌、輸入設(shè)備、輸出設(shè)備
  • 運算器 + 控制器 => CPU (中央處理器)
  • 存儲器 => 內(nèi)存 (RAM - Random Access Memory)

程序員可以使用的內(nèi)存大致分為五塊區(qū)域:

  • 棧 (stack) - 我們定義的局部變量/臨時變量都是放在棧上
    • 特點: 小熊杨、快
  • 堆 (heap) - 我們創(chuàng)建的對象都是放在堆上的
    • 特點: 大曙旭、慢
  • 靜態(tài)區(qū) (static area)
    • 數(shù)據(jù)段 - 全局量
    • 只讀數(shù)據(jù)段 - 常量
    • 代碼段 - 函數(shù)和方法

函數(shù)的重載

在Swift中同名函數(shù)只要參數(shù)列表不同是可以共存的 這個叫函數(shù)的重載

func changeName(inout name: String) {
    name = "王大錘"
}
// 參數(shù)前面加var的做法在Swift 3中肯定是要廢掉的
func changeName(var stu: Student2) {
    stu.name = "王大錘"
}

var name = "駱昊"
changeName(&name)
print(name)

var stu = Student2(name: "駱昊", age: 35)
changeName(stu)
print(stu.name)

五子棋游戲

setNeedsDisplay()

  • 刷新畫布

委托回調(diào)模式

  • 有的時候某個對象要做某件事情但其自身又沒有能力做這件事情,這個時候就可以使用委托回調(diào)的編程模式讓別的對象來做這件事情
  • 實現(xiàn)委托回調(diào)的編程模式有以下幾個步驟:
    1. 設(shè)計一個協(xié)議(被委托方必須要遵循協(xié)議才能給別的對象當(dāng)委托)
    2. 委托方添加一個屬性其類型是遵循了協(xié)議的被委托方
    3. 自己做不了的事情委托給別的對象來做
    4. 如讓下例的視圖控制器遵循協(xié)議成為被委托方(協(xié)議表能力)
    5. 遵循協(xié)議就必須要實現(xiàn)協(xié)議中的方法(協(xié)議表約定)
    6. 給下例畫布對象綁定委托(self就是視圖控制器對象它遵循了協(xié)議所以有充當(dāng)委托的能力也就是說可以扮演被委托方的角色)

guard大法

- guard !renjuBoard.isGameOver else { return }
!renjuBoard.isGameOver條件成立執(zhí)行下面的代碼晶府,否則執(zhí)行else中代碼桂躏,return表跳出,不執(zhí)行以后代碼
- Swift 2中的guard大法, Swift 3中據(jù)說要廢掉

索引器(subscript)語法 - 可以直接對棋盤對象做下標(biāo)運算來放置棋子

subscript(row: Int, col: Int) -> Bool {
        get { return board[row][col] == .Space }
        set(isBlack) {
            if board[row][col] == .Space {
                board[row][col] = isBlack ? .Black : .White
                isBlackTurn = !isBlackTurn
            }
        }
    }

if renjuBoard[row, col] {  // renjuBoard是一個棋盤類對象
    renjuBoard[row, col] = renjuBoard.isBlackTurn
    setNeedsDisplay()     // 刷新畫布
}   
/**
 棋盤交叉點的狀態(tài)
 
 - Space: 空格
 - Black: 黑棋
 - White: 白棋
 */
enum PointState {
    case Space, Black, White
}

/// 棋盤
class RenjuBoard {
    var board: [[PointState]]
    var isBlackTurn = true
    var isGameOver = false
    
    init() {
        board = [[PointState]](count: 15, repeatedValue: [PointState](count: 15, repeatedValue: .Space))
    }
    
    // 索引器語法 - 可以直接對棋盤對象做下標(biāo)運算來放置棋子
    subscript(row: Int, col: Int) -> Bool {
        get { return board[row][col] == .Space }
        set(isBlack) {
            if board[row][col] == .Space {
                board[row][col] = isBlack ? .Black : .White
                isBlackTurn = !isBlackTurn
            }
        }
    }
    
    func reset() {
        isGameOver = false
        isBlackTurn = true
        for i in 0..<board.count {
            for j in 0..<board[i].count {
                board[i][j] = .Space
            }
        }
    }
    
    func judge(row: Int, _ col: Int) -> Bool {
        return _judgeH(row, col) || _judgeV(row, col) || _judgeX1(row, col) || _judgeX2(row, col)
    }

    private func _judgeH(row: Int, _ col: Int) -> Bool {
        var counter = 1
        var currentCol = col - 1
        while currentCol >= 0 {
            if board[row][currentCol] == board[row][col] {
                counter += 1
                currentCol -= 1
            }
            else {
                break
            }
        }
        currentCol = col + 1
        while currentCol < board.count {
            if board[row][currentCol] == board[row][col] {
                counter += 1
                currentCol += 1
            }
            else {
                break
            }
        }
        return counter >= 5
    }
    
    private func _judgeV(row: Int, _ col: Int) -> Bool {
        var counter = 1
        var currentRow = row - 1
        while currentRow >= 0 {
            if board[currentRow][col] == board[row][col] {
                counter += 1
                currentRow -= 1
            }
            else {
                break
            }
        }
        currentRow = row + 1
        while currentRow < board.count {
            if board[currentRow][col] == board[row][col] {
                counter += 1
                currentRow += 1
            }
            else {
                break
            }
        }
        return counter >= 5
    }
    
    private func _judgeX1(row: Int, _ col: Int) -> Bool {
        var counter = 1
        var currentRow = row - 1
        var currentCol = col - 1
        while currentRow >= 0 && currentCol > 0 {
            if board[currentRow][currentCol] == board[row][col] {
                counter += 1
                currentRow -= 1
                currentCol -= 1
            }
            else {
                break
            }
        }
        currentRow = row + 1
        currentCol = col + 1
        while currentRow < board.count && currentCol < board.count {
            if board[currentRow][currentCol] == board[row][col] {
                counter += 1
                currentRow += 1
                currentCol += 1
            }
            else {
                break
            }
        }
        return counter >= 5
    }
    
    private func _judgeX2(row: Int, _ col: Int) -> Bool {
        var counter = 1
        var currentRow = row - 1
        var currentCol = col + 1
        while currentRow >= 0 && currentCol < board.count {
            if board[currentRow][currentCol] == board[row][col] {
                counter += 1
                currentRow -= 1
                currentCol += 1
            }
            else {
                break
            }
        }
        currentRow = row + 1
        currentCol = col - 1
        while currentRow < board.count && currentCol >= 0 {
            if board[currentRow][currentCol] == board[row][col] {
                counter += 1
                currentRow += 1
                currentCol -= 1
            }
            else {
                break
            }
        }
        return counter >= 5
    }
    
    func draw() {
        let lineBP = UIBezierPath()
        
        // 繪制15條橫線和15條豎線來構(gòu)造一個棋盤
        for i in 0..<board.count {
            lineBP.moveToPoint(CGPointMake(10, 10 + 50 * CGFloat(i)))
            lineBP.addLineToPoint(CGPointMake(710, 10 + 50 * CGFloat(i)))
            lineBP.moveToPoint(CGPointMake(10 + 50 * CGFloat(i), 10))
            lineBP.addLineToPoint(CGPointMake(10 + 50 * CGFloat(i), 710))
        }
        lineBP.stroke()
        
        // 繪制棋盤的邊框
        let rectBP = UIBezierPath(rect: CGRectMake(3, 3, 714, 714))
        rectBP.lineWidth = 6
        rectBP.stroke()
        
        // 繪制天元和星
        let starsRectArray = [
            CGRectMake(155, 155, 10, 10),
            CGRectMake(555, 155, 10, 10),
            CGRectMake(155, 555, 10, 10),
            CGRectMake(555, 555, 10, 10),
            CGRectMake(355, 355, 10, 10)
        ]
        for starRect in starsRectArray {
            let ovalBP = UIBezierPath(ovalInRect: starRect)
            ovalBP.fill()
        }
        
        // 繪制棋盤上的棋子
        for i in 0..<board.count {
            for j in 0..<board[i].count {
                if board[i][j] != .Space {
                    let ovalBP = UIBezierPath(ovalInRect: CGRectMake(-10 + CGFloat(j) * 50, -10 + CGFloat(i) * 50, 40, 40))
                    (board[i][j] == .Black ? UIColor.blackColor() : UIColor.whiteColor()).set()
                    ovalBP.fill()
                }
            }
        }
    }
}




// 有的時候某個對象要做某件事情但其自身又沒有能力做這件事情
// 這個時候就可以使用委托回調(diào)的編程模式讓別的對象來做這件事情
// 實現(xiàn)委托回調(diào)的編程模式有以下幾個步驟:
//  1. 設(shè)計一個協(xié)議(被委托方必須要遵循協(xié)議才能給別的對象當(dāng)委托)
protocol CanvasDelegate: class {
    
    // 協(xié)議里面的方法就是要委托其他對象做的事情
    func showMessage(canvas: Canvas, message: String)
}

class Canvas: UIView {
    // 2. 委托方添加一個屬性其類型是遵循了協(xié)議的被委托方
    weak var delegate: CanvasDelegate?
    
    var renjuBoard = RenjuBoard()
    var isAutoMode = false
    
    func clearBoard() {
        renjuBoard.reset()
        setNeedsDisplay()
    }
    
    func randomMove() {
        let row = Int(arc4random_uniform(15))
        let col = Int(arc4random_uniform(15))
        if renjuBoard[row, col] {
            renjuBoard[row, col] = renjuBoard.isBlackTurn
            setNeedsDisplay()
        }
    }
    
    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        // Swift 2中的guard大法, Swift 3中據(jù)說要廢掉
        guard !isAutoMode else { return }
        // guard !renjuBoard.isGameOver else { return }
        
        if !renjuBoard.isGameOver {
            if let touch = touches.first {
                let point = touch.locationInView(self)
                let row = lround(Double(point.y - 10) / 50)
                let col = lround(Double(point.x - 10) / 50)
                if renjuBoard[row, col] {
                    renjuBoard[row, col] = renjuBoard.isBlackTurn
                    setNeedsDisplay()
                    if renjuBoard.judge(row, col) {
                        renjuBoard.isGameOver = true
                        // 3. 自己做不了的事情委托給別的對象來做
                        delegate?.showMessage(self, message: renjuBoard.isBlackTurn ? "白棋勝" : "黑棋勝")
                    }
                }
            }
        }
    }
    
    override func drawRect(rect: CGRect) {
        renjuBoard.draw()
    }

}




//  4. 讓視圖控制器遵循協(xié)議成為被委托方(協(xié)議表能力)
class ViewController: UIViewController, CanvasDelegate {
    
    var timer: NSTimer?
    var canvas: Canvas!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        canvas = Canvas(frame: CGRectMake(0, 0, 720, 720))
        // canvas.isAutoMode = true
        //  6. 給畫布對象綁定委托(self就是視圖控制器對象它遵循了協(xié)議所以有充當(dāng)委托的能力也就是說可以扮演被委托方的角色)
        canvas.delegate = self
        canvas.center = self.view.center
        canvas.backgroundColor = UIColor(red: 254.0 / 255.0, green: 209.0 / 255.0, blue: 46.0 / 255.0, alpha: 1)
        self.view.addSubview(canvas)
        
        // timer = NSTimer.scheduledTimerWithTimeInterval(0.2, target: canvas, selector: "randomMove", userInfo: nil, repeats: true)
    }
    
    //  5. 遵循協(xié)議就必須要實現(xiàn)協(xié)議中的方法(協(xié)議表約定)
    func showMessage(canvas: Canvas, message: String) {
        let alertController = UIAlertController(title: message, message: "", preferredStyle: .Alert)
        let okAction = UIAlertAction(title: "確定", style: .Default) { action in
            // 此處通過尾隨閉包來定義點擊確定按鈕后要做什么
            canvas.clearBoard()
        }
        alertController.addAction(okAction)
        self.presentViewController(alertController, animated: true, completion: nil)
    }
    
    // deinit在銷毀對象的時候調(diào)用
    deinit {
        // 銷毀計時器
        timer?.invalidate()
    }
}

對象引用計數(shù)

?
構(gòu)造器:

  • 指派構(gòu)造器(designated)
  • 遍歷構(gòu)造器(convenience)
convenience init() {
        self.init(name: "無名氏", age: 20)
    }
    deinit {
        print("人嗝屁了!")
    }
  • 必要構(gòu)造器
required init(name: String, age: Int) {
        print("創(chuàng)建一個人!")
        self.name = name
        self.age = age
    }
class GrandFather {
    
}

class Father: GrandFather {
    
}

class Son: Father { 
    override init() {
        // 可以調(diào)用Father中的初始化方法
        // 不能調(diào)用GrandFather中的初始化方法
    }
}

class Person {
    var name: String
    var age: Int
    
    // 指派構(gòu)造器前面加上required可以將構(gòu)造器指定為必要構(gòu)造器
    // 所謂的必要構(gòu)造器意味著子類也要提供一模一樣的構(gòu)造器
    // 指派構(gòu)造器(designated)
    required init(name: String, age: Int) {
        print("創(chuàng)建一個人!")
        self.name = name
        self.age = age
    }
    
    // 便利構(gòu)造器(convenience)
    convenience init() {
        self.init(name: "無名氏", age: 20)
    }
    
    deinit {
        print("人嗝屁了!")
    }
}

class Student: Person {
    var major: String
    
    required init(name: String, age: Int) {
        major = "未知"
        super.init(name: name, age: age)
    }
    
    convenience init(name: String, age: Int, major: String) {
        // 下面的語句必須寫在調(diào)用自己的初始化方法之后否則major屬性會被賦上不正確的值
        // self.major = major
        self.init(name: name, age: age)
        self.major = major
        // 初始化的第一階段
        //  1. 初始化自己特有的屬性
//        self.major = major
//        // 子類只能調(diào)用直接父類的構(gòu)造器
//        // 子類構(gòu)造器必須調(diào)用父類的非便利構(gòu)造器(指派構(gòu)造器)
//        // super.init()    // compiler error
//        //  2. 調(diào)用父類的初始化方法
//        super.init(name: name, age: age)
//        // 初始化的第二階段
//        // 此處可以調(diào)用對象的方法因為對象已經(jīng)完成了初始化
//        study()
    }
    
    func study() {
        print("\(name)正在學(xué)習(xí).")
    }
    
    deinit {
        print("學(xué)生對象嗝屁了!")
    }
}

class Teacher: Person {
    
    
    deinit {
        print("老師對象嗝屁了!")
    }
}

//// 創(chuàng)建一個學(xué)生對象 然后用stu1去引用它 所以此時學(xué)生對象引用計數(shù)為1
//var stu1: Student? = Student()
//// 此處沒有創(chuàng)建新的學(xué)生對象 原來的學(xué)生對象的引用計數(shù)+1
//var stu2 = stu1
//// 同上 原來的學(xué)生對象的引用計數(shù)+1
//var stu3 = stu2
//
//// 學(xué)生對象引用計數(shù)-1
//stu1 = nil
//// 學(xué)生對象引用計數(shù)-1
//stu2 = nil
//// 學(xué)生對象引用計數(shù)-1
//// 當(dāng)學(xué)生對象引用計數(shù)為0時 ARC會自動清理內(nèi)存釋放學(xué)生對象
//// ARC即時性的內(nèi)存清理 優(yōu)于Java中的Garbage Collection(垃圾回收)
//stu3 = nil

// var stu1: Student? = Student()
// weak修飾的引用(弱引用)不會增加引用計數(shù) 默認是強引用(會增加引用計數(shù))
// weak var stu2 = stu1
// weak var stu3 = stu2

// stu1 = nil
// 如果想釋放內(nèi)存 程序員可以手動將一個引用賦值為nil

// func foo() {
    // stu是一個局部變量/常量 在函數(shù)調(diào)用結(jié)束后局部變量就消失了
    // 所以學(xué)生對象的引用計數(shù)也就變成0了 所以會被ARC釋放掉
    // let stu = Student()
    // print(stu)
// }

// foo()

// 棧 - FILO 先進后出的結(jié)構(gòu)
// 創(chuàng)建任何子類對象的時候一定是先創(chuàng)建了父類對象
// var stu: Person = Student()
// 引用轉(zhuǎn)移(會導(dǎo)致原來對象上的引用計數(shù)-1 新對象引用計數(shù)+1)
// stu = Teacher()
// stu = Person()

// Swift的自動釋放池
// 通過向autoreleasepool函數(shù)中傳入一個閉包來實現(xiàn)
// autoreleasepool { () -> () in
    // 自動釋放池中的對象引用在池的邊界會收到引用計數(shù)-1的消息
    // 將來做iOS開發(fā)時如果某個地方會創(chuàng)建很多的臨時對象
    // 那么最好在此處設(shè)置一個自動釋放池避免內(nèi)存瞬時峰值過高造成閃退
    // let stu1 = Student()
    // let stu2 = stu1
// }
// 離開自動釋放池時 stu1會收到引用計數(shù)-1消息 stu2也會收到引用計數(shù)-1消息

// 如果程序中出現(xiàn)了類與類之間雙向關(guān)聯(lián)關(guān)系 必須要將其中一端設(shè)置為weak引用
// 否則將會形成循環(huán)引用導(dǎo)致ARC無法釋放內(nèi)存
//class Emp {
//    // 推薦使用
//    // 如果允許使用可空類型通常使用weak來破除循環(huán)引用
//    // 如果員工關(guān)聯(lián)的部門對象被釋放了那么dept會被賦值為nil
//    // 如果要繼續(xù)給dept對象發(fā)消息程序不會崩潰
//    // weak var dept: Dept?
//    
//    // 謹慎使用
//    // 如果不允許使用可空類型就必須使用unowned來破除循環(huán)引用
//    // 需要注意的是如果員工對象關(guān)聯(lián)的部門對象被釋放了
//    // 如果還要通過員工對象去操作它所關(guān)聯(lián)的部門對象將導(dǎo)致程序崩潰
//    // EXC_BAD_ACCESS
//    unowned var dept: Dept
//    
//    init(dept: Dept) {
//        print("創(chuàng)建一個員工")
//        self.dept = dept
//    }
//    
//    deinit {
//        print("銷毀一個員工")
//    }
//}
//
//class Dept {
//    var manager: Emp?
//    
//    init() {
//        print("創(chuàng)建一個部門")
//    }
//
//    deinit {
//        print("銷毀一個部門")
//    }
//}
//
//func bar() {
//    // let person = Person()
//    let dept = Dept()
//    let emp = Emp(dept: dept)
//    dept.manager = emp
//}
//
//bar()

泛型 (generic) - 讓類型不再是程序中的硬代碼(寫死的東西)

func bubbleSort<T: Comparable>(array: [T]) -> [T] {
    var newArray = array
    for i in 0..<newArray.count - 1 {
        var swapped = false
        for j in 0..<newArray.count - 1 - i {
            if newArray[j] > newArray[j + 1] {
                mySwap(&newArray[j], &newArray[j + 1])
                swapped = true
            }
        }
        if !swapped {
            break
        }
    }
    return newArray
}

// 定義一個虛擬類型T, 調(diào)用函數(shù)時根據(jù)傳入的參數(shù)類型來決定T到底是什么
func mySwap<T>(inout a: T, inout _ b: T) {
    let temp = a
    a = b
    b = temp
}

// 泛型限定
// <T: Comparable>限定T類型必須是遵循了Comparable協(xié)議的類型
func myMin<T: Comparable>(a: T, _ b: T) -> T {
    return a < b ? a : b
}


let array1: Array<Int> = [23, 45, 99, 12, 68, 51, 70, 66]
let array2 = bubbleSort(array1)
print(array1)
print(array2)

let array3 = ["hello", "zoo", "kiss", "apple", "good"]
let array4 = bubbleSort(array3)
print(array3)
print(array4)

class Student: Comparable {
    var name: String
    var age: Int
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

func ==(one: Student, two: Student) -> Bool {
    return one.name == two.name
}

func <(one: Student, two: Student) -> Bool {
    return one.name < two.name
}

func <=(one: Student, two: Student) -> Bool {
    return one.name <= two.name
}

func >(one: Student, two: Student) -> Bool {
    return one.name > two.name
}

func >=(one: Student, two: Student) -> Bool {
    return one.name >= two.name
}

var stu1 = Student(name: "Luo Hao", age: 35)
var stu2 = Student(name: "Wang Dachui", age: 18)

let minStu = myMin(stu1, stu2)
print(minStu.name)

var x = "hello", y = "good"
mySwap(&x, &y)
print(x, y)
print(myMin(x, y))

var a = 3.5, b = 1.2345
mySwap(&a, &b)
print(a, b)
print(myMin(a, b))

var c = 10, d = 100
mySwap(&c, &d)
print(c, d)
print(myMin(c, d))

// Swift中的類川陆、結(jié)構(gòu)和枚舉都可以使用泛型
struct Stack<T> {
    var data: [T] = []
    
    // 入棧
    mutating func push(elem: T) {
        data.append(elem)
    }
    
    // 出棧
    mutating func pop() -> T {
        return data.removeLast()
    }
    
    var isEmpty: Bool {
        get { return data.count == 0 }
    }
}

var stack = Stack<String>()
stack.push("hello")
stack.push("good")
stack.push("zoo")

while !stack.isEmpty {
    print(stack.pop())
}

分數(shù)錯誤處理等邊角知識

// 短除法(歐幾里得算法)
// x和y的最大公約數(shù)跟y%x和x的最大公約數(shù)是一樣的
// Greatest Common Divisor
func gcd(x: Int, _ y: Int) -> Int {
    if x > y {
        return gcd(y, x)
    }
    else if y % x != 0 {
        return gcd(y % x, x)
    }
    else {
        return x
    }
}

// 定義一個遵循ErrorType協(xié)議的枚舉
// 通過不同的case定義程序中可能出現(xiàn)的若干種異常狀況
enum FractionError: ErrorType {
    case ZeroDenominator    // 分母為0
    case DivideByZero       // 除以0
}

class Fraction {
    private var _num: Int
    private var _den: Int
    
    var info: String {
        get {
            return _num == 0 || _den == 1 ? "\(_num)" : "\(_num)/\(_den)"
        }
    }
    
    // 如果一個方法拋出了異常 那么在聲明方法時必須要寫上throws關(guān)鍵字
    // throws關(guān)鍵字是提醒方法的調(diào)用者方法可能會出狀況 調(diào)用時要寫try
    init(num: Int, den: Int) throws {
        _num = num
        _den = den
        if _den == 0 {
            // 如果程序中出現(xiàn)問題就拋出錯誤(異常)
            // 被throw關(guān)鍵字拋出的必須是遵循ErrorType協(xié)議的東西
            throw FractionError.ZeroDenominator
        }
        else {
            simplify()
            normalize()
        }
    }
    
    func add(other: Fraction) -> Fraction {
        // 如果能夠確保方法調(diào)用時不出異常那么可以在try關(guān)鍵字后加!
        // 這樣就可以在不寫do...catch的情況下調(diào)用可能出狀況的方法
        return try! Fraction(num: _num * other._den + other._num * _den, den: _den * other._den)
    }
    
    func sub(other: Fraction) -> Fraction {
        return try! Fraction(num: _num * other._den - other._num * _den, den: _den * other._den)
    }
    
    func mul(other: Fraction) -> Fraction {
        return try! Fraction(num: _num * other._num, den: _den * other._den)
    }
    
    func div(other: Fraction) throws -> Fraction {
        if other._num == 0 {
            throw FractionError.DivideByZero
        }
        return try! Fraction(num: _num * other._den, den: _den * other._num)
    }
    
    func normalize() -> Fraction {
        if _den < 0 {
            _num = -_num
            _den = -_den
        }
        return self
    }
    
    func simplify() -> Fraction {
        if _num == 0 {
            _den = 1
        }
        else {
            let x = abs(_num)
            let y = abs(_den)
            let g = gcd(x, y)
            _num /= g
            _den /= g
        }
        return self
    }
}

// 運算符重載(為自定義的類型定義運算符)

func +(one: Fraction, two: Fraction) -> Fraction {
    return one.add(two)
}

func -(one: Fraction, two: Fraction) -> Fraction {
    return one.sub(two)
}

func *(one: Fraction, two: Fraction) -> Fraction {
    return one.mul(two)
}

func /(one: Fraction, two: Fraction) throws -> Fraction {
    return try one.div(two)
}






class Person {
    var car: Car?
}

class Car {
    var engine: Engine?
}

class Engine {
    var id: String?
}

let p = Person()
// 可空鏈語法(適用于開火車式的編程)
print(p.car?.engine?.id?.uppercaseString)

//func <(one: Student, two: Student) -> Bool {
//    return one.name < two.name
//}
//
//let stuArray = [
//    Student(name: "Wang Dachui", age: 24),
//    Student(name: "Lee Xiaolong", age: 49),
//    Student(name: "Zhang Nima", age: 18),
//    Student(name: "Guo Jing", age: 26)
//]
//
//// let newArray = stuArray.sort { $0.age > $1.age }
//let newArray = stuArray.sort(<)
//for stu in newArray {
//    print("\(stu.name): \(stu.age)")
//}

func foo() {
    // 如果能夠保證代碼不出錯可以在try后面加!
    // 如果不確定代碼是否出錯可以在try后面加?
    // 需要注意的是有?的地方會產(chǎn)生Optional(可空類型)
    // 稍后可能還需要對可空類型進行拆封, 拆封方式有二: 
    //  1. 不安全的做法: xxx!
    //  2. 安全的做法: 用if let = xxx { }進行拆封
    let f1 = try? Fraction(num: 3, den: 0)
    let f2 = try? Fraction(num: 0, den: 9)
    
    if let a = f1, b = f2 {
        let f3 = a + b
        print(f3.info)
    }
    else {
        print("無效的分數(shù)無法進行加法運算")
    }
}

foo()


// 對于可能出狀況的代碼要放在do...catch中執(zhí)行
// 在可能出狀況的方法前還要寫上try表示嘗試著執(zhí)行
// 如果在do中沒有出現(xiàn)任何狀況那么catch就不會執(zhí)行
// 如果do中出現(xiàn)了狀況代碼就不會再向下繼續(xù)執(zhí)行而是轉(zhuǎn)移到catch中
// 在do的后面可以跟上多個catch用于捕獲不同的異常狀況 但是最多只有一個catch會被執(zhí)行
//do {
//    let f1 = try Fraction(num: 3, den: 4)
//    let f2 = try Fraction(num: 0, den: 9)
//
//    print(f1.info)
//    print(f2.info)
//
//    let f3 = f1 + f2
//    print(f3.info)
//    let f4 = f1 - f2
//    print(f4.info)
//    let f5 = f1 * f2
//    print(f5.info)
//    let f6 = try f1 / f2
//    print(f6.info)
//}
//catch FractionError.ZeroDenominator {
//    print("瓜西西的, 分母不能為0!!!")
//}
//catch FractionError.DivideByZero {
//    print("卵球了, 除以0是不行的!!!")
//}
//catch {
//    print("出錯了! 我也不知道什么問題")
//}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末剂习,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子较沪,更是在濱河造成了極大的恐慌鳞绕,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尸曼,死亡現(xiàn)場離奇詭異们何,居然都是意外死亡,警方通過查閱死者的電腦和手機控轿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門冤竹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人茬射,你說我怎么就攤上這事鹦蠕。” “怎么了在抛?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵钟病,是天一觀的道長。 經(jīng)常有香客問我,道長档悠,這世上最難降的妖魔是什么廊鸥? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮辖所,結(jié)果婚禮上惰说,老公的妹妹穿的比我還像新娘。我一直安慰自己缘回,他們只是感情好吆视,可當(dāng)我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著酥宴,像睡著了一般啦吧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拙寡,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天授滓,我揣著相機與錄音,去河邊找鬼肆糕。 笑死般堆,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的诚啃。 我是一名探鬼主播淮摔,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼始赎!你這毒婦竟也來了和橙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤造垛,失蹤者是張志新(化名)和其女友劉穎魔招,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體筋搏,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡仆百,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了奔脐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俄周。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖髓迎,靈堂內(nèi)的尸體忽然破棺而出峦朗,到底是詐尸還是另有隱情,我是刑警寧澤排龄,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布波势,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏尺铣。R本人自食惡果不足惜拴曲,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望凛忿。 院中可真熱鬧澈灼,春花似錦、人聲如沸店溢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽床牧。三九已至荣回,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間戈咳,已是汗流浹背心软。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留除秀,地道東北人糯累。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓算利,卻偏偏與公主長得像册踩,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子效拭,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容