Go-learning

10.语言详解--其他

10.1 内存布局

  • 了解对象内存布局,有助于理解值传递、引用传递等概念。
+--------+
| 1       |  int
+--------+
+--------+
| 3.14   |  float32
+--------+
+---+---+---+---+
| 1 | 2 | 3 | 4 |   [4]int
+---+---+---+---+

string

+---------+---------+
| pointer | len = 5 |   s = "hello"
+---------+---------+
    |
    |
    +---+---+---+---+---+
    | h | e | l | l | o |   [5]byte
    +---+---+---+---+---+
        |
        |
    +---------+---------+
    | pointer | len = 2 |   sub = s[1:3]
    +---------+---------+

struct

+---+---+---+---+---+---+---+---+
| 1 | 2 | 0 | 0 | 3 | 0 | 0 | 0 |  struct { a byte; b byte; c int32 } = { 1, 2, 3 }
+---+---+---+---+---+---+---+---+
  a    b          c
    +-----------+-----+
    | pointer a |   b |  struct { a *int; b int }
    +-----------+-----+
    |
    |
    +-----+
    | int |
    +-----+

slice

+---------+---------+---------+
| pointer | len = 8 | cap = 8 |       x = []int{0, 1, 2, 3, 4, 5, 6, 7 }
+---------+---------+---------+
    |
    |
    +---+---+---+---+---+---+---+---+
    | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |     [8]int
    +---+---+---+---+---+---+---+---+
        |
        |
    +---------+---------+---------+
    | pointer | len = 2 | cap = 5 |       y = x[1:3:6]
    +---------+---------+---------+

interface

+---------+---------+
| *itab   | *data   |     struct Iface
+---------+---------+
    |        |
    |        |
    +------+ +------+
    | Itab | | data |
    +------+ +------+

new

+---------+
| pointer |      s = new([3]int)
+---------+
    |
    |
    +---+---+---+
    | 0 | 0 | 0 |    [3]int
    +---+---+---+

make

+---------+---------+---------+
| pointer | len = 1 | cap = 3 |     slice = make([]int, 1, 3)
+---------+---------+---------+
    |
    |
    +---+---+---+
    | 0 | 0 | 0 |       [3]int
    +---+---+---+

+---------+
| pointer |     map = make(map[string]int); 实际返回的是一个指针包装对象。
+---------+
    |
    |
    ..................
    . .
    . hashmap.c Hmap .
    . .
    ..................

+---------+
| pointer |    channel = make(chan int); 实际返回的是一个指针包装对象。
+---------+
    |
    |
    ................
    . .
    . chan.c Hchan .
    . .
    ................

10.2 指针陷阱

  • 对象内存分配会受编译参数影响。举个例子,当函数返回对象指针时,必然在堆上分配。 可如果该函数被内联,那么这个指针就不会跨栈帧使用,就有可能直接在栈上分配,以实 现代码优化目的。因此,是否阻止内联对指针输出结果有很大影响。 允许指针指向对象成员,并确保该对象是可达状态。
  • 除正常指针外,指针还有 unsafe.Pointer 和 uintptr 两种形态。其中 uintptr 被 GC 当 做普通整数对象,它不能阻止所 "引用" 对象被回收。
type data struct {
    x [1024 * 100]byte
}

func test() uintptr {
    p := &data{}
    return uintptr(unsafe.Pointer(p))
}

func main() {
    const N = 10000
    cache := new([N]uintptr)

    for i := 0; i < N; i++ {
        cache[i] = test()
        time.Sleep(time.Millisecond)
    }
}

输出:

$ go build -o test && GODEBUG="gctrace=1" ./test
gc607(1): 0+0+0 ms, 0 -> 0 MB 50 -> 45 (3070-3025) objects
gc611(1): 0+0+0 ms, 0 -> 0 MB 50 -> 45 (3090-3045) objects
gc613(1): 0+0+0 ms, 0 -> 0 MB 50 -> 45 (3100-3055) objects
  • 合法的 unsafe.Pointer 被当做普通指针对待。
func test() unsafe.Pointer {
    p := &data{}
    return unsafe.Pointer(p)
}

