马上着手开发 iOS 应用程序 (二) - 学习必要的 Swift 知识

重要:这是针对于正在开发中的API或技术的预备文档(预发布版本)。苹果提供这份文档的目的是帮助你按照文中描述的方式对技术的选择及界面的设计开发进行规划。这些信息有可能发生变化,因此根据本文档的软件开发应当基于最终版本的操作系统和文档进行测试。该文档的新版本或许会随着API或相关技术未来的发展而进行更新。

翻译自苹果官网:

https://developer.apple.com/library/prerelease/ios/referencelibrary/GettingStarted/DevelopiOSAppsSwift/index.html#//apple_ref/doc/uid/TP40015214-CH2-SW1

第一课以 Swift Playground 形式展现的,它是种能让你与代码进行交互并实时看到结果的文件。Playgrounds 对于学习和尝试非常棒,它帮助你提高学习基础 Swift 概念的效率。

注意

最佳实践是在 Xcode 中打开此课时的 Playground。

下载 Playground

学习目标

在课时结束时,你将学到:

  • 常量和变量之间的区别
  • 知道何时使用隐式和显式类型声明
  • 理解使用可选和可选绑定的优点
  • 区分可选和隐式展开可选
  • 理解条件语句和循环的作用
  • 为条件分支超过一个的判断使用 Switch 语句
  • 在条件语句中使用 where 从句增加额外的判断约束
  • 区分 functions(函数)methods(方法)initializers(构造方法)
  • 区分 classes(类)structures(结构体)enumerations(枚举)
  • 理解继承和协议一致性
  • 确定隐式类型和使用 Xcode 快捷帮助找到更多的信息(Option-单击)
  • 导入和使用 UIKit

基本类型

常量的值在第一次定义后会保持不变,而变量值会改变。常量被称为不可变,意味着它不可以被修改。如果你知道代码中的值不需要改变,把它定义为常量而不是变量。

var myVariable = 42
myVariable = 50
let myConstant = 42

Swift 每个常量和变量都有一个类型,但是你经常不需要明确声明类型,在赋值后,编译器会自动推断类型。在上面的例子中,编译器推断出 myVariable 是 Int 类型因为它的初始值是整数。这被称为类型推断。一旦常量或者变量有了类型,这个类型就不能再改变了。

let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70
重要提示:

Xcode 中,按住 Option 并点击常量或变量的名字来查看它的推断类型。尝试对上面代码的常量这么做。

Swift 的值永远不会被隐式转换为其他类型。如果你需要把一个值转换成其他类型,请显式转换成想要的类型的实例。这里,你转换一个 Int 的值为 String 类型。

let label = "The width is"
let width = 94
let widthLabel = label + String(width)
练习:
删除最后一行中的 String 转换,看得到什么错误提示?

有种很简单的方式向字符串中插入各种类型的值:使用小括号把值括起来,然后在括号前加个反斜杠 \ 。这被称为字符串插值。

let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."

使用可选来处理值可能丢失的情况。一个可选可能包含一个值或是 nil 来表示值缺失。使用问号(?) 来表明变量为可选的。

let optionalInt: Int? = 9

为了得到一个可选变量的值,需要展开它。稍后讲学习展开它,但是最直接的办法是使用强制展开运算符(!) 展开它。但请确保只在变量的值不为 nil 的情况下才使用这种方式。

let actualInt: Int = optionalInt!

可选在 Swift 中普遍存在,在许多值或许存在或许不存在的情形下非常有用。尤其在尝试类型转换的时候特别有用。

var myString = "7"
var possibleInt = Int(myString)
print(possibleInt)

在这段代码中,myString 字符串内容包含整形的值,所以转换成功, possibleInt 的值变成7。但是如果修改 myString 为不能转换成整数的内容,possibleInt 就变成了 nil。

myString = "banana"
possibleInt= Int(myString)
print(possibleInt)

数组是一种保存有序集合的数据类型。使用中括号([])创建数组,在括号中写下标来访问它们的元素。数据从下标 0 开始。

