Go 有强烈的 C 背景, 除了语法具有继承性外, 其设计者以及其设计目标都与 C 语言有着千丝万缕的联系。在 Go 与 C 语言互操作 (Interoperability) 方面, Go 更是提供了强大的支持。尤其是在 Go 中使用 C, 你甚至可以直接在 Go 源文件中编写 C 代码, 这是其他语言所无法望其项背的。
在如下一些场景中, 可能会涉及到 Go 与 C 的互操作:
下面是一个短小的例子:
package main
// #include
// #include
/*
void print(char *str) {
printf("%s\n", str);
}
*/
import "C"
import "unsafe"
func main() {
s := "Hello Cgo"
cs := C.CString(s)
C.print(cs)
C.free(unsafe.Pointer(cs))
}
与 "正常"Go 代码相比, 上述代码有几处 “特殊” 的地方:
没错, 这就是在 Go 源码中调用 C 代码的步骤, 可以看出我们可直接在 Go 源码文件中编写 C 代码。
首先, Go 源码文件中的 C 代码是需要用注释包裹的, 就像上面的 include 头文件以及 print 函数定义;
其次, import “C” 这个语句是必须的, 而且其与上面的 C 代码之间不能用空行分隔, 必须紧密相连。这里的 “C” 不是包名, 而是一种类似名字空间的概念, 或可以理解为伪包, C 语言所有语法元素均在该伪包下面;
最后, 访问 C 语法元素时都要在其前面加上伪包前缀, 比如 C.uint 和上面代码中的 C.print、C.free 等。
我们如何来编译这个 go 源文件呢? 其实与 "正常"Go 源文件没啥区别, 依旧可以直接通过 go build 或 go run 来编译和执行。但实际编译过程中, go 调用了名为 cgo 的工具, cgo 会识别和读取 Go 源文件中的 C 元素, 并将其提取后交给 C 编译器编译, 最后与 Go 源码编译后的目标文件链接成一个可执行程序。这样我们就不难理解为何 Go 源文件中的 C 代码要用注释包裹了, 这些特殊的语法都是可以被 Cgo 识别并使用的。
在 Go 中可以用如下方式访问 C 原生的数值类型:
C.char,
C.schar (signed char),
C.uchar (unsigned char),
C.short,
C.ushort (unsigned short),
C.int, C.uint (unsigned int),
C.long,
C.ulong (unsigned long),
C.longlong (long long),
C.ulonglong (unsigned long long),
C.float,
C.double
Go 的数值类型与 C 中的数值类型不是一一对应的。因此在使用对方类型变量时少不了显式转型操作, 如 Go doc 中的这个例子:
func Random() int {
return int(C.random())//C.long -> Go 的 int
}
func Seed(i int) {
C.srandom(C.uint(i))//Go 的 uint -> C 的 uint
}
原生数值类型的指针类型可按 Go 语法在类型前面加上 *, 比如 var p *C.int。而 void * 比较特殊, 用 Go 中的 unsafe.Pointer 表示。任何类型的指针值都可以转换为 unsafe.Pointer 类型, 而 unsafe.Pointer 类型值也可以转换为任意类型的指针值。unsafe.Pointer 还可以与 uintptr 这个类型做相互转换。由于 unsafe.Pointer 的指针类型无法做算术操作, 转换为 uintptr 后可进行算术操作。
C 语言中并不存在正规的字符串类型, 在 C 中用带