【go語言學(xué)習(xí)】反射reflect

一哑姚、認識反射

  • 維基百科中的定義:
    在計算機科學(xué)中叙身,反射是指計算機程序在運行時(Run time)可以訪問、檢測和修改它本身狀態(tài)或行為的一種能力奕枢。用比喻來說,反射就是程序在運行的時候能夠“觀察”并且修改自己的行為佩微。

不同語言的反射模型不盡相同缝彬,有些語言還不支持反射。

Go 語言支持反射哺眯,它提供了一種機制在運行時更新變量和檢查它們的值谷浅、調(diào)用它們的方法,但是在編譯時并不知道這些變量的具體類型奶卓。

1一疯、為什么要用反射
  • 需要反射的 2 個常見場景:

(1)有時你需要編寫一個函數(shù),但是并不知道傳給你的參數(shù)類型是什么夺姑,可能是沒約定好墩邀;也可能是傳入的類型很多,這些類型并不能統(tǒng)一表示瑟幕。這時反射就會用的上了磕蒲。
(2)有時候需要根據(jù)某些條件決定調(diào)用哪個函數(shù)留潦,比如根據(jù)用戶的輸入來決定只盹。這時就需要對函數(shù)和函數(shù)的參數(shù)進行反射,在運行期間動態(tài)地執(zhí)行函數(shù)兔院。

  • 在講反射的原理以及如何用之前殖卑,還是說幾點不使用反射的理由:

(1)與反射相關(guān)的代碼,經(jīng)常是難以閱讀的坊萝。在軟件工程中孵稽,代碼可讀性也是一個非常重要的指標许起。
(2)Go 語言作為一門靜態(tài)語言,編碼過程中菩鲜,編譯器能提前發(fā)現(xiàn)一些類型錯誤园细,但是對于反射代碼是無能為力的。所以包含反射相關(guān)的代碼接校,很可能會運行很久猛频,才會出錯,這時候經(jīng)常是直接 panic蛛勉,可能會造成嚴重的后果鹿寻。
(3)反射對性能影響還是比較大的,比正常代碼運行速度慢一到兩個數(shù)量級诽凌。所以毡熏,對于一個項目中處于運行效率關(guān)鍵位置的代碼,盡量避免使用反射特性侣诵。

2痢法、反射原理

interface是 Go 語言實現(xiàn)抽象的一個非常強大的工具。當(dāng)向接口變量賦一個實體類型的時候杜顺,接口會存儲實體的類型信息疯暑,反射就是通過接口的類型信息實現(xiàn)的,反射建立在類型的基礎(chǔ)上哑舒。

go語言的兩個特點:

  • go語言是靜態(tài)類型語言妇拯,因此在程序編譯階段,類型已經(jīng)確定洗鸵。
  • interface{}空接口可以和任意類型進行交互越锈,因此可以利用這一特點實現(xiàn)對任意類型的反射。

go語言的類型:

  • 變量包含(type膘滨,value)兩個部分甘凭。
  • type 包括 static type和concrete type. 簡單來說 static type是你在編碼是看見的類型(如int、string)火邓,concrete type是runtime系統(tǒng)看見的類型丹弱。
  • 類型斷言能否成功,取決于變量的concrete type铲咨,而不是static type躲胳。因此,一個 reader變量如果它的concrete type也實現(xiàn)了write方法的話纤勒,它也可以被類型斷言為writer坯苹。

go語言的反射是建立在類型的基礎(chǔ)上的,golang中對于指定類型的變量的類型是靜態(tài)的(如指定為int摇天、string類型的變量 粹湃,他們的type是static type)恐仑,也就是說在創(chuàng)建變量的時候類型已經(jīng)確定。反射主要針對interface類型(它的type是concrete type)为鳄。

在go語言的實現(xiàn)中裳仆,每一個interface變量都有一個pair,pair中記錄了實際變量的值和類型:

(value, type)

其中:value是實際變量值孤钦,type是實際變量類型鉴逞。一個interface變量包含了2個指針,一個指向值的類型(對應(yīng)concrete type)司训,一個指向?qū)嶋H的值(對應(yīng)value)构捡。

代碼示例:

package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    // 創(chuàng)建一個*os.File類型的變量f
    f, _ := os.OpenFile("a.txt", os.O_RDWR, os.ModePerm)
    // 創(chuàng)建一個io.Reader接口類型的變量r
    var r io.Reader
    r = f.(io.Reader)
    // 創(chuàng)建一個io.Writer接口類型的變量w
    var w io.Writer
    w = r.(io.Writer)
    // 創(chuàng)建一個interface{}空接口類型的變量i
    var i interface{}
    i = w
    fmt.Printf("%T, %T, %T, %T", f, r, w, i)
}

