Go reflect

reflection

  • 反射(reflection)是程序在运行时通过检查其定义的变量和值获得对应的真实类型。

在计算机科学领域,反射是指一类应用能够自描述和自控制。此类应用采用某种机制来实现自己行为的描述(self-representation)和监测(examination),并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。

  • 反射机制就是在运行时动态的调用对象的方法和属性

每种编程语言的反射模型都不同,有些语言并不支持反射。支持反射的语言可以在程序编译期将变量的反射信息,比如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样可在程序运行期获取类型的反射信息,并有能力改变它们。

  • 反射是指在程序运行期对程序本身进行访问和修改的能力

程序在编译时变量会被转换为内存地址,而变量名不会被编译器写入到可执行部分,因此在运行程序时程序是无法获取自身信息的。

reflect

Golang提供了一种机制在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但在编译时并不知道这些变量的具体类型,这种机制称为反射。

反射可将类型本身作为第一类型的值来处理

  • Golang程序在运行期可使用reflect包访问程序的反射信息

Golang提供了reflect包用来实现反射,通过refelct包能够完成对一个interface{}空接口变量的具体类型和值进行获取。

Golang程序的反射系统无法获取到一个可执行文件空间中或一个包中的所有类型信息,需配合标准库中对应的此法、语法解析器和抽象语法树(AST)对源码进行扫描后获取。

interface

  • Golang关于类型设计的原则

变量包括(value, type)两部分,type包括static typeconcrete type,简单来说static type是在编码时可见的类型,concrete typeruntime系统可见的类型。

类型断言能够成功取决于变量的concrete type而非static type,比如一个变量的concrete type如果实现了write方法则可被类型断言为writer

  • 反射是建立在类型之上的

Golang指定类型的变量是static的,因此在创建变量时就已经确定。反射主要与Golang的interface类型相关,因为interface的类型是concrete的,只有interface类型才有反射一说。


  • interfacepair的存在是Golang中实现反射的前提

在Golang的实现中每个interface变量都拥有一对pair(value, type)pair中记录了实际变量的值和类型(value, type)
value是实际变量的值
type是实际变量的类型

一个interface{}类型的变量包含两个指针:
一个指针下指向实际的值,即对应的value
一个指针指向值的类型,对应concrete type

例如:创建类型为*os.File的变量

tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)

将变量赋值给一个接口变量

var r io.Reader
r = tty

赋值后接口变量的pair中记录的信息为(tty, *os.File),这个pair在接口变量连续赋值过程中是不会改变的。

比如将接口变量赋值给另一个接口变量

var w io.Writer
w = r.(io.Writer)

此时接口变量w的pair和r的pair相同,都是(tty, *os.File)。即使w是空接口类型,pair也是不变的。

方法

Golang中反射是由reflect包支持的,reflect包提供了两个重要的类型TypeValue。任意接口值在反射中都可以理解为由reflect.Typereflect.Value两部分组成。

