欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Swift中的指針操作詳解

 更新時(shí)間:2017年01月19日 11:51:16   作者:Swiftyper  
從傳統(tǒng)的C代碼和與之無縫配合的Objective-C代碼遷移到Swift并非小工程,我們的代碼庫(kù)肯定會(huì)時(shí)不時(shí)出現(xiàn)一些和C協(xié)作的地方,如果想要繼續(xù)使用那些C API的話,了解一些基本的Swift指針操作和使用的知識(shí)會(huì)很有幫助。下面通過這篇文章一起來學(xué)習(xí)下吧。

前言

Objective-C和C語(yǔ)言經(jīng)常需要使用到指針。Swift中的數(shù)據(jù)類型由于良好的設(shè)計(jì),使其可以和基于指針的C語(yǔ)言API無縫混用。但是語(yǔ)法上有很大的差別。

默認(rèn)情況下,Swift 是內(nèi)存安全的,這意味著它禁止我們直接操作內(nèi)存,并且確保所有的變量在使用前都已經(jīng)被正確地初始化了。但是,Swift 也提供了我們使用指針直接操作內(nèi)存的方法,直接操作內(nèi)存是很危險(xiǎn)的行為,很容易就出現(xiàn)錯(cuò)誤,因此官方將直接操作內(nèi)存稱為 “unsafe 特性”。

一旦我們開始直接操作內(nèi)存,一切就得靠我們自己了,因?yàn)樵谶@種情況下編譯能給我們提供的幫助實(shí)在不多。正常情況下,我們?cè)谂c C 進(jìn)行交互,或者我們需要挖掘 Swift 內(nèi)部實(shí)現(xiàn)原理的時(shí)候會(huì)需要使用到這個(gè)特性。

Memory Layout

Swift 提供了 MemoryLayout 來檢測(cè)特定類型的大小以及內(nèi)存對(duì)齊大?。?/p>

MemoryLayout<Int>.size // return 8 (on 64-bit)
MemoryLayout<Int>.alignment // return 8 (on 64-bit)
MemoryLayout<Int>.stride // return 8 (on 64-bit)
MemoryLayout<Int16>.size // return 2
MemoryLayout<Int16>.alignment // return 2
MemoryLayout<Int16>.stride // return 2
MemoryLayout<Bool>.size // return 2
MemoryLayout<Bool>.alignment // return 2
MemoryLayout<Bool>.stride // return 2
MemoryLayout<Float>.size // return 4
MemoryLayout<Float>.size // return 4
MemoryLayout<Float>.alignment // return 4
MemoryLayout<Double>.stride // return 8
MemoryLayout<Double>.alignment // return 8
MemoryLayout<Double>.stride // return 8

MemoryLayout<Type> 是一個(gè)用于在編譯時(shí)計(jì)算出特定類型(Type)的 size, alignment 以及 stride 的泛型類型。返回的數(shù)值以字節(jié)為單位。例如 Int16 類型的大小為 2 個(gè)字節(jié),內(nèi)存對(duì)齊為 2 個(gè)字節(jié)以及當(dāng)我們需要連續(xù)排列多個(gè) Int16 類型時(shí),每一個(gè) Int16 所需要占用的大小(stride)為 2 個(gè)字節(jié)。所有基本類型的 stride 都與 size 是一致的。

接下來,看看結(jié)構(gòu)體類型的 MemoryLayout:

struct EmptyStruct {}
MemoryLayout<EmptyStruct>.size // returns 0
MemoryLayout<EmptyStruct>.alignment // returns 1
MemoryLayout<EmptyStruct>.stride // returns 1
struct SampleStruct {
 let number: UInt32
 let flag: Bool
}
MemoryLayout<SampleStruct>.size // returns 5
MemoryLayout<SampleStruct>.alignment // returns 4
MemoryLayout<SampleStruct>.stride // returns 8