運行結(jié)果

*os.File, *os.File, *os.File, *os.File

可以看到,接口變量w壳猜、r勾徽、i的pair相同,都是:(f, *os.File)统扳。

interface及其pair的存在喘帚,是Golang中實現(xiàn)反射的前提,理解了pair咒钟,就更容易理解反射吹由。反射就是用來檢測存儲在接口變量內(nèi)部(值value;類型concrete type) pair對的一種機制朱嘴。

下面倾鲫,我們通過go語言的reflect包提供的API來實現(xiàn)反射機制

二、Type和Value

reflect包提供了兩種類型(或者說兩個方法)讓我們可以很容易的訪問接口變量內(nèi)容萍嬉,分別是reflect.ValueOf() 和 reflect.TypeOf()乌昔,看看官方的解釋:

// ValueOf returns a new Value initialized to the concrete value
// stored in the interface i.  ValueOf(nil) returns the zero 
func ValueOf(i interface{}) Value {...}

翻譯一下:ValueOf用來獲取輸入?yún)?shù)接口中的數(shù)據(jù)的值,如果接口為空則返回0


// TypeOf returns the reflection Type that represents the dynamic type of i.
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i interface{}) Type {...}

翻譯一下:TypeOf用來動態(tài)獲取輸入?yún)?shù)接口中的值的類型壤追,如果接口為空則返回nil

reflect.TypeOf()是獲取pair中的type磕道,reflect.ValueOf()獲取pair中的value。

