Go语言学习杂记


=Start=

缘由:

到现在为止接触Go这门语言也快2个月了,但还没用过Go写过什么比较大的程序/项目(也许以后会有机会吧?)这期间也因为工作较忙的缘故,看书学习Go也是断断续续的,只是会在没事的时候去网上找找不错的Go语言代码来看、记录一些不错的小技巧什么的,时间长了,没有输出,进一步学习Go语言的热情有点减弱,所以,觉得该在博客中记录些什么,让自己保持这份热情;所以在参考其它文章的基础上,掺杂点自己的记录,就有了此文。

杂记:

#开发环境和工具

说明:在升级Go之前,必须先移除旧的版本。

开发环境配置

下载、解压、安装设置:

$ wget https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz
$ sudo tar -C /usr/local -xzf go1.5.1.linux-amd64.tar.gz

$ mkdir -p $HOME/go_space #在$HOME下新建一个目录作为Go项目的工作目录
$ vim ~/.profile
++++
export GOROOT=/usr/local/go
export GOPATH=$HOME/go_space
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
++++
$ source ~/.profile

涉及到的环境变量说明:

GOROOT: go的安装目录。
GOPATH:  go的工作目录,所有通过go get下载的第三方库都会位于该目录下

一些工具
go get $url
godoc -http=:8000

go help doc
go doc regexp #查看regexp这个package的文档信息
go doc regexp/syntax #查看regexp/syntax这个package的文档信息
go doc regexp.FindString #查看regexp.FindString这个method的文档信息

godoc builtin	#查看相应的package文档
godoc net/http	#查看相应的package文档

godoc fmt Printf		#查看fmt.Printf包里面的函数
godoc -src fmt Printf	#查看fmt.Printf的代码
godoc -help				#查看 godoc 帮助

go build filename.go
go run filename.go
go test -help

go tool pprof	#性能调试工具

gofmt -w src	#格式化整个Go项目
find . -name "*.go" | xargs gofmt -w	#格式化Go代码

可以用gdb调试go程序,因为Go默认编译选项就会生成调试信息,其他基本和调试C++程序类似,更多内容见文章:Debugging Go Code with GDB
#语法小结
初始化默认零值

boolean: false
integer: 0
float: 0.0
string: “”
pointer, function, interface, slice, channel, map: nil

字符串

一个Go语言字符串是一个任意字节的常量序列Go语言的字符串类型在本质上就与其他语言的字符串类型不同。Java的String、C++的std::string以及Python 3的str类型都只是定宽字符序列,而Go语言的字符串是一个用UTF-8编码的变宽字符序列,它的每一个字符都用一个或多个字节表示

Go语言中的字符串字面量使用「双引号」或「反引号」来创建。

数组和切片
intSlice := []int{1, 2, 3}
intArray := [3]int{1, 2, 3}

上面的代码中,intSlice是长度和容量均为3的slice,intArray是长度为3的数组。

切片的创建方式:

make([]Type, length, capacity)
make([]Type, length)
[]Type{}
[]Type{value1, value2, ..., valueN}	#用这种语法创建切片时可以进行初始化操作
二维数组和切片的初始化
func 2d_array_slice() {
	// Initialize a 2D array
	var grid [6][3]int
	fmt.Println(grid)
	// [[0 0 0] [0 0 0] [0 0 0] [0 0 0] [0 0 0] [0 0 0]]

	// Another way to initialize 2D array
	gridB := [6][3]int{}
	fmt.Println(gridB)
	// [[0 0 0] [0 0 0] [0 0 0] [0 0 0] [0 0 0] [0 0 0]]

	// Initialize a slice of empty slices
	gridC := make([][]int, 6)
	fmt.Println(gridC)
	// [[] [] [] [] [] []]
	for i := 0; i < len(gridC); i++ {
		gridC[i] = make([]int, 3)
	}
	fmt.Println(gridC)
	// [[0 0 0] [0 0 0] [0 0 0] [0 0 0] [0 0 0] [0 0 0]]
}
slice和append

使用append时,如果slice的容量(即对应array的长度)不够,Go会创建一个新的array(长度通常为之前的两倍)以容纳新添加的数据,所有旧的array数据都会被拷贝到新的array里。当频繁使用append时,需要考虑到其效率问题

// 如果append之前已经知道长度,可以:
intSlice := make([]int)
for i := 0; i < length; i++ {
    intSlice = append(intSlice, i)
}