空結(jié)構(gòu)體的大小為 0,內(nèi)存對(duì)齊為 1, 表明它可以存在于任何一個(gè)內(nèi)存地址上。有趣的是 stride 為 1,這是因?yàn)楸M管結(jié)構(gòu)為空,但是當(dāng)我們使用它創(chuàng)建一個(gè)實(shí)例的時(shí)候,它也必須要有一個(gè)唯一的地址。

對(duì)于 SampleStruct,它所占的大小為 5,但是 stride 為 8。這是因?yàn)榫幾g需要為其填充空白的邊界,使其符合它的 4 字節(jié)內(nèi)存邊界對(duì)齊。

再來看看類:

class EmptyClass {}
MemoryLayout<EmptyClass>.size // returns 8 (on 64-bit)
MemoryLayout<EmptyClass>.alignment // returns 8 (on 64-bit)
MemoryLayout<EmptyClass>.stride // returns 8 (on 64-bit)
class SampleClass {
 let number: Int64 = 0
 let flag: Bool = false
}
MemoryLayout<SampleClass>.size // returns 8 (on 64-bit)
MemoryLayout<SampleClass>.aligment // returns 8 (on 64-bit)
MemoryLayout<SampleClass>.stride // returns 8 (on 64-bit)

由于類都是引用類型,所以它所有的大小都是 8 字節(jié)。

關(guān)于 MemoryLayout 的更多詳細(xì)信息可以參考 Mike Ash 的演講。

指針

一個(gè)指針就是對(duì)一個(gè)內(nèi)存地址的封裝。在 Swift 當(dāng)中直接操作指針的類型都有一個(gè) “unsafe” 前綴,所以它的指針類型稱為 UnsafePointer。這個(gè)前綴似乎看起來很令人惱火,不過這是 Swift 在提醒你,你現(xiàn)在正在跨越雷池,編譯器不會(huì)對(duì)這種操作進(jìn)行檢查,你需要對(duì)自己的代碼承擔(dān)全部的責(zé)任。

Swift 中包含了一打類型的指針類型,每個(gè)類型都有它們的作用和目的,使用適當(dāng)?shù)闹羔橆愋涂梢苑乐瑰e(cuò)誤的發(fā)生并且更清晰地表達(dá)開發(fā)者的意圖,防止未定義行為的產(chǎn)生。

Swift 的指針類型使用了很清晰的命名,我們可以通過名字知道這是一個(gè)什么類型的指針??勺兓蛘卟豢勺?,原生(raw)或者有類型的,是否是緩沖(buffer)類型,這三種特性總共組合出了 8 種指針類型。

接下來的幾個(gè)小節(jié)會(huì)詳細(xì)介紹這幾種指針類型。

使用原生(Raw)指針

在 Playground 中添加如下代碼:

// 1
let count = 2
let stride = MemoryLayout<Int>.stride
let alignment = MemoryLayout<Int>.alignment
let byteCount = stride * count
 
// 2
do {
 print("Raw pointers")
 
 // 3
 let pointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
 // 4
 defer {
 pointer.deallocate(bytes: byteCount, alignedTo: alignment)
 }
 
 // 5
 pointer.storeBytes(of: 42, as: Int.self)
 pointer.advanced(by: stride).storeBytes(of: 6, as: Int.self)
 pointer.load(as: Int.self)
 pointer.advanced(by: stride).load(as: Int.self)
 
 // 6
 let bufferPointer = UnsafeRawBufferPointer(start: pointer, count: byteCount)
 for (index, byte) in bufferPointer.enumerated() {
 print("byte \(index): \(byte)")
 }
}

在這個(gè)代碼段中,我們使用了 Unsafe Swift 指針去存儲(chǔ)和讀取兩個(gè)整型數(shù)值。

接下來是對(duì)這段代碼的解釋:

1、聲明了接下來都會(huì)用到的幾個(gè)常量:

  • count 表示了我們要存儲(chǔ)的整數(shù)的個(gè)數(shù)
  • stride 表示了 Int 類型的 stride
  • alignment 表示了 Int 類型的內(nèi)存對(duì)齊
  • byteCount 表示占用的全部字節(jié)數(shù)