func main() {
    const N = 10000
    cache := new([N]unsafe.Pointer)

    for i := 0; i < N; i++ {
        cache[i] = test()
        time.Sleep(time.Millisecond)
    }
}

输出:

$ go build -o test && GODEBUG="gctrace=1" ./test
gc12(1): 0+0+0 ms, 199 -> 199 MB 2088 -> 2088 (2095-7) objects
gc13(1): 0+0+0 ms, 399 -> 399 MB 4136 -> 4136 (4143-7) objects
gc14(1): 0+0+0 ms, 799 -> 799 MB 8232 -> 8232 (8239-7) objects
  • 指向对象成员的 unsafe.Pointer,同样能确保对象不被回收。
type data struct {
    x [1024 * 100]byte
    y int
}

func test() unsafe.Pointer {
    d := data{}
    return unsafe.Pointer(&d.y)
}

func main() {
    const N = 10000
    cache := new([N]unsafe.Pointer)

    for i := 0; i < N; i++ {
        cache[i] = test()
        time.Sleep(time.Millisecond)
    }
}

输出:

$ go build -o test && GODEBUG="gctrace=1" ./test
gc12(1): 0+0+0 ms, 207 -> 207 MB 2088 -> 2088 (2095-7) objects
gc13(1): 1+0+0 ms, 415 -> 415 MB 4136 -> 4136 (4143-7) objects
gc14(1): 3+1+0 ms, 831 -> 831 MB 8232 -> 8232 (8239-7) objects
  • 由于可以用 unsafe.Pointer、uintptr 创建 "dangling pointer" 等非法指针,所以在使用时需要特别小心。
  • 另外,cgo C.malloc 等函数所返回指针,与 GC 无关。指针构成的 "循环引用" 加上 runtime.SetFinalizer 会导致内存泄露。
type Data struct {
    d [1024 * 100]byte
    o *Data
}

func test() {
    var a, b Data
    a.o = &b
    b.o = &a
    runtime.SetFinalizer(&a, func(d *Data) { fmt.Printf("a %p final.\n", d) })
    runtime.SetFinalizer(&b, func(d *Data) { fmt.Printf("b %p final.\n", d) })
}

func main() {
    for {
        test()
        time.Sleep(time.Millisecond)
    }
}

输出:

$ go build -gcflags "-N -l" && GODEBUG="gctrace=1" ./test
gc11(1): 2+0+0 ms, 104 -> 104 MB 1127 -> 1127 (1180-53) objects
gc12(1): 4+0+0 ms, 208 -> 208 MB 2151 -> 2151 (2226-75) objects
gc13(1): 8+0+1 ms, 416 -> 416 MB 4198 -> 4198 (4307-109) objects
  • 垃圾回收器能正确处理 "指针循环引用",但无法确定 Finalizer 依赖次序,也就无法调用Finalizer 函数,这会导致目标对象无法变成不可达状态,其所占用内存无法被回收。

10.3 cgo

  • 通过 cgo,可在 Go 和 C/C++ 代码间相互调用。受 CGO_ENABLED 参数限制。
package main
/*
#include <stdio.h>
#include <stdlib.h>
void hello() {
printf("Hello, World!\n");
}
*/
import "C"

func main() {
  C.hello()
}
  • 调试 cgo 代码是件很麻烦的事,建议单独保存到 .c文件中。这样可以将其当做独立的 C程序进行调试。

test.h

#ifndef __TEST_H__
#define __TEST_H__

void hello();

#endif

test.c

#include <stdio.h>
#include "test.h"

void hello() {
  printf("Hello, World!\n");
}

#ifdef __TEST__ // 避免和 Go bootstrap main 冲突。

int main(int argc, char *argv[]) {
  hello();
  return 0;
}
#endif

main.go

package main
/*
#include "test.h"
*/
import "C"
func main() {
    C.hello()
}

编译和调试 C,只需在命令⾏行提供宏定义即可。 $ gcc -g -DTEST -o test test.c

  • 由于 cgo 仅扫描当前目录,如果需要包含其他 C 项目,可在当前目录新建一个 C文件,
  • 然后用 #include 指令将所需的 .h、.c 都包含进来,记得在 CFLAGS 中使用 "-I" 参数指定原路径。某些时候,可能还需指定 "-std" 参数。