var ratingList = ["Poor","Fine","Good","Excellent"]
ratingList[1] ="OK"
ratingList

使用构造器语法创建空的数组,稍后你就会学习更多关于构造器的知识。

// 创建空数组.
let emptyArray = [String]()

你会注意上面的代码中包含一行注释。注释不会作为代码的一部分进行编译但是提供了理解程序非常有用的信息。单行注释以双正斜杠(//)作为起始标记,多行注释起始标记为单个正斜杠后跟随一个星号(/*),终止标记为一个星号后跟随单个正斜杠 ( */ )。你会看到在课程代码的各处同时用到这两种类型的注释。

隐式解析可选是可以当做非可选值用的可选,当被访问时不需要展开可选值。赋值后会被认为一直有值,尽管这个值可以改变。它用感叹号(!)而不是问号(?)表示。

var implicitlyUnwrappedOptionalInt: Int!

你很少需要在你的代码中创建隐式解析可选。通常,你会看到它们用在记录界面和源代码(会在后面一节课程中学到)之间的 outlets 中以及贯穿整套课程的 APIs 中。

控制流

Swift 有两种控制流语句。一种是像 if 和 switch 的条件语句,使用它们检查条件是否为真 - 如果为真,执行相应的代码块。一种是循环语句,如 for-in 和 while,使用它们多次执行相同的代码块。

if 语句检查一个确定的条件是否为真,如果为真,执行括号中代码。也可以向一个 if 语句添加 else 从句定义更复杂的判断行为。

let number = 23
if number < 10 {
    print("The number is small")
} else if number > 100 {
    print("The number is pretty big")
} else {
    print("The number is between 10 and 100")
}
练习

修改 number 为一个不同的整形值看看结果是哪行打印出来。

嵌套语句可以在程序中创建复杂,有趣的功能。下面例子在 for-in 语句(按顺序在集合中一个接一个地遍历每一项)中嵌套使用 if 语句和 else 从句。

let individualScores = [75,43,103,87,12]
var teamScore =0
for score in individualScores {
        if score >50 {    
             teamScore += 3
         } else {       
              teamScore +=1
        }
}
print(teamScore)

在 if 语句中使用可选绑定检查变量是否有值。

var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}
练习:
修改 optionalName 的值为 nil。看能得到什么结果?添加 else 从句处理 optionalName 值为 nil 的情况。

如果 optionalName 为 nil,判断结果为 false,会跳过大括号中代码。否则,optionalName 会被展开并赋值给 name,在代码块中可以访问这个常量。

你可以使用 if 来绑定多个值并使用 where 从句增加额外的判断。在下面这个例子中,if 语句只在全部的值绑定成功并满足 where 从句条件的情况下才执行。

var optionalHello: String? = "Hello"
if let hello = optionalHello where hello.hasPrefix("H"), let name = optionalName {
    greeting = "\(hello), \(name)"
}

Swift 中 switch 语句同样强大。与 OC 只支持整形不同,它支持多种数据类型的比较。在下面例子中, switch 语句比较 vegetable 和后面每个 case 的值,当匹配了就执行相应的代码块。

let vegetable = "red pepper" 
switch vegetable {
   case "celery" :
      print("Add some raisins and make ants on a log.")
   case "cucumber", "watercress":
      print("That would make a good tea sandwich.")
   case let x where x.hasSuffix("pepper"):
      print("Is it a spicy \(x)?")default:print("Everything tastes good in soup.")
  }
练习:
删除 default 语句,看看会有什么错误?

注意 let 怎么用在模式匹配中并把值赋给一个常量。就像在 if 语句一样,where 从句可以添加到 case 中增加条件语句进一步的判断。然而,不像在 if 语句,switch 的 case 用逗号分割的多个条件,当每个条件都满足时就会执行。
执行完匹配的 switch case 的代码后,程序会退出 switch 语句。并不会继续向下个 case 执行,所以不需要在每个 case 代码结尾明确写 break 来中断 switch 语句。

