前言
今天心情很遭,公司举行的游戏比赛我们被2比0了,哎,还是默默地过来写.....简单来写下go的反射和语言交互性
反射
反射(reflection)是在Java出现后迅速流行起来的一种概念。通过反射,你可以获取丰富的类型信息,并可以利用这些类型信息做非常灵活的工作。就是在运行期间获取类的各种信息,为什么需要反射,因为有很多种情况下,只有在运行期间我们才知道具体运行的类,和类里面的属性。Go语言的反射实现了反射的大部分功能,但没有像Java语言那样内置类型工厂,故而无法做到像Java那样通过类型字符串创建对象实例。java中很多框架都有反射的影子,常见的spring/IoC都严重依赖反射的功能。
Go语言的反射需要理解两个概念Type和Value,它们也是Go语言中reflect空间里最重要的两个类型,举例如下:
type MyReader struct {
Name string
}
func (r MyReader)Read(p []byte) (n int, err error) {
// 实现自己的Read方法
}
MyReader类型实现了io.Reader接口的所有方法(其实就是一个Read()函数),所以MyReader实现了接口io.Reader。可以按如下方式来进行MyReader的实例化和赋值:
var reader io.Reader
reader = &MyReader{"a.txt"}
对所有接口进行反射,都可以得到一个包含Type和Value的信息结构。比如对上例的reader进行反射,也将得到一个Type和Value,Type为io.Reader,Value为MyReader{"a.txt"}。顾名思义,Type主要表达的是被反射的这个变量本身的类型信息,而Value则为该变量实例本身的信息。
语言交互性
自c语言诞生以来,后面有无数的新的语言实现,但是大部分的语言底层都封装了本地方法调用底层的C语言的方法,比如java提供了JNI机制来调用那些C代码库,Go语言也提供了对C语言的调用,称为Cgo,举例如下:
package main
import "fmt"
/*
#include <stdlib.h>
*/
import "C"
func Random() int {
return int(C.random())
}
func Seed(i int) {
C.srandom(C.uint(i))
}
func main() {
Seed(100)
fmt.Println("Random:", Random())
}
这个例子会调用C语言的random()和srandom()函数,运行结果如下:
事实上,根本就不存在一个名为C的包。这个import语句其实就是一个信号,告诉Cgo它应该开始工作了。做什么事情呢?就是对应这条import语句之前的块注释中的C源代码会自动生成包装性质的Go代码。因为函数调用从汇编的角度看,就是一个将参数按顺序压栈(push),然后进行函数调用(call)的过程。Cgo生成的代码只不过是帮你封装了这个压栈和调用的过程,从外面看起来就是一个普通的Go函数调用。
类型映射
类型映射是指两种语言交互,两种语言之间变量的转换关系,比如Go语言的string类型需要跟c语言中的字符数组进行对应,并且要保证映射到C语言的对象的生命周期足够长,以避免在C语言执行过程中该对象就已经被垃圾回收。对于C语言的原生类型,Cgo都会将其映射为Go语言中的类型,知道有这种对应关系就行。
字符串映射
因为Go语言中有明确的string原生类型,而C语言中用字符数组表示,Cgo提供了一系列函数来提供支持:C.CString、C.GoString和C.GoStringN。注意的是string每次的转换都是一次内存复制,字符串其实是不能修改的,比如java中的string两个值的拼接,不是修改原来的string的的值,而是新申请内存空间去存相加后的值,所有用到C.CString的代码大致都可以写成如下的风格:
var gostr string
// ... 初始化gostr
cstr := C.CString(gostr)
defer C.free(unsafe.Pointer(cstr))
// 接下来大胆地使用cstr吧,因为保证可以被释放掉了
// C.sprintf(cstr, "content is: %d", 123)
C程序
需要在Go语言调用任何C代码都可以在注释里面写C语言的代码直接调用如下:
package hello
/*
#include <stdio.h>
void hello() {
printf("Hello, Cgo! -- From C world.\n");
}
*/
import "C"
func Hello() int {
return int(C.hello())
}
主要是/*
和*/
之间的内容是写c语言的代码块,如果需要在这个c语言代码块调用非c语言的需求,Cgo提供了#cgo一种伪C文法,让开发者有机会指定依赖的第三方库和编译选项如下:
// #cgo CFLAGS: -DPNG_DEBUG=1
// #cgo linux CFLAGS: -DLINUX=1
// #cgo LDFLAGS: -lpng
// #include <png.h>
import "C"
函数调用
常规的调用直接函数名加上需要传递的参数就好,比如有错误的返回值的调用为:
n, err := C.atoi("a234")
在传递数组类型的参数时需要注意,在Go语言中将第一个元素的地址作为整个数组的起始地址传入,这一点就不如C语言本身直接传入数组名字那么方便了。如下:
n, err := C.f(&array[0]) // 需要显示指定第一个元素的地址
编译Cgo
编译Cgo代码非常容易,不需要做任何特殊的处理。Go安装后,会自带一个cgo命令行工具,它用于处理所有带有Cgo代码的Go文件,生成Go语言版本的调用封装代码。而Go工具集对cgo命令行工具再次进行了良好的封装,使构建过程能够自动识别和处理带有Cgo代码的Go源代码文件,完全不给用户增加额外的工作负担。
备注
本文正在参与「掘金Golang主题学习月」, 点击查看活动详情。