反射是用来检测存储在接口变量内部(值 value, 类型 concrete typepair对的一种机制,reflect包提供了reflect.TypeOfreflect.ValueOf两个函数来获取任意对象的ValueType

  • reflect.ValueOf用于获取输入参数接口中的数据的值,若接口为空则返回0。
func reflect.ValueOf(i interface{}) reflect.Value
  • reflect.TypeOf用于动态获取输入参数接口中值的类型,若接口为空则返回nil
func reflect.TypeOf(i interface{}) reflect.Type

reflect.TypeOf()用于获取pair(value, type)中的typereflect.ValueOf()获取获取pair(value, type)中的value

例如:将float64类型的变量作为原值传递给reflect.TypeOf(i interface{})获取其类型信息

var num float64 = 3.14156
t.Log(reflect.ValueOf(num)) //3.14156
t.Log(reflect.TypeOf(num))  //float64

例如:

type Response struct {
    Code    int
    Message string
}

func TestReflect(t *testing.T) {
    var response Response
    t.Log(reflect.ValueOf(response)) //{0 }
    t.Log(reflect.TypeOf(response))  //test.Response
}

定律

反射三大定律

  1. 反射可以将【接口类型变量】转换为【反射类型对象】
  • 反射类型对象指的是reflect.Typereflect.Value
  • 反射提供了一种允许程序在运行时检查接口变量内部存储的pair(value, type)对的机制
  • reflect包中的ValueType类型时访问接口内部的数据成为可能
  • reflect包为ValueType类型分别提供了reflect.ValueOf()reflect.TypeOf()方法来进行读取
  1. 反射可以将【反射类型对象】转换为【接口类型变量】
  • 和物理学中的反射类似,Golang中的反射也能够创造自己反面类型的对象。
  1. 若要修改【反射类型对象】则其值必须可写
  • 可通过value.CanSet()方法来检查一个reflect.Value类型的变量是否具有“可写性”
  • 可写性类似于寻址能力,但更为严格。它是反射类型变量的一种属性,会赋予该变量修改底层存储数据的能力。
  • 可写性最终是由一个反射对象是否存储原始值而决定的

结构体

  • 使用反射获取结构体中的字段信息

通过reflect.TypeOf()从结构体中获取反射类型对象reflect.Type,可通过reflect.Type获取结构体成员信息。

结构体成员访问方法列表,与成员获取相关的reflect.Type的方法。

方法 返回值 描述
type.Field(index int) StructField 根据索引获取对应结构体字段
type.NumField() int 获取结构体成员字段数量
type.FieldByName(name string) (StructField, bool) 根据指定字符串获取对应的结构体字段
type.FieldByIndex(index []int) StructField 多层成员访问,根据[]int提供的结构体字段索引获取字段信息。
type.FieldByNameFunc(match func(string) bool) (StructField, bool) 根据匹配函数获取字段

例如:通过反射值对象reflect.Value获取结构体成员信息

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Id   int
    Name string
}

func main() {
    user := &User{Id: 1, Name: "root"}

    val := reflect.ValueOf(user)
    fmt.Printf("%v\n", val) //&{1 root}

    elem := val.Elem()
    fmt.Printf("%v\n", elem) //{1 root}

    elemType := elem.Type()
    fmt.Printf("%v\n", elemType) //main.User

    numField := elem.NumField()
    fmt.Printf("%v\n", numField) //2

    for i := 0; i < numField; i++ {
        fieldName := elemType.Field(i).Name

        field := elem.Field(i)
        fmt.Printf("%v\n", field)

        fieldType := field.Type()
        fieldValue := field.Interface()

        fmt.Printf("%d: %s %s = %v\n", i, fieldName, fieldType, fieldValue)
    }
}
&{1 root}
{1 root}
main.User
2
1
0: Id int = 1
root
1: Name string = root

reflect.StructField

  • 结构体字段类型

reflect.TypeField()方法返回StructField结构,描述了结构体的成员信息,可获取成员与结构体的关系,比如偏移、索引、是否为匿名字段、结构体标签等。

type StructField struct{
  Name string //字段名
  PkgPath string //字段路径
  Type Type //字段反射类型对象
  Tag StructTag//字段的结构体标签
  Offset uintptr //字段在结构体中的相对偏移
  Index []int //Type.FieldByIndex返回的索引值
  Anonymous bool//是否为匿名字段
}
字段 类型 描述
Name string 字段名称
PkgPath string 字段在结构体中的路径
Type Type 字段本身的反射类型对象,类型为reflect.Type。
Tag StructTag 结构体标签,为结构体字段标签的额外信息,可单独提取。
Index FieldByIndex 索引顺序
Anonymous bool 字段是否为匿名字段
  • 获取结构体成员反射信息

例如:实例化结构体后遍历成员,通过reflect.TypeFieldByName()获取字段信息。

//声明结构体
type User struct {
    Id   int
    Name string `json:"name" id:"100"`
}
//创建结构体实例
ins := User{Id: 1, Name: "root"}
//获取结构体实例的反射类型对象
typ := reflect.TypeOf(ins)
//遍历结构体成员
for i := 0; i < typ.NumField(); i++ {
    //获取成员
    field := typ.Field(i)
    //获取成员属性
    name := field.Name
    tag := field.Tag
    fmt.Printf("name = %v, tag = %v\n", name, tag)
}
//声明结构体
type User struct {
    Id   int
    Name string `json:"name" id:"100"`
}
//创建结构体实例
ins := User{Id: 1, Name: "root"}
//获取结构体实例的反射类型对象
typ := reflect.TypeOf(ins)
//通过字段名获取信息
field, ok := typ.FieldByName("Name")
if ok {
    tag := field.Tag
    tagName := tag.Get("json")
    tagId := tag.Get("id")
    fmt.Printf("name = %v, id = %v\n", tagName, tagId) //name = name, id = 100
}