switch 语句必须是完备的。这就是说,每一个可能的值都必须至少有一个 case 分支与之对应。在不可能涵盖所有值的情况下,可以使用默认(default)分支满足该要求,这个默认分支必须在 switch 语句的最后面。

使用区间来保存循环的下标。使用半闭区间运算符(..<)来创建一个下标的区间。

var firstForLoop = 0
for i in 0..<4 {
    firstForLoop += i
}
print(firstForLoop)

半闭区间运算符 ( ..< ) 并不包括最大值,所以此区间是从0到3的四次循环迭代。使用闭区间运算符(...)来创建一个包含全部值的区间。

var secondForLoop = 0
for _ in 0...4 {
    secondForLoop += 1
}
print(secondForLoop)

此区间是从0到4的五次循环迭代。当不需要用到当前执行循环的值时,使用下划线 (_) 这个通配符。

函数和方法

函数是可复用的指定代码块,它可以在程序中很多地方被引用。

使用 func 来定义一个函数。函数定义可以包括0个或更多参数,参数可以写成 name:Type 形式,作为附加信息当函数被调用时必须传递值它们给函数。函数可以选择是否有返回值,返回值写在 -> 后面,表明它们是作为结果返回的。函数的实现写在一对大括号里面({})。

func greet(name:String, day:String) -> String {
   return "Hello \(name), today is \(day)."
}

通过在名字后面的括号中添加参数列表来调用一个函数。 当你调用一个函数,参数列表的第一个参数可以不写参数名,但是后面的参数需要加上参数名。

greet("Anna", day: "Tuesday")
greet("Bob", day: "Friday")
greet("Charlie", day: "a nice day")

在具体类型中定义的函数称为方法。方法总是明确关联定义它们的类型,并且只能作为该类型的方法(或它的一个子类,稍后将会学到)。在前面的 Switch 语句例子中,可以看到 String 类中有个名叫 hasSuffix() 的方法,这里再一次用到。

let exampleString = "hello"
if exampleString.hasSuffix("lo") {
    print("ends in lo")
}

正如你所看到的,使用点语法调用方法。当调用的时候,可以不写第一个参数名,但是后面参数需要写。例如,Array 的方法有两个参数,但是只写了第二个参数名:

var array = ["apple", "banana", "dragonfruit"]
array.insert("cherry", atIndex: 2)
array

类和构造器

在面向对象编程中,一个程序很大程度上依赖对象间的相互作用。对象是一个类的实例,它可以被认为是实际实体的一个蓝图。类以属性的形式存储实体额外的信息,并且使用方法定义它的行为。

使用 class 和类名来创建一个类。类中属性和方法的定义与之前的常量、变量定义一样,唯一的区别就是它们的上下文是类。同样,方法和函数定义也是同一种写法。下面例子定义一个 Shape 类,它有一个 numberOfSides 属性和一个 simpleDescription() 方法。

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

要创建一个类的对象,在类名后面加上括号。使用点语法来访问对象的属性和方法。下面的例子, shape 是 Shape 类的一个实例对象。

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

这个 Shape 类缺少重要的东西:构造器。构造器是一个用来为类的每个属性设置初始值以及执行其他任何设置的方法。使用 init 来创建一个构造器。下面例子定义了一个新的类 NameShape,它有个构造器来接收 name 的值。

class NamedShape {
    var numberOfSides = 0
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

注意 self 用来区分 name 属性和构造器的 name 参数。确保对象的每个属性都赋值了——无论是通过定义(就像 numberOfSides)还是通过构造器(就像 name)。

不能直接通过调用 init 方法来调用构造器;而是在类的名字和括号中写上合适的参数。当以这种方式调用构造器时,需写上所有的参数和值。

let namedShape = NamedShape(name: "my named shape")

类能从它们的父类中继承行为,类从其他类继承功能被称为那个类的子类,被继承的类是父类。在子类的名字后面加上父类名,并用冒号分割。一个类只能继承一个父类,尽管那个父类可以继承别的父类,诸如此类最终形成类的层次结构。

子类如果重写父类的方法,需要用 override 标记 —— 如果没有添加 override 就重写父类方法的话编译器会报错。编译器同样会检测 override 标记的方法是否确实在父类中。

下面例子定义 Square 类,它是 NamedShape 的子类。

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 testSquare = Square(sideLength: 5.2, name: "my test square")
testSquare.area()
testSquare.simpleDescription()

注意 Square 类的构造器执行了三步不同的操作