2、使用 do 來增加一個(gè)作用域,讓我們可以在接下的示例中復(fù)用作用域中的變量名

3、使用 UnsafeMutableRawPointer.allocate 方法來分配所需的字節(jié)數(shù)。我們使用了 UnsafeMutableRawPointer,它的名字表明這個(gè)指針可以用來讀取和存儲(chǔ)(改變)原生的字節(jié)。

4、使用 defer 來保證內(nèi)存得到正確地釋放。操作指針的時(shí)候,所有內(nèi)存都需要我們手動(dòng)進(jìn)行管理。

5、storeBytes load 方法用于存儲(chǔ)和讀取字節(jié)。第二個(gè)整型數(shù)值的地址通過對(duì) pointer 的地址前進(jìn) stride 來得到。因?yàn)橹羔橆愋褪?Strideable 的,我們也可以直接使用指針?biāo)阈g(shù)運(yùn)算 (pointer+stride).storeBytes(of: 6, as: Int.self)。

6、UnsafeRawBufferPointer 類型以一系列字節(jié)的形式來讀取內(nèi)存。這意味著我們可以這些字節(jié)進(jìn)行迭代,對(duì)其使用下標(biāo),或者使用 filter,map 以及 reduce 這些很酷的方法。緩沖類型指針使用了原生指針進(jìn)行初始化。

使用類型指針

我們可以使用類型指針實(shí)現(xiàn)跟上面代碼一樣的功能,并且更簡(jiǎn)單:

do {
 print("Typed pointers")
 
 let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
 pointer.initialize(to: 0, count: count)
 defer {
 pointer.deinitialize(count: count)
 pointer.deallocate(capacity: count)
 }
 
 pointer.pointee = 42
 pointer.advanced(by: 1).pointee = 6
 pointer.pointee
 pointer.advanced(by: 1).pointee
 
 let bufferPointer = UnsafeBufferPointer(start: pointer, count: count)
 for (index, value) in bufferPointer.enumerated() {
 print("value \(index): \(value)")
 }
}

注意到以下幾點(diǎn)不同:

  • 我們使用了 UnsafeMutablePointer.allocate 進(jìn)行內(nèi)存的分配。指定的泛型參數(shù)讓 Swift 知道我們將會(huì)使用這個(gè)指針來存儲(chǔ)和讀取 Int 類型的值。
  • 在使用類型指針前需要對(duì)其進(jìn)行初始化,并在使用后銷毀。這兩個(gè)功能分別是使用 initialize deinitialize 方法。
  • 類型指針提供了 pointee 屬性,它可以以類型安全的方式讀取和存儲(chǔ)值。
  • 當(dāng)需要指針前進(jìn)的時(shí)候,我們只需要指定想要前進(jìn)的個(gè)數(shù)。類型指針會(huì)自動(dòng)根據(jù)它所指向的數(shù)值類型來計(jì)算 stride 值。同樣的,我們可以直接對(duì)指針進(jìn)行算術(shù)運(yùn)算 (pointer + 1).pointee = 6
  • 有類型的緩沖型指針也會(huì)直接操作數(shù)值,而非字節(jié)

將原生指針轉(zhuǎn)換為類型指針

類型指針并不總是使用初始化得到的,它們可以從原生指針中轉(zhuǎn)化而來。

在 Playground 中添加如下代碼:

do {
 print("Converting raw pointers to typed pointers")
 
 let rawPointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
 defer {
 rawPointer.deallocate(bytes: byteCount, alignedTo: alignment)
 }
 
 let typedPointer = rawPointer.bindMemory(to: Int.self, capacity: count)
 typedPointer.initialize(to: 0, count: count)
 defer {
 typedPointer.deinitialize(count: count)
 }
 
 typedPointer.pointee = 42
 typedPointer.advanced(by: 1).pointee = 6
 typedPointer.pointee
 typedPointer.advanced(by: 1).pointee
 
 let bufferPointer = UnsafeBufferPointer(start: typedPointer, count: count)
 for (index, value) in bufferPointer.enumerated() {
 print("value \(index): \(value)")
 }
}

