go build介绍

写的很好:go build -- 极客学院

go build
go build命令用于编译我们指定的源码文件或代码包以及它们的依赖包。

例如,如果我们在执行go build命令时不后跟任何代码包,那么命令将试图编译当前目录所对应的代码包。例如,我们想编译goc2p项目的代码包logging。其中一个方法是进入logging目录并直接执行该命令:

hc@ubt:~/golang/goc2p/src/logging$ go build
因为在代码包logging中只有库源码文件和测试源码文件,所以在执行go build命令之后不会在当前目录和goc2p项目的pkg目录中产生任何文件。

插播:Go语言的源码文件有三大类,即:命令源码文件、库源码文件和测试源码文件。他们的功用各不相同,而写法也各有各的特点。命令源码文件总是作为可执行的程序的入口。库源码文件一般用于集中放置各种待被使用的程序实体(全局常量、全局变量、接口、结构体、函数等等)。而测试源码文件主要用于对前两种源码文件中的程序实体的功能和性能进行测试。另外,后者也可以用于展现前两者中程序的使用方法。

另外一种编译logging包的方法是:

hc@ubt:~/golang/goc2p/src$ go build logging
在这里,我们把代码包logging的导入路径作为参数传递给go build命令。另一个例子:如果我们要编译代码包cnet/ctcp,只需要在任意目录下执行命令go build cnet/ctcp即可。

插播:之所以这样的编译方法可以正常执行,是因为我们已经在环境变量GOPATH中加入了goc2p项目的根目录(即~/golang/goc2p/)。这时,goc2p项目的根目录就成为了一个工作区目录。只有这样,Go语言才能正确识别我们提供的goc2p项目中某个代码包的导入路径。而代码包的导入路径是指,相对于Go语言自身的源码目录(即$GOROOT/src)或我们在环境变量GOPATH中指定的某个目录的src子目录下的子路径。例如,这里的代码包logging的绝对路径是~/golang/goc2p/src/logging。而不论goc2p项目的根文件夹被放在哪儿,logging包的导入路径都是logging。显而易见,我们在称呼一个代码包的时候总是以其导入路径作为其称谓。

言归正传,除了上面的简单用法,我们还可以同时编译多个Go源码文件:

hc@ubt:~/golang/goc2p/src$ go build logging/base.go logging/console_logger.go logging/log_manager.go logging/tag.go
但是,使用这种方法会有一个限制。作为参数的多个Go源码文件必须在同一个目录中。也就是说,如果我们想用这种方法既编译logging包又编译basic包是不可能的。不过别担心,在需要的时候,那些被编译目标依赖的代码包会被go build命令自动的编译。例如,如果有一个导入路径为app的代码包,同时依赖了logging包和basic包。那么在执行go build app的时候,该命令就会自动的在编译app包之前去检查logging包和basic包的编译状态。如果发现它们的编译结果文件不是最新的,那么该命令就会先去的编译这两个代码包,然后再编译app包。

注意,go build命令在编译只包含库源码文件的代码包(或者同时编译多个代码包)时,只会做检查性的编译,而不会输出任何结果文件。

另外,go build命令既不能编译包含多个命令源码文件的代码包,也不能同时编译多个命令源码文件。因为,如果把多个命令源码文件作为一个整体看待,那么每个文件中的main函数就属于重名函数,在编译时会抛出重复定义错误。假如,在goc2p项目的代码包cmd(此代码包仅用于示例目的,并不会永久存在于该项目中)中包含有两个命令源码文件showds.go和initpkg_demo.go,那么我们在使用go build命令同时编译它们时就会失败。示例如下:

hc@ubt:~/golang/goc2p/src/cmd$ go build showds.go initpkg_demo.go
# command-line-arguments
./initpkg_demo.go:19: main redeclared in this block
        previous declaration at ./showds.go:56
请注意上面示例中的command-line-arguments。在这个位置上应该显示的是作为编译目标的源码文件所属的代码包的导入路径。但是,这里显示的并不是它们所属的代码包的导入路径cmd。这是因为,命令程序在分析参数的时候如果发现第一个参数是Go源码文件而不是代码包,则会在内部生成一个虚拟代码包。这个虚拟代码包的导入路径和名称都会是command-line-arguments。在其他基于编译流程的命令程序中也有与之一致的操作,比如go install命令和go run命令。

另一方面,如果我们编译的多个属于main包的源码文件中没有main函数的声明,那么就会使编译器立即报出“未定义main函数声明”的错误并中止编译。换句话说,在我们同时编译多个main包的源码文件时,要保证其中有且仅有一个main函数声明,否则编译是无法成功的。

现在我们使用go build命令编译单一命令源码文件。我们在执行命令时加入一个标记-v。这个标记的意义在于可以使命令把执行过程中构建的包名打印出来。我们会在稍后对这个标记进行详细说明。现在我们先来看一个示例:

hc@ubt:~/golang/goc2p/src/basic/pkginit$ ls
initpkg_demo.go
hc@ubt:~/golang/goc2p/src/basic/pkginit$ go build -v initpkg_demo.go 
command-line-arguments
hc@ubt:~/golang/goc2p/src/basic/pkginit$ ls
initpkg_demo  initpkg_demo.go
我们在执行命令go build -v initpkg_demo.go之后被打印出的command-line-arguments”`就是命令程序为命令源码文件initpkg_demo.go生成的虚拟代码包的包名。顺带说一句,

命令go build会把编译命令源码文件后生成的结果文件存放到执行该命令时所在的目录下。这个所说的结果文件就是与命令源码文件对应的可执行文件。它的名称会与命令源码文件的主文件名相同。

顺便说一下,如果我们有多个声明为属于main包的源码文件,且其中只有一个文件声明了main函数的话,那么是可以使用go build命令同时编译它们的。在这种情况下,不包含main函数声明的那几个源码文件会被视为库源码文件(理所当然)。如此编译之后的结果文件的名称将会与我们指定的编译目标中最左边的那个源码文件的主文件名相同。

其实,除了让Go语言编译器自行决定可执行文件的名称,我们还可以自定义它。示例如下:

hc@ubt:~/golang/goc2p/src/basic/pkginit$ go build -o initpkg initpkg_demo.go 
hc@ubt:~/golang/goc2p/src/basic/pkginit$ ls
initpkg    initpkg_demo.go
使用-o标记可以指定输出文件(在这个示例中指的是可执行文件)的名称。它是最常用的一个go build命令标记。但需要注意的是,当使用标记-o的时候,不能同时对多个代码包进行编译。

标记-i会使go build命令安装那些编译目标依赖的且还未被安装的代码包。这里的安装意味着产生与代码包对应的归档文件,并将其放置到当前工作区目录的pkg子目录的相应子目录中。在默认情况下,这些代码包是不会被安装的。

除此之外,还有一些标记不但受到go build命令的支持,而且对于后面会提到的go install、go run、go test等命令同样是有效的。下表列出了其中比较常用的标记。

表0-1 go build命令的常用标记说明

标记名称    标记描述
-a  强行对所有涉及到的代码包(包含标准库中的代码包)进行重新构建,即使它们已经是最新的了。
-n  打印编译期间所用到的其它命令,但是并不真正执行它们。
-p n    指定编译过程中执行各任务的并行数量(确切地说应该是并发数量)。在默认情况下,该数量等于CPU的逻辑核数。但是在darwin/arm平台(即iPhone和iPad所用的平台)下,该数量默认是1。
-race   开启竞态条件的检测。不过此标记目前仅在linux/amd64、freebsd/amd64、darwin/amd64和windows/amd64平台下受到支持。
-v  打印出那些被编译的代码包的名字。
-work   打印出编译时生成的临时工作目录的路径,并在编译结束时保留它。在默认情况下,编译结束时会删除该目录。
-x  打印编译期间所用到的其它命令。注意它与-n标记的区别。
我们在这里忽略了一些并不常用的或作用于编译器或连接器的标记。在本小节的最后将会对这些标记进行简单的说明。如果读者有兴趣,也可以查看Go语言的官方文档以获取相关信息。

下面我们就用其中几个标记来查看一下在构建代码包logging时创建的临时工作目录的路径:

hc@ubt:~/golang/goc2p/src$ go build -v -work logging
WORK=/tmp/go-build888760008
logging
上面命令的结果输出的第一行是为了编译logging包,Go创建的一个临时工作目录,这个目录被创建到了Linux的临时目录下。输出的第二行是对标记-v的响应。这意味着此次命令执行时仅编译了logging包。关于临时工作目录的用途和内容,我们会在讲解go run命令和go test命令的时候详细说明。

现在我们再来看看如果强制重新编译会涉及到哪些代码包:

hc@ubt:~/golang/goc2p/src$ go build -a -v -work logging
WORK=/tmp/go-build929017331
runtime
errors
sync/atomic
math
unicode/utf8
unicode
sync
io
syscall
strings
time
strconv
reflect
os
fmt
log
logging
怎么会多编译了这么多代码包呢?可以确定的是,代码包logging中的代码直接依赖了标准库中的runtime包、strings包、fmt包和log包。那么其他的代码包为什么也会被重新编译呢?

从代码包编译的角度来说,如果代码包A依赖代码包B,则称代码包B是代码包A的依赖代码包(以下简称依赖包),代码包A是代码包B的触发代码包(以下简称触发包)。

go build命令在执行时,编译程序会先查找目标代码包的所有依赖包,以及这些依赖包的依赖包,直至找到最深层的依赖包为止。在此过程中,如果发现有循环依赖的情况,编译程序就会输出错误信息并立即退出。此过程完成之后,所有的依赖关系也就形成了一棵含有重复元素的依赖树。对于依赖树中的一个节点(代码包)来说,它的直接分支节点(前者的依赖包),是按照代码包导入路径的字典序从左到右排列的。最左边的分支节点会最先被编译。编译程序会依此设定每个代码包的编译优先级。

执行go build命令的计算机如果拥有多个逻辑CPU核心,那么编译代码包的顺序可能会存在一些不确定性。但是,它一定会满足这样的约束条件:依赖代码包 -> 当前代码包 -> 触发代码包。

标记-p n可以限制编译过程中任务执行的并发数量,n默认为当前计算机的CPU逻辑核数。如果在执行go build命令时加入标记-p 1,那么就可以保证代码包编译顺序严格按照预先设定好的优先级进行。现在我们再来编译logging包:

hc@ubt:~/golang/goc2p/src$ go build -a -v -work -p 1 logging
WORK=/tmp/go-build114039681
runtime
errors
sync/atomic
sync
io
math
syscall
time
os
unicode/utf8
strconv
reflect
fmt
log
unicode
strings
logging
我们可以认为,以上示例中所显示的代码包的顺序,就是logging包直接或间接依赖的代码包按照优先级从高到低排列后的排序。

另外,如果在命令中加入标记-n,那么编译程序只会输出所用到的命令而不会真正运行。在这种情况下,编译过程不会使用并发模式。

在本节的最后,我们对一些并不太常用的标记进行简要的说明:

-asmflags
此标记可以后跟另外一些标记,如-D、-I、-S等。这些后跟的标记用于控制Go语言编译器编译汇编语言文件时的行为。

-buildmode
此标记用于指定编译模式,使用方式如-buildmode=default(这等同于默认情况下的设置)。此标记支持的编译模式目前有6种。借此,我们可以控制编译器在编译完成后生成静态链接库(即.a文件,也就是我们之前说的归档文件)、动态链接库(即.so文件)或/和可执行文件(在Windows下是.exe文件)。

-compiler
此标记用于指定当前使用的编译器的名称。其值可以为gc或gccgo。其中,gc编译器即为Go语言自带的编辑器,而gccgo编译器则为GCC提供的Go语言编译器。而GCC则是GNU项目出品的编译器套件。GNU是一个众所周知的自由软件项目。在开源软件界不应该有人不知道它。好吧,如果你确实不知道它,赶紧去google吧。

-gccgoflags
此标记用于指定需要传递给gccgo编译器或链接器的标记的列表。

-gcflags
此标记用于指定需要传递给go tool compile命令的标记的列表。

-installsuffix
为了使当前的输出目录与默认的编译输出目录分离,可以使用这个标记。此标记的值会作为结果文件的父目录名称的后缀。其实,如果使用了-race标记,这个标记会被自动追加且其值会为race。如果我们同时使用了-race标记和-installsuffix,那么在-installsuffix标记的值的后面会再被追加_race,并以此来作为实际使用的后缀。

-ldflags
此标记用于指定需要传递给go tool link命令的标记的列表。

-linkshared
此标记用于与-buildmode=shared一同使用。后者会使作为编译目标的非main代码包都被合并到一个动态链接库文件中,而前者则会在此之上进行链接操作。

-pkgdir
使用此标记可以指定一个目录。编译器会只从该目录中加载代码包的归档文件,并会把编译可能会生成的代码包归档文件放置在该目录下。

-tags
此标记用于指定在实际编译期间需要受理的编译标签(也可被称为编译约束)的列表。这些编译标签一般会作为源码文件开始处的注释的一部分,例如,在$GOROOT/src/os/file_posix.go开始处的注释为:

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris windows
最后一行注释即包含了与编译标签有关的内容。大家可以查看代码包go/build的文档已获得更多的关于编译标签的信息。

-toolexec
此标记可以让我们去自定义在编译期间使用一些Go语言自带工具(如vet、asm等)的方式。

go安装或者版本升级

之前go还停留在1.5现在要升级到最新版。
go是通过环境变量来关联版本的,所以安装和升级其实是一个流程。
对于升级,直接删除或者重命名原来的目录,将新包解压到原来的位置就可以了。
对于安装,就是直接解压,然后配置环境变量,参看 mac下零基础学习go语言-2-开发环境的搭建

下载

golang 官方下载

安装

tar -C /usr/local -xzf go1.11.2.linux-amd64.tar.gz

结束

go编译中出现的link signal killed问题

在vps中编译hugo代码时出现:

tool/linux_amd64/link: signal: killed

经过搜索查到类似的场景:

https://github.com/beego/wetalk/issues/32

不同的是这个是beego,而我编译的是hugo,所以初步断定应该是跟go的环境有关,而不是hugo代码。所以验证了帖子中关于内存的说法,发现重启后,果然正常。

centos6下go get卡停问题

go get可以通过选项,直接下载所有依赖项,非常方便。所以在安装hugo时,我们也通过go get 下载源码:

go get -u -v github.com/spf13/hugo 

下载时突然卡住,没有任何反应和提示,日志停留在:

Fetching https://gopkg.in/fsnotify.v1?go-get=1
Parsing meta tags from https://gopkg.in/fsnotify.v1?go-get=1 (status code 200)
get "gopkg.in/fsnotify.v1": found meta tag main.metaImport{Prefix:"gopkg.in/fsnotify.v1", VCS:"git", RepoRoot:"https://gopkg.in/fsnotify.v1"} at https://gopkg.in/fsnotify.v1?go-get=1
gopkg.in/fsnotify.v1 (download) 

根据缺失的库逐一排查,后来根据缺失 gopkg.in/yaml.v1 为线索,网上说是原因git版本太低,需>= 1.7.9.5,而Centos6.7 自带的git 是1.7.1的。

升级到git最新版后,go get 成功!

go get引起的terminal prompts disabled错误

执行go get命令时,出现如下的错误提示:

fatal: could not read Username for 'https://github.com': terminal prompts disabled

两种解决方案:

  1. 先通过ssh成功登陆一次git,正确获取到key缓存。
  2. 手动添加key:https://help.github.com/articles/generating-ssh-keys/

然后就可以正常get了。

文章参考:
http://stackoverflow.com/questions/32232655/go-get-results-in-terminal-prompts-disabled-error-for-github-private-repo

Go中struct{}{}的解读

新人刚开始看到go中的struct{}{}作为interface的返回值时,一定很费解,怎么理解呢?看下面的例子
关于空接口,我们有如下的用法:

var v1 interface{} = 1      // 将int类型赋值给interface{}
var v2 interface{} = "abc"    // 将string类型赋值给interface{}
var v3 interface{} = &v2    // 将*interface{}类型赋值给interface{}
var v4 interface{} = struct{ X int }{1}
var v5 interface{} = &struct{ X int }{1}

看到4,5行的用法了嘛,其实 struct{}{}只是一个空的特例而已。也就是一个空的struct实例对象而已。

Go编程tip-4

从Panic中恢复

recover()的调用仅当它在defer函数中被直接调用时才有效。在defer中和直接调用这两个是必要条件

在Slice, Array, and Map "range"语句中更新引用元素的值

在“range”语句中生成的数据的值是真实集合元素的拷贝。它们不是原有元素的引用。这意味着更新这些值将不会修改原来的数据。同时也意味着使用这些值的地址将不会得到原有数据的指针。

如果你需要更新原有集合中的数据,使用索引操作符来获得数据。
package main

import "fmt"

func main() {
    data := []int{1, 2, 3}
    for i, _ := range data {
        data[i] *= 10
    }

    fmt.Println("data:", data) //prints data: [10 20 30]
}

如果你的集合保存的是指针,那规则会稍有不同。如果要更新原有记录指向的数据,你依然需要使用索引操作,但你可以使用for range语句中的第二个值来更新存储在目标位置的数据。
package main

import "fmt"

func main() {
    data := []*struct{ num int }{{1}, {2}, {3}}

    for _, v := range data {
        v.num *= 10
    }

    fmt.Println(data[0], data[1], data[2]) //prints &{10} &{20} &{30}
}

slice中的隐藏数据

因为slice是一个指针引用,所以如果引用原有数据的一小部分,实际指向的还是原来的整个内存对象。

对slice截断对象的append操作

因为slice很像指针,整个操作很容易产生“越界覆盖”

类型声明和方法

当你通过把一个现有(非interface)的类型定义为一个新的类型时,新的类型不会继承现有类型的方法。
Fails:
package main

import "sync"

type myMutex sync.Mutex

func main() {
    var mtx myMutex
    mtx.Lock()   //error
    mtx.Unlock() //error
}

如果你确实需要原有类型的方法,你可以定义一个新的struct类型,用匿名方式把原有类型嵌入其中。
package main

import "sync"

type myLocker struct {
    sync.Mutex
}

func main() {
    var lock myLocker
    lock.Lock()   //ok
    lock.Unlock() //ok
}

defer的函数什么时候执行?

被defer的调用会在包含的函数的末尾执行,而不是包含代码块的末尾。对于Go新手而言,一个很常犯的错误就是无法区分被defer的代码执行规则和变量作用规则。如果你有一个长时运行的函数,而函数内有一个for循环试图在每次迭代时都defer资源清理调用,那就会出现问题。
解决办法就是将相关的逻辑封装成内部匿名函数,这样在函数结束时,就会调用defer指定的函数

更新map的值

如果你有一个struct值的map,你无法更新单个的struct值。这个操作无效是因为map元素是无法取址的。
但是slice元素是可以取址的。

栈和堆变量

你并不总是知道变量是分配到栈还是堆上。在C++中,使用new创建的变量总是在堆上。在Go中,即使是使用new()或者make()函数来分配,变量的位置还是由编译器决定。编译器根据变量的大小和“泄露分析”的结果来决定其位置。这也意味着在局部变量上返回引用是没问题的,而这在C或者C++这样的语言中是不行的。

如果你想知道变量分配的位置,在“go build”或“go run”上传入“-m“ gc标志(即,go run -gcflags -m app.go)。

多goroutine的读写顺序可能被重排

多个goroutine运行时,顺序的语句执行次序可能会发生调换。

Go lang Slice截取时指定最大容量以及append函数的使用说明

slice是一个三元组对象
* 一个内容指针
* 有效内容长度
* 最大长度,也就是容量。

示例:

package main

import (
    "bytes"
    "fmt"
)

func main() {
    path := []byte("AAAA/BBBBBBBBB")
    sepIndex := bytes.IndexByte(path, '/')
    fmt.Println(sepIndex)
    dir1 := path[:sepIndex:sepIndex] //full slice expression
    fmt.Println(&path[0], &dir1[0])
    dir2 := path[sepIndex+1:]
    fmt.Println("dir1 =>", string(dir1)) //prints: dir1 => AAAA
    fmt.Println("dir2 =>", string(dir2)) //prints: dir2 => BBBBBBBBB

    dir1 = append(dir1, "suffix"...)
    fmt.Println(&dir1[0])
    path = bytes.Join([][]byte{dir1, dir2}, []byte{'/'})

    fmt.Println("dir1 =>", string(dir1)) //prints: dir1 => AAAAsuffix
    fmt.Println("dir2 =>", string(dir2)) //prints: dir2 => BBBBBBBBB (ok now)

    fmt.Println("new path =>", string(path))
}
  • 第12行的代码就是一个指定容量的截取。
  • 如果增加后不回超出slice的最大空间,slice是不会重新分配对象的。但是如果空间不够,从13行和19行的地址打印输出我们可以看到,因为append超出了指定的空间容量,系统动态重新分配了空间,地址发生了变化。

运行结果:

4
0xc82000a3b0 0xc82000a3b0
dir1 => AAAA
dir2 => BBBBBBBBB
0xc82000a450
dir1 => AAAAsuffix
dir2 => BBBBBBBBB
new path => AAAAsuffix/BBBBBBBBB
成功: 进程退出代码 0.

Go编程tips-3

String在“range”语句中的迭代值

package main

import "fmt"

func main() {  
    data := "A\xfe\x02\xff\x04"
    for _,v := range data {
        fmt.Printf("%#x ",v)
    }
    //prints: 0x41 0xfffd 0x2 0xfffd 0x4 (not ok)

    fmt.Println()
    for _,v := range []byte(data) {
        fmt.Printf("%#x ",v)
    }
    //prints: 0x41 0xfe 0x2 0xff 0x4 (good)
}

"switch"

分支默认不是继续下一个分支,默认是'break';
支持条件列表,通过逗号隔开
如果要强制进入下一分支,可以使用"fallthrough"

自增自减

不支持前置,只支持后置,也就是只支持 "i++"的用法

位运算

取反:^ XOR
与: & AND
与非:&^ AND NOT

操作符优先级

&优先于+
<<优先于+
|优先于^

未导出的结构不会被编码

[{ loading ... }]将会得到零值。下面的示例中,成员two没有被编码,所以再反序列化时,就变成空值了。

package main

import (  
    "fmt"
    "encoding/json"
)

type MyData struct {  
    One int
    two string
}

func main() {  
    in := MyData{1,"two"}
    fmt.Printf("%#v\n",in) //prints main.MyData{One:1, two:"two"}

    encoded,_ := json.Marshal(in)
    fmt.Println(string(encoded)) //prints {"One":1}

    var out MyData
    json.Unmarshal(encoded,&out)

    fmt.Printf("%#v\n",out) //prints main.MyData{One:1, two:""}
}

有coroutines的应用的退出

  • 应用是不会等待所有goroutines都完成才退出的,主应用和goroutine之间默认是不交互和感知的。

向关闭的chnnel发送数据会panic

操作没有初始化的"nil"的channel

在一个nil的channel上发送和接收操作会被永久阻塞。这个行为可以在select声明中用于动态开启和关闭case代码块的方法。

package main

import "fmt"
import "time"

func main() {
    inch := make(chan int)
    outch := make(chan int)

    go func() {
        var in <-chan int = inch
        var out chan<- int
        var val int
        for {
            select {
            case out <- val:
                out = nil
                in = inch
            case val = <-in:
                out = outch
                in = nil
            }
        }
    }()

    go func() {
        for r := range outch {
            fmt.Println("result:", r)
        }
    }()

    time.Sleep(0)
    inch <- 1
    inch <- 2
    time.Sleep(3 * time.Second)
}

HTTP响应的关闭

当你使用标准http库发起请求时,你得到一个http的响应变量。如果你不读取响应主体,你依旧需要关闭它。注意对于空的响应你也一定要这么做。对于新的Go开发者而言,这个很容易就会忘掉。
大多数情况下,当你的http响应失败时,resp变量将为nil,而err变量将是non-nil。然而,当你得到一个重定向的错误时,两个变量都将是non-nil。这意味着你最后依然会内存泄露。
通过在http响应错误处理中添加一个关闭non-nil响应主体的的调用来修复这个问题。另一个方法是使用一个defer调用来关闭所有失败和成功的请求的响应主体。

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
)

func main() {
    resp, err := http.Get("https://api.ipify.org?format=json")
    if resp != nil {
        defer resp.Body.Close()
    }

    if err != nil {
        fmt.Println(err)
        return
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(string(body))
    _, err = io.Copy(ioutil.Discard, resp.Body)
}

resp.Body.Close()的原始实现也会读取并丢弃剩余的响应主体数据。这确保了http的链接在keepalive http连接行为开启的情况下,可以被另一个请求复用。最新的http客户端的行为是不同的。现在读取并丢弃剩余的响应数据是你的职责。如果你不这么做,http的连接可能会关闭,而不是被重用。这个小技巧应该会写在Go 1.5的文档中。

关闭HTTP连接

一些HTTP服务器保持会保持一段时间的网络连接(根据HTTP 1.1的说明和服务器端的“keep-alive”配置)。默认情况下,标准http库只在目标HTTP服务器要求关闭时才会关闭网络连接。这意味着你的应用在某些条件下消耗完sockets/file的描述符。
你可以通过设置请求变量中的Close域的值为true,来让http库在请求完成时关闭连接。
另一个选项是添加一个Connection的请求头,并设置为close。目标HTTP服务器应该也会响应一个Connection: close的头。当http库看到这个响应头时,它也将会关闭连接。
当然是否需要关闭,是由你的应用场景决定的。

package main

import (  
    "fmt"
    "net/http"
    "io/ioutil"
)

func main() {  
    req, err := http.NewRequest("GET","http://golang.org",nil)
    if err != nil {
        fmt.Println(err)
        return
    }

    req.Close = true
    //or do this:
    //req.Header.Add("Connection", "close")

    resp, err := http.DefaultClient.Do(req)
    if resp != nil {
        defer resp.Body.Close()
    }

    if err != nil {
        fmt.Println(err)
        return
    }

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(len(string(body)))
}

比较Structs, Arrays, Slices, and Maps

只有每个成员本身能用"=="比较时,对象才可以用"=="比较。

DeepEqual()函数

对于不能直接比较的,可以使用DeepEqual函数
package main

import (
    "fmt"
    "reflect"
)

type data struct {
    num    int               //ok
    checks [10]func() bool   //not comparable
    doit   func() bool       //not comparable
    m      map[string]string //not comparable
    bytes  []byte            //not comparable
}

func main() {
    v1 := data{}
    v2 := data{}
    fmt.Println("v1 == v2:", reflect.DeepEqual(v1, v2)) //prints: v1 == v2: true

    m1 := map[string]string{"one": "a", "two": "b"}
    m2 := map[string]string{"two": "b", "one": "a"}
    fmt.Println("m1 == m2:", reflect.DeepEqual(m1, m2)) //prints: m1 == m2: true

    s1 := []int{1, 2, 3}
    s2 := []int{1, 2, 3}
    fmt.Println("s1 == s2:", reflect.DeepEqual(s1, s2)) //prints: s1 == s2: true
}

DeepEqual()不会认为空的slice与“nil”的slice相等。这个行为与你使用bytes.Equal()函数的行为不同。bytes.Equal()认为“nil”和空的slice是相等的。

如果你的byte slice(或者字符串)中包含文字数据,而当你要不区分大小写形式的值时(在使用==,bytes.Equal(),或者bytes.Compare()),你可能会尝试使用“bytes”和“string”包中的ToUpper()或者ToLower()函数。对于英语文本,这么做是没问题的,但对于许多其他的语言来说就不行了。这时应该使用strings.EqualFold()和bytes.EqualFold()。

如果你的byte slice中包含需要验证用户数据的隐私信息(比如,加密哈希、tokens等),不要使用reflect.DeepEqual()、bytes.Equal(),或者bytes.Compare(),因为这些函数将会让你的应用易于被定时攻击。为了避免泄露时间信息,使用'crypto/subtle'包中的函数(即,subtle.ConstantTimeCompare())。

[转]Golang 中的格式化输入输出

待整理验证。
原文:Golang 中的格式化输入输出

正文:

【简介】

  fmt 包实现了格式化 I/O 函数,类似于 C 的 printf 和 scanf。格式“占位符”衍生自 C,但比 C 更简单。

【打印】

占位符:

[一般]

  %v 相应值的默认格式。在打印结构体时,“加号”标记(%+v)会添加字段名

  %#v 相应值的 Go 语法表示

  %T 相应值的类型的 Go 语法表示

  %% 字面上的百分号,并非值的占位符

[布尔]

  %t 单词 true 或 false。

[整数]

  %b 二进制表示

  %c 相应 Unicode 码点所表示的字符

  %d 十进制表示

  %o 八进制表示

  %q 单引号围绕的字符字面值,由 Go 语法安全地转义

  %x 十六进制表示,字母形式为小写 a-f

  %X 十六进制表示,字母形式为大写 A-F

  %U Unicode 格式:U+1234,等同于 "U+%04X"

[浮点数及其复合构成]

  %b 无小数部分的,指数为二的幂的科学计数法,与 strconv.FormatFloat 的 'b' 转换格式一致。例如 -123456p-78

  %e 科学计数法,例如 -1234.456e+78

  %E 科学计数法,例如 -1234.456E+78

  %f 有小数点而无指数,例如 123.456

  %g 根据情况选择 %e 或 %f 以产生更紧凑的(无末尾的 0)输出

  %G 根据情况选择 %E 或 %f 以产生更紧凑的(无末尾的 0)输出

[字符串与字节切片]

  %s 字符串或切片的无解译字节

  %q 双引号围绕的字符串,由 Go 语法安全地转义

  %x 十六进制,小写字母,每字节两个字符

  %X 十六进制,大写字母,每字节两个字符

[指针]

  %p 十六进制表示,前缀 0x

[注意]

  这里没有 'u' 标记。若整数为无符号类型,他们就会被打印成无符号的。类似地, 这里也不需要指定操作数的大小(int8,int64)。

  宽度与精度的控制格式以 Unicode 码点为单位。(这点与 C 的 printf 不同, 它以字节数为单位。)二者或其中之一均可用字符 '*' 表示, 此时它们的值会从下一个操作数中获取,该操作数的类型必须为 int。

// 宽度与精度的控制以 Unicode 码点为单位
fmt.Printf("\"%8s\"\n", "123456") // 最大长度为 8
// " 123456"
fmt.Printf("\"%8s\"\n", "你好") // 最大长度为 8
// " 你好"

// 宽度与精度均可用字符 '' 表示
fmt.Printf("%0
.*f \n", 8, 3, 13.25) // 总长度 8,小数位数 3
fmt.Printf("%08.3f \n", 13.25) // 总长度 8,小数位数 3
// 0013.250

  对数值而言,宽度为该数值占用区域的最小宽度;精度为小数点之后的位数。 但对于 %g/%G 而言,精度为所有数字的总数。例如,对于 123.45,格式 %6.2f 会打印 123.45,而 %.4g 会打印 123.5。%e 和 %f 的默认精度为 6;但对于 %g 而言,它的默认精度为确定该值所必须的最小位数。

  对大多数值而言,宽度为输出的最小字符数,如果必要的话会为已格式化的形式填充空格。对字符串而言,精度为输出的最大字符数,如果必要的话会直接截断。

// 宽度与精度标记字符串
fmt.Printf("%8q", "ABC") // 最小长度为 8(包括 %q 的引号字符)
// "ABC"
fmt.Printf("%.8q", "1234567890") // 最大长度为 8(不包括 %q 的引号字符)
// "12345678"

[其它标记]

  + 总打印数值的正负号;对于 %q(%+q)保证只输出 ASCII 编码的字符。

  - 在右侧而非左侧填充空格(左对齐该区域)

  # 备用格式:为八进制添加前导 0(%#o),为十六进制添加前导 0x(%#x)或

  0X(%#X),为 %p(%#p)去掉前导 0x;如果可能的话,%q(%#q)会打印原始(即反引号围绕的)字符串;如果是可打印字符,%U(%#U)会写出该字符的 Unicode 编码形式(如字符 x 会被打印成 U+0078 'x')。

  ' ' (空格)为数值中省略的正负号留出空白(% d);以十六进制(% x, % X)打印字符串或切片时,在字节之间用空格隔开:

fmt.Printf("% x\n", "Hello")
// 48 65 6c 6c 6f

  0 填充前导的 0 而非空格;对于数字,这会将填充移到正负号之后

[注意]

  标记有时会被占位符忽略,所以不要指望它们。例如十进制没有备用格式,因此 %#d 与 %d 的行为相同。

  对于每一个 Printf 类的函数,都有一个 Print 函数,该函数不接受任何格式化, 它等价于对每一个操作数都应用 %v。另一个变参函数 Println 会在操作数之间插入空白, 并在末尾追加一个换行符。

  不考虑占位符的话,如果操作数是接口值,就会使用其内部的具体值,而非接口本身。 因此:

var i interface{} = 23
fmt.Printf("%v\n", i)
// 会打印 23

  若一个操作数实现了 Formatter 接口,该接口就能更好地用于控制格式化。

  若其格式(它对于 Println 等函数是隐式的 %v)对于字符串是有效的(%s %q %v %x %X),以下两条规则也适用:

  1、若一个操作数实现了 error 接口,Error 方法就能将该对象转换为字符串,随后会根据占位符的需要进行格式化。

  2、若一个操作数实现了 String() string 方法,该方法能将该对象转换为字符串,随后会根据占位符的需要进行格式化。

  为避免以下这类递归的情况:

  type X string
  func (x X) String() string { return Sprintf("<%s>", x) }

  需要在递归前转换该值:
  func (x X) String() string { return Sprintf("<%s>", string(x)) }

[格式化错误]

  如果给占位符提供了无效的实参(例如将一个字符串提供给 %d),所生成的字符串会包含该问题的描述,如下例所示:

  类型错误或占位符未知:%!verb(type=value)

Printf("%d", hi)
// %!d(string=hi)

  实参太多:%!(EXTRA type=value)

Printf("hi", "guys")
// hi%!(EXTRA string=guys)

  实参太少:%!verb(MISSING)

Printf("hi%d")
// hi %!d(MISSING)

  宽度或精度不是 int 类型:%!(BADWIDTH)或 %!(BADPREC)

Printf("%*s", 4.5, "hi")
// %!(BADWIDTH)hi

Printf("%.*s", 4.5, "hi")
// %!(BADPREC)hi

  所有错误都始于“%!”,有时紧跟着单个字符(占位符),并以小括号括住的描述结尾。

【扫描】

  一组类似的函数通过扫描已格式化的文本来产生值。Scan、Scanf 和 Scanln 从 os.Stdin 中读取;Fscan、Fscanf 和 Fscanln 从指定的 io.Reader 中读取; Sscan、Sscanf 和 Sscanln 从实参字符串中读取。Scanln、Fscanln 和 Sscanln 在换行符处停止扫描,且需要条目紧随换行符之后;Scanf、Fscanf 和 Sscanf 需要输入换行符来匹配格式中的换行符;其它函数则将换行符视为空格。

  Scanf、Fscanf 和 Sscanf 根据格式字符串解析实参,类似于 Printf。例如,%x 会将一个整数扫描为十六进制数,而 %v 则会扫描该值的默认表现格式。

  格式化行为类似于 Printf,但也有如下例外:

  %p 没有实现
  %T 没有实现
  %e %E %f %F %g %G 都完全等价,且可扫描任何浮点数或复合数值
  %s 和 %v 在扫描字符串时会将其中的空格作为分隔符
  标记 # 和 + 没有实现

  在使用 %v 占位符扫描整数时,可接受友好的进制前缀 0(八进制)和 0x(十六进制)。

  宽度被解释为输入的文本(%5s 意为最多从输入中读取 5 个符文来扫描成字符串),而扫描函数则没有精度的语法(没有 %5.2f,只有 %5f)。

  当以某种格式进行扫描时,无论在格式中还是在输入中,所有非空的连续空白字符 (除换行符外)都等价于单个空格。由于这种限制,格式字符串文本必须匹配输入的文本,如果不匹配,扫描过程就会停止,并返回已扫描的实参数。

  在所有的扫描参数中,若一个操作数实现了 Scan 方法(即它实现了 Scanner 接口),该操作数将使用该方法扫描其文本。此外,若已扫描的实参数少于所提供的实参数,就会返回一个错误。

  所有需要被扫描的实参都必须是基本类型或实现了 Scanner 接口的类型。

  注意:Fscan 等函数会从输入中多读取一个字符(符文),因此,如果循环调用扫描函数,可能会跳过输入中的某些数据。一般只有在输入的数据中没有空白符时该问题才会出现。若提供给 Fscan 的读取器实现了 ReadRune,就会用该方法读取字符。若此读取器还实现了 UnreadRune 方法,就会用该方法保存字符,而连续的调用将不会丢失数据。若要为没有 ReadRune 和 UnreadRune 方法的读取器加上这些功能,需使用 bufio.NewReader。

Go编程tips-2

未使用的imports

可以注释掉或者加下划线'_',否则,编译报错

package main

import (  
    - "fmt"
    "log"
    "time"
)

var _ = log.Println

func main() {  
    _ = time.Now
}

使用简式声明重复声明变量

你不能在一个单独的声明中重复声明一个变量,但在多变量声明中这是允许的,其中至少要有一个新的声明变量。

重复变量需要在相同的代码块内,否则你将得到一个隐藏变量。

Fails:

package main

func main() {  
    one := 0
    one := 1 //error
}

Compile Error:

/tmp/sandbox706333626/main.go:5: no new variables on left side of :=

Works:

package main

func main() {  
    one := 0
    one, two := 1,2

    one,two = two,one
}

用 nil 初始化slice和map

在一个“nil”的slice中添加元素是没问题的,但对一个map做同样的事将会生成一个运行时的panic。

Works:

package main

func main() {  
    var s []int
    s = append(s,1)
}

Fails:

package main

func main() {  
    var m map[string]int
    m["one"] = 1 //error

}

map 的容量

你可以在map创建时指定它的容量,但你无法在map上使用cap()函数。
package main

func main() {  
    m := make(map[string]int,99)
    cap(m) //error
}

Compile Error:

/tmp/sandbox326543983/main.go:5: invalid argument m (type map[string]int) for cap

字符串不能初始化为 "nil"

这对于经常使用“nil”分配字符串变量的开发者而言是个需要注意的地方。

二维数组的创建

你可以使用纯一维数组、“独立”切片的切片,“共享数据”切片的切片来构建动态的多维数组。

如果你使用纯一维的数组,你需要处理索引、边界检查、当数组需要变大时的内存重新分配。

使用“独立”slice来创建一个动态的多维数组需要两步。首先,你需要创建一个外部的slice。然后,你需要分配每个内部的slice。内部的slice相互之间独立。你可以增加减少它们,而不会影响其他内部的slice。

package main

func main() {  
    x := 2
    y := 4

    table := make([][]int,x)
    for i:= range table {
        table[i] = make([]int,y)
    }
}

使用“共享数据”slice的slice来创建一个动态的多维数组需要三步。首先,你需要创建一个用于存放原始数据的数据“容器”。然后,你再创建外部的slice。最后,通过重新切片原始数据slice来初始化各个内部的slice。

package main

import "fmt"

func main() {  
    h, w := 2, 4

    raw := make([]int,h*w)
    for i := range raw {
        raw[i] = i
    }
    fmt.Println(raw,&raw[4])
    //prints: [0 1 2 3 4 5 6 7] <ptr_addr_x>

    table := make([][]int,h)
    for i:= range table {
        table[i] = raw[i*w:i*w + w]
    }

    fmt.Println(table,&table[1][0])
    //prints: [[0 1 2 3] [4 5 6 7]] <ptr_addr_x>
}

判断map中节点是否存在

检测给定map中的记录是否存在的最可信的方法是,通过map的访问操作,检查第二个返回的值。

package main

import "fmt"

func main() {  
    x := map[string]string{"one":"a","two":"","three":"c"}

    if _,ok := x["two"]; !ok {
        fmt.Println("no entry")
    }
}

string的修改

如果是ASCII码,要转换成[]byte,如果是unicode,要转化成rune byte
[]byte的方法

package main

import "fmt"

func main() {  
    x := "text"
    xbytes := []byte(x)
    xbytes[0] = 'T'

    fmt.Println(string(xbytes)) //prints Text
}

string 与 rune byte 的关系

在Go当中 string底层是用byte数组存的,并且是不可以改变的。

例如 s:="Go编程" fmt.Println(len(s)) 输出结果应该是8因为中文字符是用3个字节存的。

len(string(rune('编')))的结果是3

如果想要获得我们想要的情况的话,需要先转换为rune切片再使用内置的len函数

fmt.Println(len([]rune(s)))

结果就是4了。

所以用string存储unicode的话,如果有中文,按下标是访问不到的,因为你只能得到一个byte。 要想访问中文的话,还是要用rune切片,这样就能按下表访问。

判断字符串是否为UTF8

为了知道字符串是否是UTF8,你可以使用“unicode/utf8”包中的ValidString()函数。

求字符串长度

ASCII:len();
unicode:RuneCountInString()。实际上,RuneCountInString()求的是rune的个数,不是绝对的字符个数,因为一个字符可能占多个rune

在多行的Slice、Array和Map语句中遗漏逗号

Fails:

package main

func main() {  
    x := []int{
    1,
    2 //error
    }
    _ = x
}

Works:

package main

func main() {  
    x := []int{
    1,
    2,
    }
    x = x

    y := []int{3,4,} //no error
    y = y
}

当你把声明折叠到单行时,如果你没加末尾的逗号,你将不会得到编译错误

log.Fatal和log.Panic不仅仅是Log

Logging库一般提供不同的log等级。与这些logging库不同,Go中log包在你调用它的Fatal()和Panic()函数时,可以做的不仅仅是log。当你的应用调用这些函数时,Go也将会终止应用 🙂

Go面向对象

本来想写下相关的内容,找材料时,发现了这篇不错的内容,强烈推荐,关于面向对象的基本用法都讲到了。

原文:Go面向对象
原英文:Go Object Oriented Design

正文:

Go是一个完全面向对象的语言。例如,它允许基于我们定义的类型的方法,而没有像其他语言一样的装箱/拆箱操作。

Go没有使用classes,但提供很多相似的功能:

·通过嵌入实现的自动消息委托

·通过接口实现多台

·通过exports实现的命名空间

Go语言中没有继承。忘记is-a的关系,而是就组合而言的面向对象设计。

“使用经典的继承始终是可选的;每个问题都可以通过其他方法得到解决” - Sandi Metz

通过例子说明组合

最近阅读了一篇Ruby的面向对象编程实践, 我决定使用Go语言翻译这个例子。

Chapter 6说明了一个简单的问题。维修工需要知道自行车出行需要带上的备件,决定于哪一辆自行车已经被租出去。问题可以通过经典的继承来解决,山地车和公路自行车是自行车基类的一个特殊化例子。Chapter 8使用组合改写了同一个例子。我很高兴这个例子翻译成Go。让我们看看。

Packages(包)

  1. package main
  2. import "fmt"

包提供了命名空间概念. main() 函数是这个包的入口函数. fmt包提供格式化功能

Types(类型)

  1. type Part struct {
  2.     Name        string
  3.     Description string
  4.     NeedsSpare  bool
  5. }

我们定义了一个新的类型名为Part, 非常像c的结构体

  1. type Parts []Part

Parts类型是包含Part类型的数组切片, Slice可以理解为动态增长的数组, 在Go中是很常见的.

我们可以在任何类型上声明方法,  所以我们不需要要再去封装 []Part, 这意味着 Parts 会拥有slice的所有行为, 再加上我们自己定义的行为方法.

方法

  1. func (parts Parts) Spares() (spares Parts) {
  2.     for _, part := range parts {
  3.         if part.NeedsSpare {
  4.             spares = append(spares, part)
  5.         }
  6.     }
  7.     return spares
  8. }

Go中定义方法就像一个函数,除了它有一个显式的接收者,紧接着func之后定义。这个函数利用命名返回变量,并为我们初始化备件。

方法的主体十分简单。我们重复parts,忽略索引的位置(_),过滤parts后返回。append builtin 需要分配和返回一个大的切片,因为我们并没有预先分配好它的容量。

这段代码没有ruby代码来得优雅。在Go语言中有过滤函数,但它并非是builtin.

内嵌

  1. type Bicycle struct {
  2.     Size string
  3.     Parts
  4. }

自行车由Size和Parts组成。没有给Parts指定一个名称,我们是要保证实现 内嵌。这样可以提供自动的委托,不需特殊的声明,例如bike.Spares()和bike.Parts.Spares()是等同的。

如果我们向Bicycle增加一个Spares()方法,它会得到优先权,但是我们仍然引用嵌入的Parts.Spares()。这跟继承十分相似,但是内嵌并不提供多态。Parts的方法的接收者通常是Parts类型,甚至是通过Bicycle委托的。

与继承一起使用的模式,就像模板方法模式,并不适合于内嵌。就组合和委托而言去考虑会更好,就如我们这个例子一样。

Composite Literals(复合语义)

  1. var (
  2.     RoadBikeParts = Parts{
  3.         {"chain""10-speed"true},
  4.         {"tire_size""23"true},
  5.         {"tape_color""red"true},
  6.     }
  7.     MountainBikeParts = Parts{
  8.         {"chain""10-speed"true},
  9.         {"tire_size""2.1"true},
  10.         {"front_shock""Manitou"false},
  11.         {"rear_shock""Fox"true},
  12.     }
  13.     RecumbentBikeParts = Parts{
  14.         {"chain""9-speed"true},
  15.         {"tire_size""28"true},
  16.         {"flag""tall and orange"true},
  17.     }
  18. )

Go提供优美的语法,来初始化对象,叫做 composite literals。使用像数组初始化一样的语法,来初始化一个结构,使得我们不再需要ruby例子中的Parts工厂。

  1. func main() {
  2.     roadBike := Bicycle{Size: "L", Parts: RoadBikeParts}
  3.     mountainBike := Bicycle{Size: "L", Parts: MountainBikeParts}
  4.     recumbentBike := Bicycle{Size: "L", Parts: RecumbentBikeParts}

Composite literals(复合语义)同样可以用于字段:值的语法,所有的字段都是可选的。

简短的定义操作符(:=)通过Bicycle类型,使用类型推论来初始化roadBike,和其他。

输出

  1. fmt.Println(roadBike.Spares())
  2. fmt.Println(mountainBike.Spares())
  3. fmt.Println(recumbentBike.Spares())

我们将以默认格式打印 Spares 的调用结果:

  1. [{chain 10-speed true} {tire_size 23 true} {tape_color red true}]
  2. [{chain 10-speed true} {tire_size 2.1 true} {rear_shock Fox true}]
  3. [{chain 9-speed true} {tire_size 28 true} {flag tall and orange true}]

组合 Parts

  1.     comboParts :Parts{}
  2.     comboParts = append(comboParts, mountainBike.Parts...)
  3.     comboParts = append(comboParts, roadBike.Parts...)
  4.     comboParts = append(comboParts, recumbentBike.Parts...)
  5.     fmt.Println(len(comboParts), comboParts[9:])
  6.     fmt.Println(comboParts.Spares())
  7. }

Parts 的行为类似于 slice。按照长度获取切片,或者将数个切片结合。Ruby 中的类似解决方案就数组的子类,但是当两个 Parts 连接在一起时,Ruby 将会“错置” spares 方法。

“……在一个完美的面向对象的语言,这种解决方案是完全正确的。不幸的是,Ruby语言并没有完美的实现……”
—— Sandi Metz

在 Ruby 中有一个那看的解决方法,使用 Enumerable、forwardable,以及 def_delegators。 Go有没有这样的缺陷。 []Part 正是我们所需要的,且更为简洁(更新:Ruby 的 SimpleDelegator 看上去好了一点)。

接口 Interfaces

Go的多态性由接口提供。不像JAVA和C#,它们是隐含实现的,所以接口可以为不属于我们的代码定义。

和动态类型比较,接口是在它们声明过程中静态检查和说明的,而不是通过写一系列响应(respond_to)测试完成的。

“不可能不知不觉的或者偶然的创建一个抽象;在静态类型语言中定义的接口总是有倾向性的。” - Sandi Metz

给个简单的例子,假设我们不需要打印Part的NeedsSpare标记。我们可以写这样的字符串方法:

  1. func (part Part) String() string {
  2.     return fmt.Sprintf("%s: %s", part.Name, part.Description)
  3. }

然后对上述Print的调用将会输出这样的替代结果:

  1. [chain: 10-speed tire_size: 23 tape_color: red]
  2. [chain: 10-speed tire_size: 2.1 rear_shock: Fox]
  3. [chain: 9-speed tire_size: 28 flag: tall and orange]

这个机理是因为我们实现了fmt包会用到的Stringer接口。它是这么定义的:

  1. type Stringer interface {
  2.     String() string
  3. }

接口类型在同一个地方可以用作其它类型。变量与参数可以携带一个Stringer,可以是任何实现String() string方法签名的接口。

Exports 导出

Go 使用包来管理命名空间, 要使某个符号对其他包(package )可见(即可以访问),需要将该符号定义为以大写字母开头,  当然,如果以小写字母开关,那就是私有的.包外不可见.

  1. type Part struct {
  2.     name        string
  3.     description string
  4.     needsSpare  bool
  5. }

为了对Part类型应用统一的访问原则(uniform access principle), 我们可以改变Part类型的定义并提供setter/getter 方法,就像这样:

  1. func (part Part) Name() string {
  2.     return part.name
  3. }
  4. func (part *Part) SetName(name string) {
  5.     part.name = name
  6. }

这样可以很容易的确定哪些是public API, 哪些是私有的属性和方法, 只要通过字母的大小写.(例如(part.Name()vs.part.name)

注意 我们不必要对 getters 加前Get, (例如.GetName),Getter不是必需,特别是对于字符串,当我们有需要时,我们可以使用满足Stringer 类型接口的自定义的类型去改变Name 字段。

找到一些私有性

私有命名(小写字母)可以从同一个包的任何地方访问到,即使是包含了跨越多个文件的多个结构。如果你觉得这令人不安,包也可以像你希望的那么小。

可能的情况下用(更稳固的)公共API是一个好的实践,即使是来自经典语言的同样的类中。这需要一些约定,当然这些约定可以应用在GO中。

最大的好处

组合,内嵌和接口提供了Go语言中面向对象设计的强大工具。继承概念的思想真的不起什么作用。相信我,我尝试了

习惯Go需要思维的改变,当触及到Go对象模型的力量时,我非常高兴的吃惊于Go代码的简单和简洁。

Go编程tips-1

结构体的初始化

示例代码中,前者是创建普通对象,后者是创建指针

package main

import (
    "fmt"
)

type Rect struct {
    x, y          float64
    width, height float64
}

func main() {
    // common object
    rect2 := Rect{}
    rect3 := Rect{0, 0, 100, 200}
    rect4 := Rect{width: 100, height: 200}
    fmt.Println(rect2, rect3, rect4)

    /*
        // pointer objects
        rect1 := new(Rect)
        rect2 := &Rect{}
        rect3 := &Rect{0, 0, 100, 200}
        rect4 := &Rect{width: 100, height: 200}
        fmt.Println(rect1, rect2, rect3, rect4)
    */
}

变参

go支持变参,变参中所有参数的类别必须是同一种,且必须是最后一个形参。使用方法如下:“…type”表示具有不定个type类型的参数,不定参数实质上是一个slice类型,故可以使用range对其参数进行取值。如下例子。

func myfunc(args ...int) {
    for _, arg := range args {
        fmt.Println(arg)
    }
}

defer 与return执行顺序

当函数执行到最后时,先执行defer语句,然后才执行return语句.所以可以利用这个来进行资源安全关闭,解加锁,记录执行情况等。defer是采用先进后出的模式的,这种情形与栈的情况一致。注意:定义的defer延迟操作,如有提供参数会发生值的拷贝,尽管这些函数在退出时才执行,但所使用的参数是在定义时就进行拷贝,拷贝的原则和变量的赋值原则一个,slice,map,channel是指针拷贝.如下例子:

package main

import (
    "fmt"
)

func main() {
    x := 1
    defer func(a int) { //直接将x=1赋值给a,虽然他在后面才执行.
        fmt.Println("a=", a)
    }(x)
    defer func() {
        fmt.Println("x=", x) //经过x++后,x值变为2
    }()
    x++
}

运行结果:
x= 2
a= 1

临时变量的作用域

go语言中对于堆和栈的内存分配没有严格区分,在go中返回一个局部变量的地址是绝对没有问题的,变量关联的存储在函数返回后依然存在.(注:尤其对由C/C++转过来的程序员,开始肯定不是很适应,但是go这种内存分配方式解放了程序员,使得程序员能够专注做事情,而不用花费太多的时间在堆和栈的内存分配上).更直接的说,在go语言中,如果一个局部变量在函数返回后仍然被使用,那么这个变量会在堆heap,而不是栈stack中进行内存分配.详情参考How do I know whether a variable is allocated on the heap or the stack?

Mac下零基础学习go语言-2-开发环境的搭建

环境介绍

  • 系统:OS X EI Capitan 10.11.1
  • go version:1.5.1

安装

下载最新的安装包 https://golang.org/dl/

mac下安装

直接点击,按照引导就可以完成安装。

linux下安装

linux下实际就是手动设置文件路径了,大概的动作如下:

#!/bin/bash
tar -C /usr/local -xzf go*.tar.gz
echo 'export PATH=$PATH:/usr/local/go/bin' >> /etc/profile

最后重启,或者:

source /etc/profile

验证版本

终端验证,输入

go version
go version go1.11.2 linux/amd64

配置环境变量

设置环境变量,go默认安装在/usr/local/go 下面,参见:Mac OS X 配置环境变量

所以我们修改 .bash_profile:

export GOPATH=/Users/alex/dev/go
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOBIN

注意:

  • 默认情况下GOROOT已经在安装时指定为安装目录了,是不需要设置的
  • GOPATH 设置成你的本地开发路径,而不是安装路径

设置完成后可以用 go env 命令检测

测试

在 GOPATH下 的src目录下创建项目demo。注意这个将是默认的bin档名。 然后在demo目录下创建main.go

package main

import (
  "fmt"
)

func main() {
  fmt.Println("hello world");
}

然后运行 go build demo,如果在demo项目目录下,直接 go build,完成后,在项目目录下生成 demo

直接运行 ./demo,就会输出 :hello world

DEBUG

初学者建议在LiteIDE下面debug,因为IDE直接UI支持,熟悉了以后也可以在命令行下面直接debug。第一次试用时要解决GDB的证书签名问题,可以参看网上同学给出的操作流程。

下面终于可以开始coding了!

Mac下零基础学习go语言-1-开篇

缘由

一直以来都在慢慢悠悠地关注各种go的相关主题,因为本职还是做c/c++相关的工作,所以一致以来都没有系统的对go进行系统的学习,仅局限于对相关的知识点进行进行简单的了解和学习。最近由于工作变动原因,稍息赋闲,打算对go做一个系统的学习,所以开此主题,希望能详细记录下学习的细节,能对后来的新人有个引路的作用

为什么学习go

这个应该是最先要回答的问题,也是群里经常争论的问题,为什么要选go,而不是php,java。
首先说下go的几个特点

  • 静态语言
  • 有高级语法,又不是非常复杂晦涩
  • 高并发,非常小的协程开销,非常适合服务器场景
  • 其他的网友自行google,这里不详细展开

我自己的观点是,如果你是c,c++的背景,要提高后台开发的效率,go是不二的选择,go的语法复杂度,介于c和c++之间,功能上长于后端开发,也有完备的库,开发效率上会有很大提高。
如果你原来就是php,java,node的背景,你所做的工作都是基于接口的数据开发,也建议尝试go。go天生适合做后台接口,开发效率高,性能也不错。
但如果你想要做一个web系统,其实go就目前的情况看,并不比php,java,node更合适,不必盲目跟风。
最后,建议初学者读一下这篇
Donovan/Kernighan大神们关于go语言的问答
其中关于go的特点,潜力,为什要选go等都有涉及。
其他的问题参见go的FAQ

环境

mac air
go version go1.5.1 darwin/amd64

主要学习材料

  1. google go官网教程
  2. 谢大的书籍:
    go web编程
    go实践开发
  3. 无闻的<<go编程基础>>视频教程
  4. 雨痕的笔记
  5. 其他:go学习资料链接汇总

最后,由于本人在go方面也是小白,难免水平有限,如果有大牛路过,一定不要怜惜您的键盘,狠狠的拍吧

go基础笔记

本文不讲解详细的语法,详细的知识请参见 tour.go-zh.org,只是纪录本人学习中觉得需要注意或者有意思的点

  • switch 可以不带条件,用来简化冗长的 if-else if-else
  • 结构体初始化参数可变,缺省有零值。支持 Name:特定字段赋值
  • slice 区间表示时左闭右开,可以省略上标或下标
  • _ 用来在 slice 值对中忽略序号和值
  • make 用来创建slice map channel ,其他的用new
  • map 可以同时获取值和行为结果(true/falses)
  • 值可以隐式表示函数
  • 闭包的概念

MAC下go code安装问题

在安装go lang时,已经设置过GOPATH,go 也运行正常,但是安装 gocode时提示:

Agreeing to the Xcode/iOS license requires admin privileges, please re-run as root via sudo.
package github.com/nsf/gocode: exit status 69

当我们使用sudo 时,提示:

package github.com/nsf/gocode: cannot download, $GOPATH not set. For more details see: go help gopath

虽然之前已经设置,但是当切换权限时,也切换了环境变量,所以当用sudo管理员权限时提示没有设置环境变量。

解决:

sudo env GOPATH=/Users/alex/dev/go 
go get -u github.com/nsf/gocode

在命令行里临时指定env变量,完成