  1. 设置子类 Square 定义的属性的值。
  2. 调用父类 NamedShape 的构造器。
  3. 改变父类 NamedShape 的属性值。这个阶段可以调用方法、设置 getters 和 setters 等操作。

对象的构造过程有可能失败,需要定义为一个可失败构造器,其语法为在 init 关键字后面添加问号 (init?) 。这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。

class Circle: NamedShape {
    var radius: Double
    
    init?(radius: Double, name: String) {
        self.radius = radius
        super.init(name: name)
        numberOfSides = 1
        if radius <= 0 {
            return nil
        }
    }
    
    override func simpleDescription() -> String {
        return "A circle with a radius of \(radius)."
    }
}

构造器同样可以有些相关的关键字。指定构造器不需要任何关键字。这种构造器作为类的主要构造器;类中任何的构造器必须最终调用一个指定构造器。

构造器旁边的 convenience 关键字表示它是一个便利构造器。便利构造器是辅助的构造器。用于增加额外和定制的功能,但必须最终调用一个指定构造器。

构造器旁边的 required 关键字表示拥有这个构造器的每个子类必须实现它自己的版本而不能直接继承父类的构造器(如果它实现了任何其他构造器)。

类型转换用在类层次结构上,检查类型能否转换,如果可以将类型转换成这个层次结构中的其他类型。

一个确定类类型的常量或变量幕后可能属于它的子类实例。当确定是这种情况时,可以使用类型转换运算符尝试下转到这个子类型。

因为向下转型可能会失败,所以 as 有两种不同写法。可选方式,as?, 返回一个你试图转换类型的可选值。强制形式 as! 把向下转型和强制解包结合为单一复合操作。

当你不确定向下转型是否成功时,用可选类型转换符(as?)。它总是返回一个可选值,如果下转不可能值将变为 nil。这能让你检查向下转型是否成功。

只有在你确定向下转型一定成功时,才使用 as! 。当你试图向下转型为一个不正确的类型时,会触发一个运行时错误。

在这个例子中使用 as? 检查 shape 数组中元素中是否是正方形或三角形。如果是,相应增加 squares 和 triangles 变量的计数,最后打印值。

class Triangle: NamedShape {
    init(sideLength: Double, name: String) {
        super.init(name: name)
        numberOfSides = 3
    }
}
 
let shapesArray = [Triangle(sideLength: 1.5, name: "triangle1"), Triangle(sideLength: 4.2, name: "triangle2"), Square(sideLength: 3.2, name: "square1"), Square(sideLength: 2.7, name: "square2")]
var squares = 0
var triangles = 0
for shape in shapesArray {
    if let square = shape as? Square {
        squares++
    } else if let triangle = shape as? Triangle {
        triangles++
    }
}
print("\(squares) squares and \(triangles) triangles.")
练习

尝试使用 as! 替换 as?。看能得到什么错误?

枚举和结构体

在 Swift 中类不是定义数据类型的唯一方式。枚举和结构体和类具有类似的能力,但在不同的情况下可以非常有用。

枚举为一组关联的值定义一个通用类型,让你在代码中以一种安全的方式使用这些值。枚举也可以包含关联的方法。

使用 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.rawValue)
        }
    }
}
let ace = Rank.Ace
let aceRawValue = ace.rawValue

在上面的例子中,枚举原始值的类型是 Int,所以你只需设置第一个 case 的原始值。剩下的原始值会按照顺序赋值。你也可以使用字符串或者浮点数作为枚举的原始值。使用 rawValue 属性来访问一个枚举成员的原始值。

使用 init?(rawValue:) 构造方法在原始值和枚举之间进行转换。

if let convertedRank = Rank(rawValue:3) {
   let threeDescription = convertedRank.simpleDescription()
}