10.3.1 Flags

  • 可使用 #cgo 命令定义 CFLAGS、LDFLAGS 等参数,自动合并多个设置。
/*
#cgo CFLAGS: -g
#cgo CFLAGS: -I./lib -D__VER__=1
#cgo LDFLAGS: -lpthread
#include "test.h"
*/
import "C"
  • 可设置 GOOS、GOARCH 编译条件,空格表⽰示 OR,逗号 AND,感叹号 NOT。
#cgo windows,386 CFLAGS: -I./lib -D__VER__=1

10.3.2 DataType

  • 数据类型对应关系。
          C                    cgo                 sizeof
    --------------------+--------------------+--------------------------------------------
    char                        C.char                    1
    signed char                 C.schar                   1
    unsigned char               C.uchar                   1
    short                       C.short                   2
    unsigned short              C.ushort                  2
    int                         C.int                     4
    unsigned int                C.uint                    4
    long                        C.long                    4 或 8
    unsigned long               C.ulong                   4 或 8
    long long                   C.longlong                8
    unsinged long long          C.ulonglong               8
    float                       C.float                   4
    double                      C.double                  8
    void*                       unsafe.Pointer
    char*                       *C.char
    size_t                      C.size_t
    NULL                        nil
  • 可将 cgo 类型转换为标准 Go 类型。
/*
int add(int x, int y) {
return x + y;
}
*/
import "C"

func main() {
    var x C.int = C.add(1, 2)
    var y int = int(x)
    fmt.Println(x, y)
}

10.3.3 String

  • 字符串转换函数。
/*
#include <stdio.h>
#include <stdlib.h>
void test(char *s) {
printf("%s\n", s);
}
char* cstr() {
return "abcde";
}
*/

import "C"
func main() {
    s := "Hello, World!"
    cs := C.CString(s) // 该函数在 C heap 分配内存,需要调⽤用 free 释放。
    defer C.free(unsafe.Pointer(cs)) // #include <stdlib.h>
    C.test(cs)
    cs = C.cstr()
    fmt.Println(C.GoString(cs))
    fmt.Println(C.GoStringN(cs, 2))
    fmt.Println(C.GoBytes(unsafe.Pointer(cs), 2))
}

输出:

Hello, World!
abcde
ab
[97 98]
  • 用 C.malloc/free 分配 C heap 内存。
/*
#include <stdlib.h>
*/
import "C"

func main() {
    m := unsafe.Pointer(C.malloc(4 * 8))
    defer C.free(m) // 注释释放内存。
    p := (*[4]int)(m) // 转换为数组指针。

    for i := 0; i < 4; i++ {
        p[i] = i + 100
    }
    fmt.Println(p)
}

输出:

&[100 101 102 103]
9.3.4 Struct/Enum/Union
  • 对 struct、enum 支持良好,union 会被转换成字节数组。如果没使用 typedef 定义,那么必须添加 struct、enum、union_ 前缀。

struct

/*
#include <stdlib.h>
struct Data {
int x;

};
typedef struct {
int x;
} DataType;
struct Data* testData() {
return malloc(sizeof(struct Data));
}
DataType* testDataType() {
return malloc(sizeof(DataType));
}
*/
import "C"

func main() {
    var d *C.struct_Data = C.testData()
    defer C.free(unsafe.Pointer(d))

    var dt *C.DataType = C.testDataType()
    defer C.free(unsafe.Pointer(dt))

    d.x = 100
    dt.x = 200

    fmt.Printf("%#v\n", d)
    fmt.Printf("%#v\n", dt)
}

输出:

```go
&main._Ctype_struct_Data{x:100}
&main._Ctype_DataType{x:200}

enum

/*
enum Color { BLACK = 10, RED, BLUE };
typedef enum { INSERT = 3, DELETE } Mode;
*/

import "C"

func main() {
    var c C.enum_Color = C.RED
    var x uint32 = c

    fmt.Println(c, x)
    var m C.Mode = C.INSERT
    fmt.Println(m)
}

union

/*
#include <stdlib.h>
union Data {
char x;
int y;
};
union Data* test() {
union Data* p = malloc(sizeof(union Data));
p->x = 100;
return p;
}
*/

import "C"

func main() {
    var d *C.union_Data = C.test()
    defer C.free(unsafe.Pointer(d))
    fmt.Println(d)
}

输出:

&[100 0 0 0]
### 9.3.5 Export

* 导出 Go 函数给 C调用,须使用 "//export" 标记。建议在独立头文件中声明函数原型,避免 "duplicate symbol" 错误。


main.go
```go
package main
import "fmt"
/*
#include "test.h"
*/
import "C"
//export hello

func hello() {
    fmt.Println("Hello, World!\n")
}

func main() {
    C.test()
}

test.h

#ifndef __TEST_H__
#define __TEST_H__

extern void hello();
void test();
#endif

test.c

#include <stdio.h>
#include "test.h"
void test() {
hello();
}

10.3.6 Shared Library

  • 在 cgo 中使用 C 共享库。

test.h

#ifndef __TEST_HEAD__
#define __TEST_HEAD__
int sum(int x, int y);
#endif

test.c

#include <stdio.h>
#include <stdlib.h>
#include "test.h"
int sum(int x, int y)
{
return x + y + 100;
}

编译成 .so 或 .dylib。

$ gcc -c -fPIC -o test.o test.c
$ gcc -dynamiclib -o libtest.dylib test.o

将共享库和头文件拷贝到 Go 项目目录。

main.go

package main
/*
#cgo CFLAGS: -I.
#cgo LDFLAGS: -L. -ltest
#include "test.h"
*/
import "C"
func main() {
    println(C.sum(10, 20))
}

输出:

$ go build -o test && ./test
130
  • 编译成功后可用 ldd 或 otool 查看动态库使用状态。 静态库使用方法类似。

10.4 Reflect

  • 没有运行期类型对象,实例也没有附加字段用来表明身份。只有转换成接口时,才会在其itab 内部存储与该类型有关的信息,Reflect 所有操作都依赖于此。

10.4.1 Type

  • 以 struct 为例,可获取其全部成员字段信息,包括非导出和匿名字段。
type User struct {
    Username string
}

type Admin struct {
    User
    title string
}

func main() {
    var u Admin
    t := reflect.TypeOf(u)

    for i, n := 0, t.NumField(); i < n; i++ {
        f := t.Field(i)
        fmt.Println(f.Name, f.Type)
    }
}

输出:

User main.User // 可进一步递归。
title string
  • 如果是指针,应该先使用 Elem方法获取目标类型,指针本身是没有字段成员的。
func main() {
u := new(Admin)
t := reflect.TypeOf(u)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
for i, n := 0, t.NumField(); i < n; i++ {
f := t.Field(i)
fmt.Println(f.Name, f.Type)
}
}
  • 同样,value-interface 和 pointer-interface 也会导致方法集存在差异。
type User struct {
}

type Admin struct {
    User
}

func (*User) ToString() {}

func (Admin) test() {}

func main() {
    var u Admin
    methods := func(t reflect.Type) {
        for i, n := 0, t.NumMethod(); i < n; i++ {
        m := t.Method(i)
        fmt.Println(m.Name)
        }
    }
    fmt.Println("--- value interface ---")
      methods(reflect.TypeOf(u))
    fmt.Println("--- pointer interface ---")
      methods(reflect.TypeOf(&u))
}

输出:

--- value interface ---
test
--- pointer interface ---
ToString
test
  • 可直接用名称或序号访问字段,包括用多级序号访问嵌入字段成员。
type User struct {
    Username string
    age int
}

type Admin struct {
    User
    title string
}

func main() {
    var u Admin
    t := reflect.TypeOf(u)
    f, _ := t.FieldByName("title")
    fmt.Println(f.Name)

    f, _ = t.FieldByName("User") // 访问嵌⼊入字段。
    fmt.Println(f.Name)

    f, _ = t.FieldByName("Username") // 直接访问嵌⼊入字段成员,会⾃自动深度查找。
    fmt.Println(f.Name)

    f = t.FieldByIndex([]int{0, 1}) // Admin[0] -> User[1] -> age
    fmt.Println(f.Name)
}