這段代碼與上一段類似,除了它先創(chuàng)建了原生指針。我們通過將內(nèi)存綁定(binding)到指定的類型上來創(chuàng)建類型指針。通過對(duì)內(nèi)存的綁定,我們可以通過類型安全的方法來訪問它。將我們手動(dòng)創(chuàng)建類型指針的時(shí)候,系統(tǒng)其實(shí)自動(dòng)幫我們進(jìn)行了內(nèi)存綁定。

獲取一個(gè)實(shí)例的字節(jié)

很多時(shí)候我們需要從一個(gè)現(xiàn)存的實(shí)例里獲取它的字節(jié)。這時(shí)可以使用 withUnsafeBytes(of:) 方法。

在 Playground 中添加如下代碼:

do {
 print("Getting the bytes of an instance")
 
 var sampleStruct = SampleStruct(number: 25, flag: true)
 
 withUnsafeBytes(of: &sampleStruct) { bytes in
 for byte in bytes {
 print(byte)
 }
 }
}

這段代碼會(huì)打印出 SampleStruct 實(shí)例的原生字節(jié)。withUnsafeBytes(of:) 方法可以獲取到 UnsafeRawBufferPointer并傳入閉包中供我們使用。

withUnsafeBytes 同樣適合用 Array Data 的實(shí)例。

使用 Swift 操作指針的三大原則

當(dāng)我們使用 Swift 操作指針的時(shí)候必須加倍小心,防止寫出未定義行為的代碼。下面是幾個(gè)壞代碼的示例。

不要從 withUnsafeBytes 中返回指針

 // Rule #1
do {
 print("1. Don't return the pointer from withUnsafeBytes!")
 
 var sampleStruct = SampleStruct(number: 25, flag: true)
 
 let bytes = withUnsafeBytes(of: &sampleStruct) { bytes in
 return bytes // strange bugs here we come ☠️☠️☠️
 }
 
 print("Horse is out of the barn!", bytes) /// undefined !!!
}

絕對(duì)不要讓指針逃出 withUnsafeBytes(of:) 的作用域范圍。這樣的代碼會(huì)成為定時(shí)炸彈,你永遠(yuǎn)不知道它什么時(shí)候可以用,而什么時(shí)候會(huì)崩潰。

一次只綁定一種類型