// 当然,最好是不用append:
intSlice := make([]int, 0, length)
for i := 0; i < length; i++ {
    intSlice[i] = i
}
映射

映射的创建方式:

make(map[keyType]valueType, initCapacity)
make(map[keyType]valueType)
map[keyType]valueType{}
map[keyType]valueType{key1: value1, key2: value2, ..., keyN:valueN}	#用这种语法创建映射时可以进行初始化操作

根据key取value的值:

value, found := aMap[key]

上面的代码中,value是映射中key对应的值的拷贝。如果不使用第二个参数found,如下:

value := aMap[key]

则当key不存在时,value被初始化为对应的零值。

函数和方法

函数:

func Getwd() (dir string, err error)
func Hostname() (name string, err error)

方法:

func (f *File) Read(b []byte) (n int, err error)
func (f *File) Stat() (FileInfo, error)

type File struct {
        // contains filtered or unexported fields
}
方法和函数的异同

方法是作用在自定义类型的值上的一类特殊函数,通常自定义类型的值会被传递给该函数。
该值可以以指针或者值的形式进行传递,这取决于方法是如何定义的。定义方法的语法几乎等同于定义函数,除了需要在 func 关键字和方法名之间写上接收者(放在括号中)之外。

变参函数

所谓可变参数函数就是指函数的最后一个参数可以接受任意个参数。这类函数在最后一个参数的类型前面添加有一个省略号。在函数里面这个参数实质上变成了一个对应参数类型的切片。

任意个int型参数的函数		func MinimunInt0(ints ...int) int {}
至少需要一个int型的参数	func MinimunInt1(first int, rest ...int) int {}
至少需要两个int型的参数	func MinimunInt2(first, second int, rest ...int) int {}
init() 和 main() 函数

一个package里可以有很多 init() 函数,但是一个Go程序有且只能有一个 main() 函数。

init() 和 main() 函数是自动执行的,且 init() 函数先于 main() 函数执行

接口

在Go语言中,接口是一个自定义类型,它声明了一个或者多个方法签名。
接口是完全抽象化的,因此不能将其实例化。然而可以创建一个其类型为接口的变量,它可以被赋值为任何满足该接口类型的实际类型的值。

值传递

Go语言中函数的参数都是通过值传递的!As in all languages in the C family, everything in Go is passed by value.

引用类型

Go语言中的引用类型:映射、切片、通道、函数和方法。在发生值拷贝时,被拷贝的仅仅是指向实际数据的指针。

Go语言中的指针

Go语言提供了两种创建变量的语法,同时获得指向它们的指针。
一种方法是使用内置的 new() 函数;另一种方法是使用地址操作符 & 。new(Type) == &Type{}

Go语言不提供指针的运算操作。

使用指针的好处在于:值在传递给函数或者方法的时候会被复制一次。因此按值传递 布尔变量、数值类型、字符串(Go语言中的字符串是不可变的,因此可以进行优化) 时会比较廉价。但是在传递大数组的时候代价会非常大。这个时候可以考虑切片(引用)。通过使用指针,我们可以让参数的传递成本最低并且内容可修改,而且还可以让变量的生命周期独立于作用域。指针是指保存了另一个变量内存地址的变量。(引用,也即保存指针的变量

Go语言中的并发

Go语言中的并发是通过goroutine来实现的。

goroutine是程序中与其他goroutine完全相互独立而并发执行的函数或者方法调用。每一个程序至少包含一个goroutine,即会执行main包中的main()函数的主goroutine。所有的goroutine共享相同的地址空间,同时Go语言提供了锁原语来保证数据能够安全的跨goroutine共享。然而,Go语言推荐的并发方式是通信(通道channel),而非共享数据。

#常见问题
赋值操作符

使用赋值操作符「:=」需要注意的地方:

  • 左边的成员至少要有一个是未声明的变量。
  • 已声明的变量,在同一作用域内,变量值被改写。
  • 如果已声明的变量作用域在外层,则定义一个新的同名变量,会屏蔽外层的变量。
for…range
for k, v := range myMap {
    log.Printf("key=%v, value=%v", k, v)
}

for v := range myChannel {
    log.Printf("value=%v", v)
}

for i, v := range myArray {
    log.Printf("array value at [%d]=%v", i, v)
}

// `range` on strings iterates over Unicode code points.
// The first value is the starting byte index of the `rune` and the second the `rune` itself.
for i, c := range "go" {
    fmt.Println(i, c)
}
defer

defer语句用于延迟一个函数或者方法(或者当前所创建的匿名函数)的执行,它会在外围函数或者方法返回之前但是其返回值(如果有的话)计算之后执行。
如果一个函数或者方法中有多个defer语句,它们会以LIFO(Last in First out)的顺序执行。

package main

import "fmt"

func main() {
	defer fmt.Println("world")
	fmt.Println("hello")
}
#其它问题
Go语言的文件操作?

http://www.devdungeon.com/content/working-files-go

Go语言中如何进行字符串连接操作?

http://stackoverflow.com/questions/1760757/how-to-efficiently-concatenate-strings-in-go

如何减小编译后文件的大小?

http://stackoverflow.com/questions/3861634/how-to-reduce-compiled-file-size

#一些例子
/*
简单(并发)端口扫描器
https://golang.org/pkg/sync/#WaitGroup
https://phpinfo.me/2015/06/24/958.html
*/
package main

import (
	"fmt"
	"net"
	"os"
	"runtime"
	"sync"
)

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	if len(os.Args) != 2 {
		fmt.Fprintf(os.Stderr, "Usage: %s ip/host\n", os.Args[0])
		os.Exit(1)
	}
	ip := os.Args[1]
	fmt.Println("[+]Scaning", ip)

	ports := []string{"80", "443", "8080", "8009", "3306", "21", "22", "6379", "27017", "11211", "3389", "1433"}
	wg := sync.WaitGroup{}
	wg.Add(len(ports))
	for _, port := range ports {
		go ScanPort(ip, port, &wg)
	}
	wg.Wait()
	fmt.Println("[+]Stop", ip)
}