reflect.StructTag

  • 结构体标签

通过reflect.Type获取结构体成员reflect.StructField结构中的Tag即结构体标签StructTag

结构体标签是对结构体字段的额外信息标签

JSON、BSON等格式进行序列化,ORM关系对象映射都会使用到结构体标签,使用标签设定字段在处理时应具备的特殊属性和可能发生的行为。这些信息都是静态的,无需实例化结构体可通过反射获取到。

结构体标签书写格式:key1:"value1" key2:"value2"

结构体标签由一个或多个键值对组成,键与值使用冒号分割,值使用双引号包裹,键值对之间使用空格分割。

从结构体标签中获取值

StructTag拥有方法可以对Tag信息进行解析和提取

  • Get方法根据Tag中的键获取对应的值
func (tag StructTag) Get(key string) string
  • Lookup方法根据Tag中的键查询值是否存在
func (tag StructTag) Lookup(key string) (value string, ok bool)

例如:

//声明结构体
type User struct {
    Id   int    `json:"id" bson:"id"`
    Name string `json:"name" bson:"name" orm:"size(32)"`
}
//创建结构体实例
ins := User{Id: 1, Name: "root"}
//获取结构体实例的反射类型对象
typ := reflect.TypeOf(ins)
//通过字段名获取信息
field, ok := typ.FieldByName("Name")
if ok {
    tag := field.Tag

    val, ok := tag.Lookup("bson")
    if ok {
        fmt.Printf("bson = %v\n", val) //bson = name
    }

    val = tag.Get("json")
    fmt.Printf("json = %v\n", val) //json = name
}

反射值对象

reflect.ValueOf

func reflect.ValueOf(rawValue interface{}) reflect.Value

例如:

val := reflect.ValueOf(nil)
fmt.Printf("%v\n", val) //<invalid reflect.Value>

例如:动态调用无参数的结构体方法

package test

import (
    "fmt"
    "reflect"
    "testing"
)

type User struct {
    Id   int
    Name string
}

func (u *User) Do() {
    fmt.Printf("User Do...\n")
}

func TestReflect(t *testing.T) {
    ins := &User{Id: 1, Name: "root"}
    val := reflect.ValueOf(ins)

    fname := "Do"
    val = val.MethodByName(fname)

    val.Call(nil)
}

例如:动态调用结构体带参数的方法

package test

import (
    "fmt"
    "reflect"
    "testing"
)

type User struct {
    Id   int
    Name string
}

func (u *User) Do(id int, msg string) {
    fmt.Printf("%v %v\n", id, msg)
}

func TestReflect(t *testing.T) {
    ins := &User{Id: 1, Name: "root"}
    val := reflect.ValueOf(ins)

    fname := "Do"
    val = val.MethodByName(fname)

    id := reflect.ValueOf(1)
    msg := reflect.ValueOf("hello")
    in := []reflect.Value{id, msg}
    val.Call(in)
}

reflect.Value

reflect.Value
  • reflect.ValueOf(rawValue interface)原值会转换为类型为reflect.Value的反射值对象。
type Value struct {
    typ *rtype
    ptr unsafe.Pointer
    flag
}
  • reflect.Value反射值对象可与原值通过包装和值获取相互转化

通过reflect.Value重新获取原始值

方法 返回值 描述
Interface() interface{} 通过类型断言转换为指定类型
Int() int64 将值以int类型返回
Uint() uint64 将值以uint类型返回
Float() float64 将值以双精度类型返回
Bool() bool 将值以bool类型返回
Bytes() []byte 将值以字节数组类型返回
String() string 将值以字符串类型返回

例如:

var i int = 1024

val := reflect.ValueOf(i)

var j int = val.Interface().(int)
fmt.Printf("j = %v\n", j)