枚举的成员值是实际值,而不是原始值的另一种写法。事实上,有时不需要为 cases 赋一个没有意义的原始值。

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.Hearts
let heartsDescription = hearts.simpleDescription()

注意,上面提到两种使用 Hearts 成员的方式:给 hearts 常量赋值时,因为它没有显式指定类型,所以 Suit.Hearts 用全名来表示。在 switch 里,因为已知 self 的类型,所以枚举成员可以使用缩写如 .Hearts 来引用。

结构体支持许多和类相同的功能,包括方法和构造器。它们之间最大的一个区别就是当在代码中传递它们时,结构体是以拷贝形式的,而类是以引用形式传递的。当你不需要继承和类型转换的功能时,用结构体定义轻量级数据类型非常棒。

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()

协议

协议定义方法,属性和其他要求的一个蓝图,它适合一个特定的任务或功能。协议不为这些要求提供实际的实现-它仅仅描述这个实现是什么样子的。
类、结构体或枚举都可以遵循协议,并为协议定义的这些要求提供具体实现。任何满足一个协议的要求的类型被说成遵循那个协议了。�

使用 protocol 关键字来定义协议。

protocol ExampleProtocol {
    var simpleDescription: String { get }
    func adjust()
}
注意:
simpleDescription 属性后面的 { get } 表示它是只读的,这意味着属性的值可以读取,但不能修改。

类,结构体和枚举通过在名字后面列出协议的名字并用冒号分割来遵循协议。类型可以同时遵循几个协议,把这些协议放在逗号分割的列表中。如果一个类有父类,这个父类的名字必须出现在列表的第一个,而协议跟在后面。通过实现协议的所有要求来遵循这个协议。

下面的例子中,SimpleClass 通过实现 simpleDescription 属性和 adjust() 方法来遵循 ExampleProtocol 协议。

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 

协议是头等类型,意味着它们可以被当做其他命名类型。例如,你可以创建 ExampleProtocol 数组来调用其中的每个实例的 adjust() 的方法(因为协议的强制要求,数组中每个实例都保证实现了 adjust()方法)。

class SimpleClass2: ExampleProtocol {
    var simpleDescription: String = "Another very simple class."
    func adjust() {
        simpleDescription += "  Adjusted."
    }
}
 
var protocolArray: [ExampleProtocol] = [SimpleClass(), SimpleClass(), SimpleClass2()]
for instance in protocolArray {
    instance.adjust()
}
protocolArray

Swift 和 Cocoa Touch

Swift 做了很好的设计使它能与 Cocoa Touch 这个用来开发 iOS app 的框架集无缝交互。当你学完后续课程,你会对 Swift 与 Cocoa Touch 交互有个基本的了解。

到目前为止,你一直致力于 Swift 标准库中的数据类型。Swift 标准库是 Swift 语言内置数据类型集和功能集。可以在标准库中看到像 String 和 Array 这样的数据类型例子。

let sampleString: String = "hello"
let sampleArray: Array = [1, 2, 3.1415, 23, 42]
练习

在 Xcode 中按住 Option 键点击类型来阅读标准库中的类型。当查看 Xcode 的 playground 时,Option+点击上面代码中的 String 和 Array。

当开发 iOS 应用程序时,将使用不止 Swift 标准库。其中一个使用最频繁的框架是 UIKit。UIKit 包含一些 app UI 层很有用的类。

为了访问 UIKit,在 Swift 文件或者 playground 中简单的以模块的形式导入。

import UIKit

导入 UIKit 后就可以使用 Swift 语法来访问 UIKit 类型以及类型的方法和属性,等等。

let redSquare = UIView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
redSquare.backgroundColor = UIColor.redColor()

这些课程介绍的很多类都来自 UIKit,所以你经常会看到这个 import 语句。

有了这些 Swift 方面的知识后,你将在下一课进入开发一个完整的 app 的过程。尽管本课还停留在 playgrounds 的程度,但请记住它们在 app 调试方面是强有力的工具,能做到可视化复杂的代码以及快速产生原型。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容