func ScanPort(ip, port string, wg *sync.WaitGroup) {
	socket := ip + ":" + port
	_, err := net.Dial("tcp", socket)
	if err != nil {
		fmt.Println("[-]", port, "Close")
	} else {
		fmt.Println("[+]", port, "Open")
	}
	wg.Done()
}
参考链接:

=EOF=

, ,

《 “Go语言学习杂记” 》 有 26 条评论

  1. Golang的一些库:
      字符处理( fmt / strings / strconv / regexp / utf8 / unicode / …)
      文件处理( io / bufio / path / …)
      进程处理( os / runtime / …)
      网络库( socket / http / rpc / …)
      编码库( json / xml / gob / archive / compress / …)
      加密库( 加密算法 / 摘要算法 / …)
      Web( template / html / …)

  2. Go语言学习目标:
      [文件操作]实际使用:
        1.将一个 指定文件夹 进行 zip/unzip
        2.将一个 正常文件/zipped文件 进行压缩/解压缩
        3.将一个文本文件中的内容以数组形式读入以方便遍历
        4.将一个json文件中的内容读入并提取出指定字段
        5.将一个文件中的内容以指定格式读取、解析并进行 去重/计数 操作
        6.字符串查找(+正则表达式)
      
      [网络操作]实际使用:
        7.端口扫描
        8.网络文件下载
        9.爬虫
      
      [进程操作]实际使用:
        10.进程查找、停止
        11.daemon进程编写
      
      [数据库操作]实际使用:
        12.MySQL、Redis、MongoDB、Memcached

  3. golang 项目实战简明指南
    http://litang.me/post/golang-project-guide/
    `
    开发环境搭建
    hello world
    命名规范
     常量
     局部变量
     形参
     返回值
     接收器(Receivers)
     包级导出名
     接口
     错误
     Getter/Setter
     包
     包路径
    项目代码布局
    依赖管理
    可执行程序版本管理
    性能剖析(profiling)
    测试
    总结
    参考资料
    `

  4. 一键解决 go get golang.org/x 包失败
    https://paper.tuisec.win/detail/fc2e8c1cc07d5d7
    https://shockerli.net/post/go-get-golang-org-x-solution/
    `
    # 手动下载
    我们常见的golang.org/x/…包,一般在 GitHub 上都有官方的镜像仓库对应。比如golang.org/x/text对应github.com/golang/text。所以,我们可以手动下载或 clone 对应的 GitHub 仓库到指定的目录下。

    # 设置代理
    如果你有代理,那么可以设置对应的环境变量:
    export http_proxy=http://proxyAddress:port
    export https_proxy=http://proxyAddress:port
    或者,直接用all_proxy:
    export all_proxy=http://proxyAddress:port

    # go mod replace
    从 Go 1.11 版本开始,新增支持了go modules用于解决包依赖管理问题。该工具提供了replace,就是为了解决包的别名问题,也能替我们解决golang.org/x无法下载的的问题。

    go module被集成到原生的go mod命令中,但是如果你的代码库在$GOPATH中,module功能是默认不会开启的,想要开启也非常简单,通过一个环境变量即可开启export GO111MODULE=on。

    # GOPROXY 环境变量

    终于到了本文的终极大杀器 —— GOPROXY。

    我们知道从Go 1.11版本开始,官方支持了go module包依赖管理工具。

    其实还新增了GOPROXY环境变量。如果设置了该变量,下载源代码时将会通过这个环境变量设置的代理地址,而不再是以前的直接从代码库下载。这无疑对我等无法科学上网的开发良民来说是最大的福音。
    `

  5. 实战Go内存泄露
    https://mp.weixin.qq.com/s/d0olIiZgZNyZsO-OZDiEoA
    `
    最近解决了我们项目中的一个内存泄露问题,事实再次证明pprof是一个好工具,但掌握好工具的正确用法,才能发挥好工具的威力,不然就算你手里有屠龙刀,也成不了天下第一,本文就是带你用pprof定位内存泄露问题。

    关于Go的内存泄露有这么一句话不知道你听过没有:
    10次内存泄露,有9次是goroutine泄露。

    我所解决的问题,也是goroutine泄露导致的内存泄露,所以这篇文章主要介绍Go程序的goroutine泄露,掌握了如何定位和解决goroutine泄露,就掌握了内存泄露的大部分场景。
    `

  6. goproxy.cn – 为中国 Go 语言开发者量身打造的模块代理
    https://mp.weixin.qq.com/s/Pw_a5heUgyIkuJrXF4HCVg
    `
    Go 1.13 的发布为 Go 带来了不少变化(详见: https://golang.org/doc/go1.13 ),有些变化可能是开发者无法直接感觉到的,但有些又是和开发者日常开发息息相关的。其中,Go modules 的扶正就是这次 Go 1.13 发布中开发者能直接感觉到的最大变化。

    Go modules 最早发布于 Go 1.11,经过两个版本的更新后,它作为依赖管理解决方案来说现在已经变得光彩夺目。随着 Go modules 一起被发布的还有一个叫做 Module proxy protocol 的协议,通过它我们可以搭建 Go 模块代理,最后交由 GOPROXY 环境变量以指引 go 命令后续在抓取模块时的途径。

    对于咱们中国的开发者来说,一个优秀的 Go 模块代理可以帮助我们解决很多问题。比如 Go 语言中最知名的 golang.org/x/… 模块在中国大陆是无法访问到的,以前我们会用很多其他的办法来抓取他们,而若依靠一个可以访问到它们的模块代理,那么将事半功倍。

    更因为 Go 1.13 将 GOPROXY 默认成了中国大陆无法访问的 https://proxy.golang.org ,所以我们中国的开发者从今以后必须先修改 GOPROXY 才能正常使用 go 来开发应用了。为此,我们联合中国备受信赖的云服务提供商七牛云专门为咱们中国开发者而打造了一个 Go 模块代理:goproxy.cn。

    goproxy.cn 是目前中国最可靠的 Go 模块代理,这个如果有人存在质疑可以一一测试比对列表中所有能在国内访问的代理。对于那个和 goproxy.cn 域名比较相近的 goproxy.io,我之前已经发表过一篇实测文章(详见: https://studygolang.com/topics/9994 )。
    `

  7. Go语言init函数你必须记住的六个特征
    https://mp.weixin.qq.com/s/P-BhuQy1Vd3lxlYgClDAJA
    `
    Go应用程序的初始化是在单一的goroutine中执行的。对于包这一级别的初始化来说,在一个包里会先进行包级别变量的初始化。一个包下可以有多个init函数,每个文件也可以有多个init 函数,多个 init 函数按照它们的文件名顺序逐个初始化。但是程序不可能把所有代码都放在一个包里,通常都是会引入很多包。如果main包引入了pkg1包,pkg1包本身又导入了包pkg2,那么应用程序的初始化会按照什么顺序来执行呢?

    * 包级别变量的初始化先于包内init函数的执行。

    * 一个包下可以有多个init函数,每个文件也可以有多个init 函数。

    * 多个 init 函数按照它们的文件名顺序逐个初始化。

    * 应用初始化时初始化工作的顺序是,从被导入的最深层包开始进行初始化,层层递出最后到main包。

    * 不管包被导入多少次,包内的init函数只会执行一次。

    * 应用在所有初始化工作完成后才会执行main函数。
    `

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注