// Rule #2
do {
 print("2. Only bind to one type at a time!")
 
 let count = 3
 let stride = MemoryLayout<Int16>.stride
 let alignment = MemoryLayout<Int16>.alignment
 let byteCount = count * stride
 
 let pointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
 
 let typedPointer1 = pointer.bindMemory(to: UInt16.self, capacity: count)
 
 // Breakin' the Law... Breakin' the Law (Undefined behavior)
 let typedPointer2 = pointer.bindMemory(to: Bool.self, capacity: count * 2)
 
 // If you must, do it this way:
 typedPointer1.withMemoryRebound(to: Bool.self, capacity: count * 2) {
 (boolPointer: UnsafeMutablePointer<Bool>) in
 print(boolPointer.pointee) // See Rule #1, don't return the pointer
 }
}
**絕對(duì)不要**讓一個(gè)內(nèi)存同時(shí)綁定兩個(gè)不同的類型。如果你需要臨時(shí)這么做,可以使用 `withMemoryRebound(to:capacity:)` 來對(duì)內(nèi)存進(jìn)行重新綁定。并且,這條規(guī)則也表明了不要將一個(gè)基本類型(如 Int)重新綁定到一個(gè)自定義類型(如 class)上。不要做這種傻事。
### 不要操作超出范圍的內(nèi)存
```swift
// Rule #3... wait
do {
 print("3. Don't walk off the end... whoops!")
 
 let count = 3
 let stride = MemoryLayout<Int16>.stride
 let alignment = MemoryLayout<Int16>.alignment
 let byteCount = count * stride
 
 let pointer = UnsafeMutableRawPointer.allocate(bytes: byteCount, alignedTo: alignment)
 let bufferPointer = UnsafeRawBufferPointer(start: pointer, count: byteCount + 1) // OMG +1????
 
 for byte in bufferPointer {
 print(byte) // pawing through memory like an animal
 }
}

這是最糟糕的一種錯(cuò)誤了,請(qǐng)?jiān)偃龣z查你的代碼,保證不要有這種情況出現(xiàn)。切記。

示例:隨機(jī)數(shù)生成

隨機(jī)數(shù)在很多地方都有重要的作用,從游戲到機(jī)器學(xué)習(xí)。macOS 提供了 arc4random 方法用于隨機(jī)數(shù)生成。不幸的是,這個(gè)方法無法在 Linux 上使用。并且,arc4random 方法只提供了 UInt32 類型的隨機(jī)數(shù)。事實(shí)上,/dev/urandom 這個(gè)設(shè)備文件中就提供了無限的隨機(jī)數(shù)。

這一小節(jié)中,我們將使用指針讀取這個(gè)文件,并產(chǎn)生完全類型安全的隨機(jī)數(shù)。

創(chuàng)建一個(gè)新 Playground,命名為 RandomNumbers,并確保選擇了 macOS 平臺(tái)。

創(chuàng)建完成后,添加如下代碼:

import Foundation
 
enum RandomSource {
 
 static let file = fopen("/dev/urandom", "r")!
 static let queue = DispatchQueue(label: "random")
 
 static func get(count: Int) -> [Int8] {
 let capacity = count + 1 // fgets adds null termination
 var data = UnsafeMutablePointer<Int8>.allocate(capacity: capacity)
 defer {
 data.deallocate(capacity: capacity)
 }
 queue.sync {
 fgets(data, Int32(capacity), file)
 }
 return Array(UnsafeMutableBufferPointer(start: data, count: count))
 }
}

為了確保整個(gè)系統(tǒng)中只存在一個(gè) file 變量,我們對(duì)其使用了 static 修飾符。系統(tǒng)會(huì)在我們的進(jìn)程結(jié)束時(shí)關(guān)閉文件。因?yàn)槲覀冇锌赡茉诙鄠€(gè)線程中同時(shí)獲取隨機(jī)數(shù),所以需要使用一個(gè)串行的 GCD 隊(duì)列來進(jìn)行保護(hù)。

get 函數(shù)是所有功能完成的地方。首先,我們根據(jù)傳入的大小分配了必要的內(nèi)存,注意這里需要 +1 是因?yàn)?fets 函數(shù)總是以 \0 結(jié)束。接下來,我們就使用 fgets 函數(shù)從文件中讀取數(shù)據(jù),確保我們?cè)诖嘘?duì)列中進(jìn)行讀取操作。最后,我們先將數(shù)據(jù)封裝為一個(gè) UnsafeMutableBufferPointer,并將其轉(zhuǎn)化為一個(gè)數(shù)組。

在 playground 的最后添加如下代碼:

extension Integer {
 
 static var randomized: Self {
 let numbers = RandomSource.get(count: MemoryLayout<Self>.size)
 return numbers.withUnsafeBufferPointer { bufferPointer in
 return bufferPointer.baseAddress!.withMemoryRebound(to: Self.self, capacity: 1) {
 return $0.pointee
 }
 }
 }
 
}
 
Int8.randomized
UInt8.randomized
Int16.randomized
UInt16.randomized
Int16.randomized
UInt32.randomized
Int64.randomized
UInt64.randomized

這里我們?yōu)?Integer 協(xié)議添加了一個(gè)靜態(tài)屬性,并為其提供了默認(rèn)實(shí)現(xiàn)。我們首先獲取了隨機(jī)數(shù),隨后我們將獲得字節(jié)數(shù)組重新綁定為所需要的類型,然后返回它的值。簡(jiǎn)單!

就這樣,我們使用 unsafe Swift 實(shí)現(xiàn)了一個(gè)類型安全的隨機(jī)器生成方法。

在日常開發(fā)中,我們并不會(huì)接觸到很多直接操作內(nèi)存的情境。但是掌握它的操作,能讓我們?cè)谂龅筋愃拼a里更加從容。

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,如果有疑問大家可以留言交流。

相關(guān)文章

  • Swift實(shí)現(xiàn)Selection Sort選擇排序算法的實(shí)例講解

    Swift實(shí)現(xiàn)Selection Sort選擇排序算法的實(shí)例講解

    選擇排序是一種穩(wěn)定的排序算法,且實(shí)現(xiàn)代碼通常比冒泡排序要來的簡(jiǎn)單,這里我們就來看一下Swift實(shí)現(xiàn)Selection Sort選擇排序的實(shí)例講解
    2016-07-07
  • RxSwift發(fā)送及訂閱 Subjects、Variables代碼示例

    RxSwift發(fā)送及訂閱 Subjects、Variables代碼示例

    這篇文章主要介紹了RxSwift發(fā)送及訂閱 Subjects、Variables代碼示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-12-12
  • swift 字符串String的使用方法

    swift 字符串String的使用方法

    這篇文章主要介紹了swift 字符串String的使用方法的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • Objective-C和Swift的轉(zhuǎn)換速查手冊(cè)(推薦)

    Objective-C和Swift的轉(zhuǎn)換速查手冊(cè)(推薦)

    這篇文章主要給大家介紹了關(guān)于Objective-C和Swift的轉(zhuǎn)換速查手冊(cè)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),非常推薦給大家參考學(xué)習(xí)使用,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)不
    2018-06-06
  • Swift仿微信語(yǔ)音通話最小化時(shí)后的效果實(shí)例代碼

    Swift仿微信語(yǔ)音通話最小化時(shí)后的效果實(shí)例代碼

    這篇文章主要介紹了Swift仿微信語(yǔ)音通話最小化時(shí)后的效果的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • 詳解Swift面向?qū)ο缶幊讨械姆椒?method)

    詳解Swift面向?qū)ο缶幊讨械姆椒?method)

    既然面向?qū)ο竽蔷鸵欢〞?huì)有method,方法和面向過程語(yǔ)言中的function函數(shù)并沒什么區(qū)別,只不過方法在面向?qū)ο笳Z(yǔ)言中可以被類來約束作用域,這里我們就來詳解Swift面向?qū)ο缶幊讨械姆椒?method)
    2016-07-07
  • 利用Swift如何計(jì)算文本的size示例詳解

    利用Swift如何計(jì)算文本的size示例詳解

    這篇文章主要給大家介紹了關(guān)于利用Swift如何計(jì)算文本的size的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)各位iOS開發(fā)者們的工作或者學(xué)習(xí)具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2017-11-11
  • Swift中常量和變量的區(qū)別與聲明詳解

    Swift中常量和變量的區(qū)別與聲明詳解

    Swift語(yǔ)言同樣和Java和OC等語(yǔ)言一樣是同樣是需要聲明常量和變量的,下面就讓我們來學(xué)習(xí)一下Swift的常量和變量。這篇文章主要給大家介紹了關(guān)于Swift中常量和變量的區(qū)別與聲明的相關(guān)資料,需要的朋友可以參考下。
    2017-11-11
  • Swift中添加雙擊手勢(shì)識(shí)別器

    Swift中添加雙擊手勢(shì)識(shí)別器

    在這次IOS應(yīng)用開發(fā)教程中,我們打算實(shí)現(xiàn)手勢(shì)識(shí)別。正如你所知道的,IOS支持大量的手勢(shì)操作,它們能提供了很好的應(yīng)用控制和出色用戶體驗(yàn)。
    2019-08-08
  • Swift編程中用以管理內(nèi)存的自動(dòng)引用計(jì)數(shù)詳解

    Swift編程中用以管理內(nèi)存的自動(dòng)引用計(jì)數(shù)詳解

    這篇文章主要介紹了Swift編程中用以管理內(nèi)存的自動(dòng)引用計(jì)數(shù)詳解,是Swift入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下
    2015-11-11

最新評(píng)論