var k int = int(val.Int())
fmt.Printf("k = %v\n", k)
  • 反射值对象reflect.Value对零值和空进行有效性判断
方法 返回值 描述
value.IsNil () bool 判断返回值是否为nil
value.IsValid() bool 判断返回值是否合法
func (v Value) IsNil() bool

若值类型不是通道、函数、接口、映射、指针、切片时会发生panic,类似语言层面的v == nil操作。

func (v Value) IsValid() bool

若值本身非法则返回false

例如:判断空指针

var ptr *int
isNil := reflect.ValueOf(ptr).IsNil()
fmt.Printf("%v\n", isNil) //true

例如:判断nil是否非法

isValid := reflect.ValueOf(nil).IsValid()
fmt.Printf("%v\n", isValid) //false
  • 通过反射修改变量的值

使用reflect.Value对包装的值进行修改时,需遵循一定规则,若没有按照规则进行代码设计和编写,则无法修改对象的值或运行时发生宕机。

反射值对象的判定及获取元素的方法

方法 返回值 描述
value.Elem() reflect.Value 获取值指向的元素值,类似*操作,当值类型不是指针或接口时会发生宕机,空指针返回nilValue
value.Addr() reflect.Value 对可寻址的值返回其地址,类似&操作,但值不可寻址时发生宕机。
value.CanAddr() bool 值是否可寻址
value.CanSet() bool 值是否可被修改,要求值可寻址且是可导出的字段。

反射类型对象

reflect.Type

reflect.Type

根据传入的结构体生成SQL语句

func BuildInsertSQL(obj interface{}) string {
    val := reflect.ValueOf(obj)
    typ := reflect.TypeOf(obj)
    //判断参数是否为结构体
    if val.Kind() != reflect.Struct {
        panic("unsupported type")
    }
    //获取结构体名作为数据表名
    typename := typ.Name()
    //遍历结构体字段
    var cols []string
    var vals []string
    for i := 0; i < val.NumField(); i++ {
        //获取字段名称
        col := typ.Field(i).Name
        cols = append(cols, fmt.Sprintf("`%s`", col))
        //获取字段值
        var v string
        field := val.Field(i)
        switch field.Kind() {
        case reflect.String:
            v = field.String()
        case reflect.Int:
            v = strconv.FormatInt(field.Int(), 10)
        default:
        }
        vals = append(vals, v)
    }
    //转换为字符串
    colstr := strings.Join(cols, ",")
    valstr := strings.Join(vals, ",")
    //拼接SQL
    query := fmt.Sprintf("INSERT INTO `%s`(%s) VALUES(%s)", typename, colstr, valstr)
    return query
}
type User struct {
    id   int
    name string
}
user := User{id: 1, name: "admin"}
sql := BuildInsertSQL(user)
fmt.Println(sql)
INSERT INTO `User`(`id`,`name`) VALUES(1,admin)

reflect.Elem

  • 指针与指针指向的元素
func (v reflect.Value) Elem() reflect.Value

Golang对指针获取反射对象时,通过reflect.Elem()方法获取指针指向的元素类型,这个获取过程被称为取元素,等效于对指针类型变量做了一个*操作。


Golang反射中对指针变量的种类都是Ptr

package test

import (
    "fmt"
    "reflect"
    "testing"
)

//声明空结构体
type User struct {
}

func TestReflect(t *testing.T) {
    //创建指针变量
    ins := &User{}
    //获取指针变量的反射类型信息
    typ := reflect.TypeOf(ins)

    //获取指针变量的类型名称
    name := typ.Name()
    //获取指针变量的种类
    kind := typ.Kind()
    fmt.Printf("name = %v, kind = %v\n", name, kind) //name = , kind = ptr

    //获取指针变量的元素类型
    elem := typ.Elem()
    //获取指针类型元素的名称
    name = elem.Name()
    //获取指针类型元素的种类
    kind = elem.Kind()
    fmt.Printf("name = %v, kind = %v\n", name, kind) //name = User, kind = struct
}

value.Elem()方法会返回反射值对象所包含的值或指针指向的值,若反射值对象的种类Kind不是接口或指针,则会发生panic。若反射值为0则返回零值。