输出:

title
User
Username
age
  • 字段标签可实现简单元数据编程,比如标记 ORM Model 属性。
type User struct {
    Name string `field:"username" type:"nvarchar(20)"`
    Age int `field:"age" type:"tinyint"`
}

func main() {
    var u User
    t := reflect.TypeOf(u)
    f, _ := t.FieldByName("Name")

    fmt.Println(f.Tag)
    fmt.Println(f.Tag.Get("field"))
    fmt.Println(f.Tag.Get("type"))
}

输出:

field:"username" type:"nvarchar(20)"
username
nvarchar(20)
  • 可从基本类型获取所对应复合类型。
var (
Int = reflect.TypeOf(0)
String = reflect.TypeOf("")
)

func main() {
    c := reflect.ChanOf(reflect.SendDir, String)
    fmt.Println(c)

    m := reflect.MapOf(String, Int)
    fmt.Println(m)

    s := reflect.SliceOf(Int)
    fmt.Println(s)

    t := struct{ Name string }{}
    p := reflect.PtrTo(reflect.TypeOf(t))
    fmt.Println(p)
}

输出:

chan<- string
map[string]int
[]int
*struct { Name string }

与之对应方法 Elem 可返回复合类型的基类型。

func main() {
    t := reflect.TypeOf(make(chan int)).Elem()
    fmt.Println(t)
}
  • 方法 Implements 判断是否实现了某个具体接口,AssignableTo、ConvertibleTo用于赋值和转换判断。
type Data struct {
}

func (*Data) String() string {
    return ""
}

func main() {
    var d *Data

    t := reflect.TypeOf(d)
    // 没法直接获取接⼝口类型,好在接口本身是个 struct,创建
    // 一个空指针对象,这样传递给 TypeOf 转换成 interface{}
    // 时就有类型信息了。。
    it := reflect.TypeOf((*fmt.Stringer)(nil)).Elem()
    // 为啥不是 t.Implements(fmt.Stringer),完全可以由编译器生成。
    fmt.Println(t.Implements(it))
}
  • 某些时候,获取对齐信息对于内存自动分析是很有用的。
type Data struct {
    b byte
    x int32
}

func main() {
    var d Data
    t := reflect.TypeOf(d)
    fmt.Println(t.Size(), t.Align()) // sizeof,以及最宽字段的对齐模数。
    f, _ := t.FieldByName("b")
    fmt.Println(f.Type.FieldAlign()) // 字段对齐。
}

输出:

8 4
1

10.4.2 Value

  • Value 和 Type 使用方法类似,包括使用 Elem 获取指针目标对象。
type User struct {
    Username string
    age int
}

type Admin struct {
    User
    title string
}

func main() {
    u := &Admin{User{"Jack", 23}, "NT"}
    v := reflect.ValueOf(u).Elem()

    fmt.Println(v.FieldByName("title").String()) // 用转换方法获取字段值
    fmt.Println(v.FieldByName("age").Int()) // 直接访问嵌入字段成员
    fmt.Println(v.FieldByIndex([]int{0, 1}).Int()) // 用多级序号访问嵌入字段成员
}

输出:

NT
23
23
  • 除具体的 Int、String 等转换方法,还可返回 interface{}。只是非导出字段无法使用,需用 CanInterface 判断一下。
type User struct {
    Username string
    age int
}

func main() {
    u := User{"Jack", 23}
    v := reflect.ValueOf(u)

    fmt.Println(v.FieldByName("Username").Interface())
    fmt.Println(v.FieldByName("age").Interface())
}

输出:

Jack
panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
  • 当然,转换成具体类型不会引发 panic。
func main() {
    u := User{"Jack", 23}
    v := reflect.ValueOf(u)
    f := v.FieldByName("age")

    if f.CanInterface() {
        fmt.Println(f.Interface())
    } else {
        fmt.Println(f.Int())
    }
}
  • 除 struct,其他复合类型 array、slice、map 取值示例。
