01 | go语言初识
开发环境搭建
安装go
a 打开网址https://golang.org/dl
b 根据操作系统选择对应的安装包
c 点击安装包进行安装(linux直接解压)
d 设置环境变量(linux)
1. export GOROOT=$PATH:/path/to/go/
2. export PATH=$PATH:$GOROOT/bin/
3. export GOPATH=/home/user/project/go
e. 设置环境变量(window不用设置)
IDE搭建(vscode)
a. 打开网址:https://code.visualstudio.com/ b. 根据操作系统选择对应的安装包 c. 点击安装包进行安装(linux直接解压) d. 选择查看-》扩展-》搜索go,安装第二个 e. vscode会提示你安装一些go的工具,我们点击install all
3. 调试工具delve安装
1 打开网址: https://github.com/derekparker/delve/tree/master/Documentation/installation
2. mac: brew install go-delve/delve/delve
3. linux&windows: go get github.com/derekparker/delve/cmd/dlv
由于我的go代码都是放在D/project目录中
所以把为该目录添加环境变量
一、第一个go程序
package main import ( "fmt" ) func main(){ fmt.Println("hello world") }
对于代码的解释
如果是为了将代码编译成一个可执行程序,那么package必须是main
如果是为了将代码编译成库,那么package则没有限制
go中所有的代码都应该隶属一个包
fmt 是go的一个系统库
fmt.println()则可以打印输出
如果想要运行程序:go run 程序名
在一个可执行程序只有一个main函数
关于注释
单行注释://
多行注释:/* */
在控制台输出
go run hello.go
打印结果如下
二、go语言初识
关于定义一个变量
var 变量名 变量类型
变量名 = 值
这里需要注意:go语言中定义的变量必须被用到,否则会报错
同时定义变量和赋值可以一步完成通过: 变量名 := 值
定义一个函数
如果我们要定义一个函数,我们先看下面求和的例子:
package main import( "fmt" ) func add(a int,b int) int { var sum int sum = a + b return sum } func main() { var c int c = add(22,33) fmt.Println(c) }
输出结果为
这里我们需要知道,下面这种格式是被严格要求的,第一个大括号不能放到下一行
func 函数名( 参数1 类型,参数2 类型) 返回值类型 {
}
三、golang语言特性
垃圾回收
内存自动回收,不需要开发人员管理内存
开发人员专注业务实现
只需要new分配内存,不需要释放
天然高并发
- 从语言曾元支持并发,非常简单
- goroute,轻量级线程,创建成千上万goroute成为可能
- 基于CSP模型实现
关于高并发的一个简单演示:
package main import ( "fmt" "time" ) func test_print(a int){ fmt.Println(a) } func main(){ for i:= 0;i < 100; i ++ { go test_print(i) } time.Sleep(time.Second) }
在实现高并发的时候只需要在调用的函数前面加上go,就表示开启了并发
如果在for循环的外面不加上time.Sleep(time.Second),就会发现会少打印了,这是因为当主程序运行完之后,并不会等待线程,所以程序直接终止
打印结果(异步调度)
channel管道
类似linux中的pipe
多个goroute之间通过channel进行通信
支持任何类型
package main import ( "fmt" ) func test_pipe(){ pipe := make(chan int,3) pipe <- 1 pipe <- 2 pipe <- 3 fmt.Println(len(pipe)) } func main(){ test_pipe() }
上述代码的解释:
pipe := make(chan int,3) 这里是定义一个管道pipe,go是强类型语言,所以这里声明一个管道变量pipe需要通过有三个参数,chan表示是管道,int表示管道类型,3表示管道容量
通过len(pipe)可以查看管道的的长度
如果想要从管道里获取数据
t1 :=<- pipe 这个写法等同于
var t1 int
t1 = <- pipe
fmt.Println(t1)
管道遵循的原则是先进先出,所以第一个获取的值是1
小结:如果想要给管道放入值:定义的管道pipe < - 要存入的内容
如果想要从管道中获取值:变量名 :=<- 定义的管道pipe
这里强调一下go中package包的概念,一个包里的变量,在这个包里是都可以访问,但是在包之外也是有权限限制是否可以访问到,如果一个变量在一个包里是大写的,在其他包里就可以访问到,如果是小写的其他包里则访问不到。类似其他语言中的public和private
多返回值
一个函数可以返回多个值
package main import "fmt" func calc(a int,b int) (int,int ){ sum := a + b avg := sum / 2 return sum,avg } func main(){ sum,avg := calc(100,200) fmt.Println(sum,avg) }
关于需要传入多个参数的时候是用括号括起来单个的情况下一般不用括号括起来,直接int,而这里是返回两个则需要 (int,int)表示返回两个整数类型值
如果有多个返回值,但是我只想返回一个值,是通过下划线方式实现,则上述代码改为:
func main(){ sum,_ := calc(100,200) fmt.Println(sum) }
四、包的概念
- 和python一样,把相同功能的代码放到一个目录,称之为包
- 包可以被其他包引用
- main包是用来生成可执行文件,每个程序只有一个main包
- 包的主要用途是提高代码的课复用性
关于main包中的main函数,go程序经过编译之后,运行该程序,会将编译好的二进制文件加载到内存中,会首先调用main函数,所以main函数是程序的入口函数,即必须有package main
关于包,是我们可以把一些常用的功能封装到包中,这个时候包中的每个go文件的开头则不需要package main,而是package 自定义名字 这个自定义名字是根据这个包的功能进行命名
go源码按package进行组织,并且package要放到非注释的第一行
一个程序只有一个main包,一个包中只能有一个main函数,不能重复定义
main函数是程序的执行入口
没有被引用的变量,编译的时候会报错
go的目录规范
这里举一个简单的例子:如果我们在建立一个go_project目录,通常在这个目录下我们会创建如下目录
src 存放不同的项目代码
bin 存放编译后的可执行程序
vender 存放引用的第三方库
pgk 存放静态库
我们的go环境变量中的GOPATH一般会设置为:
(我这里是路径是/users/zhaofan/go_project)
export GOPATH=/users/zhaofan/go_project
go的编译
如果我们写好了go的代码文件,我们如果测试运行可以通过:
go run 快速执行go文件
go build 编译程序,生成二进制文件
go install 安装可执行文件到bin目录下
基本命令:
go test执行单元测试或压力测试
go env 显示go相关的环境变量
go fmt 格式化源代码
我们通过下面例子理解这个编译命令的使用:
例子一
我在D:\project/go_project/src/go_dev/day01/hello目录下写了一个hello程序
现在把这个hello程序进行编译
我们在go_project目录下执行的编译命令,如果不指定编译生成的文件会直接将编译文件生成在当前目录即go_project目录下
这里需要解释的是go build 后面的路径go build go_dev/day01/hello
我们从目录结构可以看出,go_dev的上一级目录src目录并没有写,这是因为go编译的时候,会自动去GOPATH下的src目录里去找,所以这里是不需要写,同时编译的路径的最后我们只写到hello目录而不是hello.go文件
例子二
我们在project/src/go_project/src/go_dev/day01/ 目录下建立一个goroute目录
并在goroute目录下建立两个go文件,main.go和goroute.go文件
main.go文件的代码为:
package main import( "go_dev/day1/goroute_example/goroute" "fmt" ) func main() { var pipe chan int pipe = make(chan int, 1) go goroute.Add(100, 300, pipe) sum := <- pipe fmt.Println("sum=", sum) }
goroute.go文件的代码为:
package goroute func Add(a int, b int, c chan int) { sum := a +b c <- sum }
在project目录下创建bin用来保存编译后的可执行文件,目录结构如下
这样我们编译的时候只需要在project目录下执行:
go build -o bin/calc_test.exe go_dev/day1/goroute_example/main
这样就会在bin目录下生成一个可执行文件
执行结果为
例子3 自定义包
还是在project/src/go_dev/day01/下建立一个package_example目录
在goroute_test目录下建立calc目录和main目录
同时在calc下建立一个add.go和sub.go文件,在main目录下建立一个main.go文件
sub.go文件代码如下:
package calc func Sub(a int, b int) int { return a - b }
add.go文件代码如下:
package calc func Add(a int, b int) int { return a + b }
这里有个地方需要注意这里我们的add.go和sub.go是作为包写的,所以我们开头是:package calc,即package+sum.go的所在上级目录,并且是sum中定义的函数名首字母要大些(这个是必须的)这里其实是因为
我们定义的包都是要被外部的其他包调用,即我们这里定义的sum.go是要被其他包调用,这个时候只有首字母大写才能被其他包调用到
main.go文件代码如下:
package main import( "go_dev/day1/package_example/calc" "fmt" ) func main() { sum := calc.Add(100, 300) sub := calc.Sub(100, 300) fmt.Println("sum=",sum) fmt.Println("sub=", sub) }
这次我们编译的时候指定编译文件生成的目录路径,命令如下:
go build -o bin/main.exe go_dev/day1/package_example/main
执行的结果为
关于单元测试例子:
单元测试的代码文件的名字格式必须是:*_test.go
例如我要写关于calc.go文件的单元测试
新建一个文件命名为:calc_test.go
这里需要知道的是开头的calc的名字并不是强制的,但是为了方便测试哪个代码文件,开头就以那个文件开头,下面是一个例子代码:
package calc import ( "testing" ) func TestAdd(t *testing.T){ var sum int sum = Add(5,6) if sum != 11{ t.Fatalf("add is not right,sum:%v expected:11",sum) } t.Logf("add is Ok") }
在代码中我们定义函数时候函数的名字也需要以Test开头
上述测试文件执行结果:
bogon:calc zhaofan$ go test PASS ok go_dev/01/calc 0.007s bogon:calc zhaofan$ go test -v === RUN TestAdd --- PASS: TestAdd (0.00s) calc_test.go:12: add is Ok PASS ok go_dev/01/calc 0.007s bogon:calc zhaofan$
Go的结构开发规范
好的代码规范是非常重要的,这样当你看别人代码或者别人看你的代码的时候就能很清楚的明白,下面是结构规范:
// 当前程序的包名 package main //导入其他的包 import "fmt" //常量的定义 const PI=3.14 //全局变量的声明和赋值 var name = "gopher" //一般类型声明 type newType int //结构的声明 type gopher struct{} //接口的声明 type golang interface{} //由main函数作为程序入口点启动 func main(){ fmt.Println("Hello world! 你好世界") }