首先需要把它轉(zhuǎn)化成reflect對象(reflect.Type或者reflect.Value行冰,根據(jù)不同的情況調(diào)用不同的函數(shù)溺蕉。

說明:

  1. reflect.TypeOf: 直接給到了我們想要的type類型,如float64悼做、int疯特、各種pointerstruct 等等真實的類型
  2. reflect.ValueOf:直接給到了我們想要的具體的值贿堰,如1.2345這個具體數(shù)值辙芍,或者類似&{1, "Allen.Wu", 25}這樣的結(jié)構(gòu)體struct的值
  3. 也就是說明反射可以將“接口類型變量”轉(zhuǎn)換為“反射類型對象”,反射類型指的是reflect.Type和reflect.Value這兩種

Type 和 Value 都包含了大量的方法羹与,其中第一個有用的方法應(yīng)該是 Kind故硅,這個方法返回該類型的具體信息:Uint、Float64 等纵搁。Value 類型還包含了一系列類型方法吃衅,比如 Int(),用于返回對應(yīng)的值腾誉。以下是Kind的種類:

// A Kind represents the specific kind of type that a Type represents.
// The zero Kind is not a valid kind.
type Kind uint

const (
    Invalid Kind = iota
    Bool
    Int
    Int8
    Int16
    Int32
    Int64
    Uint
    Uint8
    Uint16
    Uint32
    Uint64
    Uintptr
    Float32
    Float64
    Complex64
    Complex128
    Array
    Chan
    Func
    Interface
    Map
    Ptr
    Slice
    String
    Struct
    UnsafePointer
)

三徘层、反射的規(guī)則

下圖描述了實例、Value利职、Type 三者之間的轉(zhuǎn)換關(guān)系:

1趣效、從實例到Value

func ValueOf(i interface {}) Value

2、從實例到Type

func TypeOf(i interface{}) Type

3猪贪、從Type到Value

Type 里面只有類型信息跷敬,所以直接從一個 Type 接口變量里面是無法獲得實例的 Value 的,但可以通過該 Type 構(gòu)建一個新實例的 Value热押。reflect 包提供了兩種方法西傀,示例如下:

//New 返回的是一個 Value,該 Value 的 type 為 PtrTo(typ)桶癣,即 Value 的 Type 是指定 typ 的指針類型
func New(typ Type) Value
//Zero 返回的是一個 typ 類型的零佳拥褂,注意返回的 Value 不能尋址,位不可改變
func Zero(typ Type) Value

4牙寞、從Value到Type

從反射對象 Value 到 Type 可以直接調(diào)用 Value 的方法饺鹃,因為 Value 內(nèi)部存放著到 Type 類型的指針。

func (v Value) Type() Type

5间雀、從Value到實例

Value 本身就包含類型和值信息尤慰,reflect 提供了豐富的方法來實現(xiàn)從 Value 到實例的轉(zhuǎn)換。例如:

//該方法最通用雷蹂,用來將 Value 轉(zhuǎn)換為空接口伟端,該空接口內(nèi)部存放具體類型實例
//可以使用接口類型查詢?nèi)ミ€原為具體的類型
func (v Value) Interface() (i interface{})

//Value 自身也提供豐富的方法,直接將 Value 轉(zhuǎn)換為簡單類型實例匪煌,如果類型不匹配责蝠,則直接引起 panic
func (v Value) Bool () bool
func (v Value) Float() float64
func (v Value) Int() int64
func (v Value) Uint() uint64

6、從 Value 的指針到值

從一個指針類型的 Value 獲得值類型 Value 有兩種方法萎庭,示例如下霜医。

//如果 v 類型是接口,則 Elem() 返回接口綁定的實例的 Value驳规,如采 v 類型是指針肴敛,則返回指針值的 Value,否則引起 panic
func (v Value) Elem() Value
//如果 v 是指針,則返回指針值的 Value医男,否則返回 v 自身砸狞,該函數(shù)不會引起 panic
func Indirect(v Value) Value

7、Type 指針和值的相互轉(zhuǎn)換

指針類型 Type 到值類型 Type镀梭。例如:

//t 必須是 Array刀森、Chan、Map报账、Ptr研底、Slice,否則會引起 panic
//Elem 返回的是其內(nèi)部元素的 Type
t.Elem() Type

值類型 Type 到指針類型 Type透罢。例如:

//PtrTo 返回的是指向 t 的指針型 Type
func PtrTo(t Type) Type

8榜晦、Value 值的可修改性

Value 值的修改涉及如下兩個方法:

//通過 CanSet 判斷是否能修改
func (v Value ) CanSet() bool
//通過 Set 進行修改
func (v Value ) Set(x Value)

根據(jù) Go 官方關(guān)于反射的博客,反射有三大定律:

  1. Reflection goes from interface value to reflection object.
  2. Reflection goes from reflection object to interface value.
  3. To modify a reflection object, the value must be settable.

第一條是最基本的:反射可以從接口值得到反射對象羽圃。

第二條實際上和第一條是相反的機制乾胶,反射可以從反射對象獲得接口值。

第三條不太好懂:如果需要操作一個反射變量统屈,則其值必須可以修改胚吁。

四、反射的使用

1愁憔、從relfect.Value中獲取接口interface的信息

當(dāng)執(zhí)行reflect.ValueOf(interface)之后腕扶,就得到了一個類型為”relfect.Value”變量,可以通過它本身的Interface()方法獲得接口變量的真實內(nèi)容吨掌,然后可以通過類型判斷進行轉(zhuǎn)換半抱,轉(zhuǎn)換為原有真實類型。

已知類型

從反射值對象(reflect.Value)中獲取值得方法:

方法名 說 明
Interface() interface{} 將值以 interface{} 類型返回膜宋,可以通過類型斷言轉(zhuǎn)換為指定類型
Int() int64 將值以 int 類型返回窿侈,所有有符號整型均可以此方式返回
Uint() uint64 將值以 uint 類型返回,所有無符號整型均可以此方式返回
Float() float64 將值以雙精度(float64)類型返回秋茫,所有浮點數(shù)(float32史简、float64)均可以此方式返回
Bool() bool 將值以 bool 類型返回
Bytes() []bytes 將值以字節(jié)數(shù)組 []bytes 類型返回
String() string 將值以字符串類型返回

已知類型后轉(zhuǎn)換為其對應(yīng)的類型的做法如下,直接通過Interface方法然后強制轉(zhuǎn)換肛著,如下:

realValue := value.Interface().(已知的類型)

示例代碼:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var a int = 10
    rType := reflect.TypeOf(a)
    fmt.Println(rType)
    rValue := reflect.ValueOf(a)
    fmt.Println(rValue)
    rPointer := reflect.ValueOf(&a)
    fmt.Println(rPointer)
    // 1. 轉(zhuǎn)換的時候圆兵,如果轉(zhuǎn)換的類型不完全符合,則直接panic枢贿,類型要求非常嚴格殉农!
    // 2. 轉(zhuǎn)換的時候,要區(qū)分是指針還是值
    // 3. 也就是說反射可以將“反射類型對象”再重新轉(zhuǎn)換為“接口類型變量”
    // convertValue := rValue.Interface().(int) // 10
    convertValue := rValue.Int()
    // convertValue := rValue.Bool() // panic: reflect: call of reflect.Value.Bool on int Value
    convertPointer := rPointer.Interface().(*int)
    fmt.Println(convertValue)
    fmt.Println(convertPointer)
}