ptr := (*int)(nil)

val := reflect.ValueOf(ptr)
fmt.Printf("%v\n", val) //<nil>

elem := val.Elem()
fmt.Printf("%v\n", elem) //<invalid reflect.Value>

isValid := elem.IsValid()
fmt.Printf("%v\n", isValid) //false

reflect.Indirect

func reflect.Indirect(v reflect.Value) reflect.Value

Golang的reflect.Indirect()函数用于获取v指向的值,若vnil指针则返回零值,若v不为指针返回为v

reflect.Elem()方法不同的是,Elem()方法当调用者的Kind种类不是Interface接口或Ptr指针类型时,会报panic。如果调用者为0,则返回零值。Indirect()的参数如果为nil指针时会返回零值。

当调用者或参数reflect.Value为指针时,value.Elem()等效于reflect.Indirect(value)。若不是指针则并非等效。

reflect.Indirect()用于需要接受待定类型或指向该类型的指针。

reflect.Kind

编程中使用最多的是类型(Type),反射中当需要区别一个大品种的类型时使用种类(Kind)。

例如:需要统一判断类型中的指针时,使用种类(Kind)较为方便。

Golang中类型(Type)是指系统原生的数据类型,比如intstringboolfloat32等,以及使用type关键字定义的类型,这些类型的名称就是其类型本身的名称。

种类(Kind)指的是对象归属的品种

reflect包中定义的种类Kind

type Kind uint
种类常量 描述
Invalid Kind 非法类型
Bool 布尔型
Int 有符号整型
Int8 有符号8位整型
Int16 有符号16位整型
Int32 有符号32位整型
Uint 无符号整型
Uint8 无符号8位整型
Uint16 无符号16位整型
Uint32 无符号32位整型
Uint64 无符号64位整型
Uintptr 指针
Float32 单精度浮点数
Float64 双精度浮点数
Complex64 64位复数类型
Complet128 128位复数类型
Array 数组
Chan 通道
Func 函数
Interface 接口
Map 映射
Ptr 指针
Slice 切片
String 字符串
Struct 结构体
UnsafePointer 底层指针

Map、Slice、Chan属于引用类型,使用起来类似指针,但在种类中仍属于独立的种类,而不属于Ptr。

例如:

type User struct{}

User结构体属于Struct种类,*User属于Ptr种类。


从类型对象reflect.Type中获取类型名称Name和种类Kind

  • 类型名称对应的反射获取方法是reflect.Type中的Name()方法,返回表示类型名称的字符串。
  • 类型归属的种类Kind使用的是reflect.Type中的Kind()方法,返回reflect.Kind类型的常量。

例如:从常量中获取类型信息

package test

import (
    "fmt"
    "reflect"
    "testing"
)

//定义枚举类型
type Enum int

//定义常量
const (
    Zero Enum = 0
)

func TestReflect(t *testing.T) {
    typ := reflect.TypeOf(Zero)
    name := typ.Name()                               //Enum
    kind := typ.Kind()                               //int
    fmt.Printf("Name = %v, Kind = %v\n", name, kind) //Name = Enum, Kind = int
}

例如:从结构体中获取类型信息

package test

import (
    "fmt"
    "reflect"
    "testing"
)

type Response struct {
    Code    int
    Message string
}

func TestReflect(t *testing.T) {
    //结构体实例
    response := Response{}
    //获取结构体实例的反射类型对象
    typ := reflect.TypeOf(response)
    //获取反射类型对象的名称和种类
    name := typ.Name()                               //Response
    kind := typ.Kind()                               //struct
    fmt.Printf("Name = %v, Kind = %v\n", name, kind) //Name = Response, Kind = struct
}

type.Implements

  • 判断实例是否已实现接口

创建接口

type IT interface {
    test()
}

创建结构体并实现接口方法

type T struct {
}

func (t *T) test() {
    fmt.Printf("T struct test run...\n")
}

判断结构体是否实现接口

t1 := reflect.TypeOf(&T{})
t2 := reflect.TypeOf((*IT)(nil)).Elem()

ok := t1.Implements(t2)
fmt.Printf("%v\n", ok)

reflect.Addr

