这一个星期,主要是在敲代码。照着例子敲,随着一遍遍的敲,对之前不太懂的 go 语言也渐渐清楚了一些。在这里,将开发ChainCode过程中用到的 GO 语言总结一下。
在 Fabric 中, 开发一个智能合约,简单来讲,就是定义一个结构体,然后给这个结构体实现两个接口函数 Init( ) 和 Invoke( )。
所以,接下来会根据ChainCode开发经验,逐一介绍此过程中出现的Go 语言基础知识。全文将按照如下流程展开:
一、Go 语言中的结构体
Go 语言的结构体(struct)和其他语言的类(class)有同等的地位,但 Go 语言放弃了包括继承在内的大量面向对象特性,只保留了组合(composition)这个最基础的特性。组合是复合类型的基础。在 Go 语言中, 所有的类型(指针类型除外)都可以有自己的方法。所以,Go 语言的结构体也可以定义自己的方法。
1、Go 语言结构体的定义
例如,我们要定义一个矩形类型:
type Rect struct {
x, y float64
width, height float64
}
2、Go 语言结构体成员函数的定义
然后我们定义成员方法 Area()来计算矩形的面积:
func (r *Rect) Area() float64 {
return r.width * r.height
}
在 Go 语言中,func 为定义函数的关键字。(r *Rect)表示该函数为结构体 Rect 的成员函数。该函数的返回值类型为 float64,写在函数定义行的最后。
3、Go 语言结构体的初始化
在定义了 Rect 类型后,该如何创建并初始化 Rect 类型的实例呢?这可以通过如下几种方法实现:
rect1 := new{Rect}
rect2 := &Rect{}
retc3 := &Rect{0,0,100,200}
retc4 := &Rect{width: 100, height: 200}
在 Go 语言中,未进行显式初始化的变量都会被初始化为该类型的零值,例如 bool 类型的零值为 false,int 类型的零值为 0,string 类型的零值为空字符串.
在 Go 语言中没有构造函数的概念,对象的创建通常交由一个全局的创建函数来完成,以 NewXXX 来命名,表示“构造函数”:
func NewRect(x, y, width, height float64) *Rect {
return &Rect{x, y, width, height}
}
这一切非常自然,开发者也不需要分析在使用了 new 之后到底背后发生了多少事情。在 Go 语言中,一切要发生的事情都直接可以看到。
二、Go 语言的接口
1、侵入式接口
Go 语言的接口并不是其他语言(C++、Java、C#等)中所提供的接口概念。在 Go 语言出现之前,接口主要作为不同组件之间的契约存在。对契约的实现是强制的,你必须声明你的确实现了该接口。为了实现一个接口,你需要从该接口继承。如在 java 语言中:
interface IFoo {
void Bar();
}
class Foo implements IFoo { // Java 文法
// ...
}
要实现 IFoo 接口,必须要在 Foo 类的实现过程中声明实现该接口。
这类接口我们称为侵入式接口。
“侵入式”的主要表现在于实现类需要明确声明自己实现了某个接口。
2、非侵入式接口
在 Go 语言中,一个类只需要实现了接口要求的所有函数,我们就说这个类实现了该接口,
type File struct {
// ...
}
func (f *File) Read(buf []byte) (n int, err error)
func (f *File) Write(buf []byte) (n int, err error)
func (f *File) Seek(off int64, whence int) (pos int64, err error)
func (f *File) Close() error
这里我们定义了一个 File 类,并实现有 Read()、Write()、Seek()、Close()等方法。设
想我们有如下接口:
type IFile interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
Seek(off int64, whence int) (pos int64, err error)
Close() error
}
type IReader interface {
Read(buf []byte) (n int, err error)
}
type IWriter interface {
Write(buf []byte) (n int, err error)
}
type ICloser interface {
Close() error
}
尽管 File 类并没有从这些接口继承,甚至可以不知道这些接口的存在,但是 File 类实现了
这些接口,可以进行赋值:
var file1 IFile = new(File)
var file2 IReader = new(File)
var file3 IWriter = new(File)
var file4 ICloser = new(File)
因此,在 Go 语言的非侵入式接口中,我们实现某一接口,并不需要在进行声明。
三、Go 的 strconv 包的使用
在 go 语言中,用到了很多的字符串处理相关的包,在ChainCode的编写过程中,与字符串相关的类型转换都是通过 strconv
包实现的。这里主要介绍该包的两个字符转换函数。
1、strconv.Atoi
该函数的主要功能是将字符串类型转换为数字类型。
该函数原型如下:
strconv.Atoi(s string) (i int, err error)
将字符串转换为 int 型。
利用多返回值的特性,该函数会返回 2 个值,第 1 个是转换后的结果(如果转换成功),第 2 个是可能出现的错误,因此,我们一般使用以下形式来进行从字符串到其它类型的转换
val, err = strconv.Atoi(s)
2、strconv.Itoa
该函数的主要功能是将数字类型转换到字符串类型。
该函数原型如下:
strconv.Itoa(i int) string
返回数字 i 所表示的字符串类型的十进制数。
该函数仅仅返回一个十进制数对应的字符串类型。
四、Go 中 json 包的使用
在目前ChainCode的开发过程中,选择的数据库是 CouchDB。
CouchDB 是一种文档型数据库,CouchDB 支持原生的 JSON 和字节数组的操作,基于 JSON 的操作,可以支持复杂的查询。如果存储的数据是字节数组,也支持基本的键值对操作。所以在这个过程中,不可避免的要在结构体对象和 JSON 数据之间进行相互转换。这里主要介绍两个函数。
1、json.Marshal | 将一个对象转换为 json 格式的数据
- 函数原型如下:
func Marshal (v interface{ }) ([]byte, error)
在 go 语言中,要把数据转换为 json 格式,可以使用 encoding/json 包中的 Marshal 函数进行数据的转换,Marshal 函数可以接收任意类型的参数,转换完成后,返回字节码数组和错误信息。
- 使用案例
创建一个结构
type Message struct {
Name string
Body string
Time int64
}
使用 Marshal 函数对数据进行转换
m := Message{"Alice", "Hello", 1294706395881547000}
b, err := json.Marshal(m)
关于 Struct tag 的使用。
在将对象转换成 Json 数据的过程中,有一个关于 Struct tag 的知识点。可以使用它来改变对象所对应的 json 数据的字段。
比如,对于如下结构体
type Message struct {
Name string
Body string
Time int64
}
假如我想让 Name 换成小写 name,或者其他的字段,
例如 username,我就可以加上 struct tag:(以 username 为例
type Message struct {
Name string `json:”username”`
Body string
Time int64
}
这样子得到的 json 数据如下:
{
username: “Alice”,
Body: “Hello”,
Time: 1294706395881547000,
}
2、json.Unmarshal | 将 json 数据解析成一个对象
要解析 json 数据,可以使用 encoding/json 包中的 Unmarshal 函数进行解析
- 该函数的原型如下:
func Unmarshal(data []byte, v interface{}) error
该函数输入一个 json 字符串,将其解析成对象 v。转换完成之后,返回错误信息。
- 使用案例
上接 json.marshal 的使用案例。首先,需要创建一个结构,用来存储转换后的数据
var m Message
解析 json 数据
err := json.Unmarshal(b, &m)
五、Go 语言中的 nil
在编写 go 语言程序的时候,经常会写如下判断
if err != nil{
// do something
}
当出现不等于 nil 的时候,说明出现某些错误了,需要我们对这个错误进行一些处理,
而如果等于 nil 说明运行正常。
那什么是 nil 呢?
nil 的意思是无,或者是零值。
在 Go 语言中,如果你声明了一个变量但是没有对它进行赋值操作,那么这个变量就会有一个类型的默认零值。对于未进行显式初始化的变量都会被初始化为该类型的零。
例如 bool 类型的零值为 false,int 类型的零值为 0,string 类型的零值为空字符串.
Go 的文档中说到,nil 是预定义的标识符,代表指针、通道、函数、接口、映射或切片的零值,也就是预定义好的一个变量。
参考资料:
《Go 语言编程》-第三章-面向对象编程-许式伟、吕桂华
转自知乎 苏小乐 :https://www.zhihu.com/people/shan-de-ding-zhu/activities