運行結(jié)果

int
10
0xc000012090
10
0xc000012090

結(jié)構(gòu)體類型

反射值對象(reflect.Value)提供對結(jié)構(gòu)體訪問的方法局荚,通過這些方法可以完成對結(jié)構(gòu)體字段和方法的訪問超凳,如下表所示愈污。

方 法 說 明
Field(i int) Value 根據(jù)索引,返回索引對應(yīng)的結(jié)構(gòu)體成員字段的反射值對象暂雹。當(dāng)值不是結(jié)構(gòu)體或索引超界時發(fā)生panic
NumField() int 返回結(jié)構(gòu)體成員字段數(shù)量。當(dāng)值不是結(jié)構(gòu)體或索引超界時發(fā)生panic
FieldByName(name string) Value 根據(jù)給定字符串返回字符串對應(yīng)的結(jié)構(gòu)體字段擎析。沒有找到時返回零值挥下,當(dāng)值不是結(jié)構(gòu)體或索引超界時發(fā)生panic
FieldByIndex(index []int) Value 多層成員訪問時,根據(jù) []int 提供的每個結(jié)構(gòu)體的字段索引桨醋,返回字段的值棚瘟。 沒有找到時返回零值喜最,當(dāng)值不是結(jié)構(gòu)體或索引超界時發(fā)生panic
FieldByNameFunc(match func(string) bool) Value 根據(jù)匹配函數(shù)匹配需要的字段。找到時返回零值瞬内,當(dāng)值不是結(jié)構(gòu)體或索引超界時發(fā)生panic
NumMethod() int 返回該類型的方法集中方法的數(shù)目
Method(int) Method 返回該類型方法集中的第i個方法
MethodByName(string)(Method, bool) 根據(jù)方法名返回該類型方法集中的方法

示例代碼:

package main

import (
    "fmt"
    "reflect"
)

// Student 學(xué)生結(jié)構(gòu)體
type Student struct {
    Name    string
    Age     int
    Address string
}

// Say Student結(jié)構(gòu)體的方法
func (s Student) Say(msg string) {
    fmt.Println(msg)
}

// PrintInfo Student結(jié)構(gòu)體的方法
func (s Student) PrintInfo() {
    fmt.Printf("姓名:%s\t年齡:%d\t地址:%s\n", s.Name, s.Age, s.Address)
}

func main() {
    s := Student{"tom", 20, "上海市"}
    Test(s)
}

// Test 測試函數(shù)
func Test(i interface{}) {
    // 獲取i的類型
    rType := reflect.TypeOf(i)
    fmt.Println("i的類型是:", rType.Name()) // Student
    fmt.Println("i的種類是:", rType.Kind()) // struct
    // 獲取i的值
    rValue := reflect.ValueOf(i)
    fmt.Println("i的值是:", rValue)
    // 獲取i的字段信息
    for i := 0; i < rValue.NumField(); i++ {
        field := rType.Field(i)
        value := rValue.Field(i).Interface()
        fmt.Printf("字段名稱:%s, 字段類型:%s, 字段值:%v\n", field.Name, field.Type, value)
    }
    // 獲取i的方法信息
    for i := 0; i < rValue.NumMethod(); i++ {
        method := rType.Method(i)
        fmt.Printf("方法的名稱:%s, 方法的類型:%s\n", method.Name, method.Type)

    }
}

運行結(jié)果:

i的類型是: Student
i的種類是: struct
i的值是: {tom 20 上海市}
字段名稱:Name, 字段類型:string, 字段值:tom
字段名稱:Age, 字段類型:int, 字段值:20
字段名稱:Address, 字段類型:string, 字段值:上海市
方法的名稱:PrintInfo, 方法的類型:func(main.Student)
方法的名稱:Say, 方法的類型:func(main.Student, string)
2迷雪、通過reflect.Value設(shè)置實際變量的值

reflect.Value是通過reflect.ValueOf(X)獲得的,只有當(dāng)X是指針的時候章咧,才可以通過reflec.Value修改實際變量X的值能真,即:要修改反射類型的對象就一定要保證其值是“addressable”的。

// Elem returns the value that the interface v contains
// or that the pointer v points to.
// It panics if v's Kind is not Interface or Ptr.
// It returns the zero Value if v is nil.
func (v Value) Elem() Value {}
// 翻譯:
// Elem返回接口v包含的值或指針v指向的值疼约。
// 如果v的類型不是interface或ptr蝙泼,它會恐慌。
// 如果v為零汤踏,則返回零值。