func (v reflect.Value) reflect.Addr() reflect.Value

Golang中的reflect.Addr()函数用于获取表示v地址的指针值

reflect.New

func reflect.New(typ reflect.Type) reflect.Value

reflect.SliceOf

func reflect.SliceOf(t reflect.Type) reflect.Type
  • reflect.SliceOf() 函数用于获取元素类型为t的切片类型,如果t表示intSliceOf(t)表示[]int

value.Call

  • 反射中函数和方法的调用
func (v reflect.Value) Call(in []reflect.Value) []reflect.Value
  • 反射中调用函数

例如:Golang中函数类型可以像普通类型等类型变量作为值来使用

func test(){
    fmt.Println("fn running....")
}
func main(){
    fn := test
    fn()//fn running....
}

既然函数可以像普通类型变量一样,那么在反射机制中和不同的变量是一样的。

反射中函数和方法的类型(Type)都是reflect.Func,若需调用函数可通过ValueCall()方法实现。

func test(){
    fmt.Println("fn running....")
}
func main(){
    fn := test
    rv := reflect.ValueOf(fn)
    if rv.Kind() != reflect.Func{
        fmt.Println("INVIDAD TYPE")
    }
    rv.Call(nil)
}

reflect.ValueCall()方法的参数是一个[] reflect.Value切片,对应的反射函数类型的类型,返回值也是一个reflect.Value[] reflect.Value切片,同样对应反射函数类型的返回值。

func test(i int) string{
    fmt.Printf("fn running.... %d\n", i)
    return strconv.Itoa(i)
}
func main(){
    fn := test
    //判断类型
    rv := reflect.ValueOf(fn)
    if rv.Kind() != reflect.Func{
        fmt.Println("INVALID TYPE")
    }
    //设置参数
    args := make([]reflect.Value, 1)
    args[0] = reflect.ValueOf(100)
    //调用函数
    vs := rv.Call(args)
    fmt.Printf("%v\n", vs)//[100]
    fmt.Printf("%v\n", vs[0])//100
    rs := vs[0].Interface().(string)
    fmt.Printf("%v\n", rs)//100
}
  • 反射中调用方法

反射中方法的调用,函数和方法本质上是相同的,只不过方法与一个对象进行了绑定,方法是对象的一种行为,这种行为是对于这个对象的一系列操作。

type Users struct{
    id int
    name string
}
func (u *Users) SetId(id int){
    u.id = id
}
func (u *Users) SetName(name string){
    u.name = name
}
func (u *Users) String() string{
    return fmt.Sprintf("%p", u) + " name:"+u.name + " id:" + strconv.Itoa(u.id)
}
func main(){
    u := &Users{1, "root"}

    rv := reflect.ValueOf(&u).Elem()

    vs := rv.MethodByName("String").Call(nil)
    fmt.Printf("%v\n", vs[0])//0xc000004480 name:root id:1

    args := make([]reflect.Value, 1)
    args[0] = reflect.ValueOf(100)
    vs = rv.MethodByName("SetId").Call(args)
    fmt.Printf("%v\n", vs)//[]

    args[0] = reflect.ValueOf("admin")
    vs = rv.MethodByName("SetName").Call(args)
    fmt.Printf("%v\n", vs)//[]

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

推荐阅读更多精彩内容

  • Go reflect 反射实例解析 ——教坏小朋友系列 0 FBI WARNING 对于本文内容,看懂即可,完全不...
    楊浥塵阅读 695评论 0 3
  • 转载自: Go Reflect 最近在看一些go语言标准库以及第三方库的源码时,发现go的reflect被大量使用...
    lucasdada阅读 381评论 0 1
  • 在 reflect 包中,主要通过两个函数 TypeOf() 和 ValueOf() 实现反射,TypeOf() ...
    随风化作雨阅读 498评论 0 1
  • 一、认识反射 维基百科中的定义:在计算机科学中,反射是指计算机程序在运行时(Run time)可以访问、检测和修改...
    Every_dawn阅读 1,580评论 0 0
  • Reflect 本文侧重讲解reflect反射的实践应用,适合新手初窥门径。 reflect两个基本功能 refl...
    tinsonHo阅读 371评论 0 0