func main() {
    v := reflect.ValueOf([]int{1, 2, 3})

    for i, n := 0, v.Len(); i < n; i++ {
        fmt.Println(v.Index(i).Int())
    }

    fmt.Println("---------------------------")
    v = reflect.ValueOf(map[string]int{"a": 1, "b": 2})

    for _, k := range v.MapKeys() {
        fmt.Println(k.String(), v.MapIndex(k).Int())
    }
}

输出:

1
2
3
---------------------------
a 1
b 2
  • 需要注意,Value 某些方法没有遵循 "comma ok" 模式,而是返回 ZeroValue,因此需要用 IsValid 判断一下是否可用。
func (v Value) FieldByName(name string) Value {
    v.mustBe(Struct)

    if f, ok := v.typ.FieldByName(name); ok {
        return v.FieldByIndex(f.Index)
    }
    return Value{}
}

type User struct {
    Username string
    age int
}

func main() {
    u := User{}
    v := reflect.ValueOf(u)
    f := v.FieldByName("a")

    fmt.Println(f.Kind(), f.IsValid())
}

输出:

invalid false
  • 另外,接口是否为 nil,需要 tab 和 data 都为空。可使用 IsNil 方法判断 data 值。
func main() {
    var p *int
    var x interface{} = p

    fmt.Println(x == nil)

    v := reflect.ValueOf(p)

    fmt.Println(v.Kind(), v.IsNil())
}

输出:

false
ptr true
  • 将对象转换为接口,会发生复制行为。该复制品只读,无法被修改。所以要通过接口改变目标对象状态,必须是 pointer-interface。
  • 就算是指针,我们依然没法将这个存储在 data 的指针指向其他对象,只能透过它修改目标对象。目标对象并没有被复制,被复制的只是指针。
type User struct {
    Username string
    age int
}

func main() {
    u := User{"Jack", 23}
    v := reflect.ValueOf(u)
    p := reflect.ValueOf(&u)

    fmt.Println(v.CanSet(), v.FieldByName("Username").CanSet())
    fmt.Println(p.CanSet(), p.Elem().FieldByName("Username").CanSet())
}

输出:

false false
false true
  • 非导出字段无法直接修改,可改用指针操作。
type User struct {
    Username string
    age int
}

func main() {
    u := User{"Jack", 23}
    p := reflect.ValueOf(&u).Elem()
    p.FieldByName("Username").SetString("Tom")
    f := p.FieldByName("age")

    fmt.Println(f.CanSet())
    // 判断是否能获取地址。
    if f.CanAddr() {
        age := (*int)(unsafe.Pointer(f.UnsafeAddr()))
        // age := (*int)(unsafe.Pointer(f.Addr().Pointer())) // 等同
        *age = 88
    }
    // 注意 p 是 Value 类型,需要还原成接口才能转型。
    fmt.Println(u, p.Interface().(User))
}

输出:

false
{Tom 88} {Tom 88}
  • 复合类型修改示例。
func main() {
    s := make([]int, 0, 10)
    v := reflect.ValueOf(&s).Elem()
    v.SetLen(2)
    v.Index(0).SetInt(100)
    v.Index(1).SetInt(200)
    fmt.Println(v.Interface(), s)

    v2 := reflect.Append(v, reflect.ValueOf(300))
    v2 = reflect.AppendSlice(v2, reflect.ValueOf([]int{400, 500}))

    fmt.Println(v2.Interface())
    fmt.Println("----------------------")

    m := map[string]int{"a": 1}
    v = reflect.ValueOf(&m).Elem()
    v.SetMapIndex(reflect.ValueOf("a"), reflect.ValueOf(100)) // update
    v.SetMapIndex(reflect.ValueOf("b"), reflect.ValueOf(200)) // add

    fmt.Println(v.Interface(), m)
}

输出:

[100 200] [100 200]
[100 200 300 400 500]
----------------------
map[a:100 b:200] map[a:100 b:200]
9.4.3 Method
  • 可获取方法参数、返回值类型等信息。
type Data struct {
}

func (*Data) Test(x, y int) (int, int) {
    return x + 100, y + 100
}

func (*Data) Sum(s string, x ...int) string {
    c := 0
    for _, n := range x {
        c += n
    }
    return fmt.Sprintf(s, c)
}