示例代碼:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    var a int = 10
    rValue := reflect.ValueOf(&a)
    fmt.Println("是否可修改:", rValue.Elem().CanSet())
    rValue.Elem().SetInt(20)
    fmt.Println(a)
}

運行結(jié)果

是否可修改: true
20
3昙沦、通過reflect.Value來進行函數(shù)的調(diào)用

如果反射值對象(reflect.Value)中值的類型為函數(shù)時载荔,可以通過 reflect.Value 調(diào)用該函數(shù)。使用反射調(diào)用函數(shù)時丘损,需要將參數(shù)使用反射值對象的切片 []reflect.Value 構(gòu)造后傳入 Call() 方法中,調(diào)用完成時衔蹲,函數(shù)的返回值通過 []reflect.Value 返回呈础。

示例代碼:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    rValue := reflect.ValueOf(add)
    res := rValue.Call([]reflect.Value{reflect.ValueOf(10), reflect.ValueOf(20)})
    fmt.Printf("%v, %T\n", res, res)
    fmt.Println(res[0].Int())
}

func add(a, b int) int {
    return a + b
}

運行結(jié)果

[<int Value>], []reflect.Value
30

提示:

反射調(diào)用函數(shù)的過程需要構(gòu)造大量的 reflect.Value 和中間變量,對函數(shù)參數(shù)值進行逐一檢查而钞,還需要將調(diào)用參數(shù)復(fù)制到調(diào)用函數(shù)的參數(shù)內(nèi)存中。調(diào)用完畢后撬陵,還需要將返回值轉(zhuǎn)換為 reflect.Value网缝,用戶還需要從中取出調(diào)用值。因此草添,反射調(diào)用函數(shù)的性能問題尤為突出维费,不建議大量使用反射函數(shù)調(diào)用。

4犀盟、通過反射,調(diào)用方法

調(diào)用方法和調(diào)用函數(shù)是一樣的倡怎,只不過結(jié)構(gòu)體需要先通過rValue.Method()先獲取方法再調(diào)用贱枣。

示例代碼:

package main

import (
    "fmt"
    "reflect"
)

// Student 學(xué)生結(jié)構(gòu)體
type Student struct {
    Name    string
    Age     int
    Address string
}

// Say Student結(jié)構(gòu)體的方法
func (s Student) Say(msg string) {
    fmt.Println(msg)
}

// PrintInfo Student結(jié)構(gòu)體的方法
func (s Student) PrintInfo() {
    fmt.Printf("姓名:%s\t年齡:%d\t地址:%s\n", s.Name, s.Age, s.Address)
}

func main() {
    s := Student{"tom", 20, "上海市"}

    rValue := reflect.ValueOf(s)
    m1 := rValue.MethodByName("Say")
    m1.Call([]reflect.Value{reflect.ValueOf("hello world")})
    m2 := rValue.MethodByName("PrintInfo")
    m2.Call(nil)
}

運行結(jié)果

hello world
姓名:tom       年齡:20        地址:上海市
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纽哥,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子晓避,更是在濱河造成了極大的恐慌,老刑警劉巖暑塑,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锅必,死亡現(xiàn)場離奇詭異,居然都是意外死亡驹愚,警方通過查閱死者的電腦和手機尔许,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門味廊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來棠耕,“玉大人,你說我怎么就攤上這事窍荧。” “怎么了郊楣?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵净蚤,是天一觀的道長输硝。 經(jīng)常有香客問我,道長点把,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任哥童,我火速辦了婚禮褒翰,結(jié)果婚禮上压恒,老公的妹妹穿的比我還像新娘探赫。我一直安慰自己撬呢,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布毛仪。 她就那樣靜靜地躺著芯勘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪衡怀。 梳的紋絲不亂的頭發(fā)上安疗,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音怖现,去河邊找鬼玉罐。 笑死,一個胖子當(dāng)著我的面吹牛吊输,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播讨韭,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼透硝,長吁一口氣:“原來是場噩夢啊……” “哼疯搅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起幔欧,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎觉义,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體霉撵,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡徒坡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年瘤缩,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片锦溪。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡铐殃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情域帐,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布民假,位于F島的核電站羊异,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏野舶。R本人自食惡果不足惜宰衙,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望供炼。 院中可真熱鬧窘疮,春花似錦冀墨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至酥筝,卻和暖如春雏门,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背茁影。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工募闲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人浩螺。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓要出,卻偏偏與公主長得像,于是被迫代替她去往敵國和親患蹂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345