func info(m reflect.Method) {
    t := m.Type
    fmt.Println(m.Name)

    for i, n := 0, t.NumIn(); i < n; i++ {
        fmt.Printf(" in[%d] %v\n", i, t.In(i))
    }

    for i, n := 0, t.NumOut(); i < n; i++ {
        fmt.Printf(" out[%d] %v\n", i, t.Out(i))
    }
}

func main() {
    d := new(Data)
    t := reflect.TypeOf(d)

    test, _ := t.MethodByName("Test")
    info(test)
    sum, _ := t.MethodByName("Sum")
    info(sum)
}

输出:

Test
in[0] *main.Data // receiver
in[1] int
in[2] int
out[0] int
out[1] int
Sum
in[0] *main.Data
in[1] string
in[2] []int
out[0] string
  • 动态调用方法很简单,按 In 列表准备好所需参数即可 (不包括 receiver)。
func main() {
    d := new(Data)
    v := reflect.ValueOf(d)

    exec := func(name string, in []reflect.Value) {
        m := v.MethodByName(name)
        out := m.Call(in)

        for _, v := range out {
            fmt.Println(v.Interface())
        }
    }
    exec("Test", []reflect.Value{
        reflect.ValueOf(1),
        reflect.ValueOf(2),
    })

    fmt.Println("-----------------------")

    exec("Sum", []reflect.Value{
        reflect.ValueOf("result = %d"),
        reflect.ValueOf(1),
        reflect.ValueOf(2),
    })
}

输出:

101
102
-----------------------
result = 3
  • 如改用 CallSlice,只需将变参打包成 slice 即可。
func main() {
    d := new(Data)
    v := reflect.ValueOf(d)
    m := v.MethodByName("Sum")

    in := []reflect.Value{
        reflect.ValueOf("result = %d"),
        reflect.ValueOf([]int{1, 2}), // 将变参打包成 slice。
    }

    out := m.CallSlice(in)
    for _, v := range out {
        fmt.Println(v.Interface())
    }
}
  • 非导出方法无法调用,甚至无法透过指针操作,因为接口类型信息中没有该方法地址。

10.4.4 Make

  • 利用 Make、New 等函数,可实现近似泛型操作。
var (
    Int = reflect.TypeOf(0)
    String = reflect.TypeOf("")
)

func Make(T reflect.Type, fptr interface{}) {
    // 实际创建 slice 的包装函数。
    swap := func(in []reflect.Value) []reflect.Value {
        // --- 省略算法内容 --- //
        // 返回和类型匹配的 slice 对象。
        return []reflect.Value{
            reflect.MakeSlice(
                reflect.SliceOf(T), // slice type
                int(in[0].Int()), // len
                int(in[1].Int()) // cap
            ),
        }
    }
    // 传入的是函数变量指针,因为我们要将变量指向 swap 函数。
    fn := reflect.ValueOf(fptr).Elem()
    // 获取函数指针类型,生成所需 swap function value。
    v := reflect.MakeFunc(fn.Type(), swap)
    // 修改函数指针实际指向,也就是 swap。
    fn.Set(v)
}

func main() {
    var makeints func(int, int) []int
    var makestrings func(int, int) []string
    // 用相同算法,生成不同类型创建函数。
    Make(Int, &makeints)
    Make(String, &makestrings)
    // 按实际类型使用。
    x := makeints(5, 10)
    fmt.Printf("%#v\n", x)

    s := makestrings(3, 10)
    fmt.Printf("%#v\n", s)
}

输出:

[]int{0, 0, 0, 0, 0}
[]string{"", "", ""}
  • 原理并不复杂。
      1. 核心是提供一个 swap 函数,其中利用 reflect.MakeSlice生成最终 slice 对象,因此需要传入 element type、len、cap 参数。
      1. 接下来,利用 MakeFunc 函数生成 swap value,并修改函数变量指向,以达到调用 swap 的目的。
      1. 当调用具体类型的函数变量时,实际内部调用的是 swap,相关代码会自动转换参数列表,并将返回结果还原成具体类型返回值。在共享算法的前提下,无须用 interface{}无须做类型转换,颇有泛型的效果