一、第一个go程序
package main
import (
"fmt"
)
func main() {
fmt.Println("hello world")
}
解释:
如果是为了将代码编译成一个可执行程序,那么package
必须是main
如果是为了将代码编译成库,那么package
则没有限制
Go中所有的代码都应该隶属一个包
fmt是go的一个系统库 fmt.Println()则可以打印输出 如果想要运行程序:go run 程序名 在一个可执行程序只有一个main函数
注释:
单行注释://
多行注释:/* */
二、go语言初识
关于定义一个变量
var 变量名 变量类型 变量名 = 值
这里需要注意:go语言中定义的变量必须被用到否则会报错 同时定义变量和赋值可以一步完成通过:变量名 := 值
定义一个函数
如果我们要定义一个函数,我们先看下求和的例子:
func add(a int,b int) int {
var sum int
sum = a + b
return sum
}
这里我们需要知道,下面这种格式是被严格要求的: func 函数名 (参数1 参数1类型,参数2 参数2类型) 返回值类型 { }
三、golang语言特性
垃圾回收
- 内存自动回收,不需要开发人员管理内存
- 开发人员专注业务实现
- 只需要new分配内存,不需要释放
天然高并发
- 从语言层面支持并发,非常简单
- goroute,轻量级线程,创建成千上万个goroute成为可能
- 基于CSP(Communicating Sequential Process)模型实现
关于高并发的一个简单示例:
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()
}
上述代码的解释: pip := 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(3,5)
fmt.Println(sum,avg)
}
关于需要传入多个参数的时候是用括号括起来单个的情况下一般不用括号括起来,直接int
,而这里是返回两个则需要(ini,int)
表示返回两个整数类型值
如果有多个返回值,但是我只想返回一个值,是通过下划线方式实现,则上述代码改为:
func main() {
sum,_ := calc(3,5)
fmt.Println(sum)
}
四、包的概念
- 和python一样,把相同功能的代码放在一个目录,称之为包
- 包可以被其他包引用
- main包是用来生成可执行文件,每个程序只有一个main包
- 包的主要用途是提高代码的可复用性 关于main包中的main函数,go程序经过编译之后,运行该程序,会将编译好的二进制文件加载到内存中,会首先调用main函数,所以main函数是程序的入口函数,即必须有package main
go 源码按package进行组织,并且package要放到非注释的第一行 一个程序只有一个main包,一个包中只能有一个main函数,不能重复定义 main函数是程序的执行入口 没有被引用的变量,编译的时候会报错
go的目录规范
这里举一个简单的例子:如果我们在建立一个go_project目录,通常会在这个目录下创建如下目录
- src 存放不同的项目代码
- bin 存放编译后的可执行程序
- vender 存放引用的第三方库
- pkg 存放静态库
➜ ~ tree GoWorks GoWorks ├── bin ├── pkg ├── src │ ├── project_1 │ ├── project_2 │ └── project_3 └── vender
go环境变量中GOPATH一般会设置为: export GOPATH=/Users/wangyangyang/GoWorks
五、Go基本命令介绍
- go run: 编译和执行go程序
- go build: 编译程序,生成二进制可执行文件
- go install: 编译和安装可执行程序到bin目录
- go test:执行单元测试或压力测试
- go env: 显示go相关的环境变量
- go fmt:格式化源代码
我们通过下面例子理解这个编译命令的使用:
例一:
在src/go_dev/hello
目录下写了一个hello程序
现在把这个hello程序进行编译
$ cd $GOPATH
$ go build go_dev/hello
在$GOPATH
目录下执行的编译命令,如果不指定编译生成在当前目录即在$GOPATH
目录下
这里需要解释的是go build
后面的路径 go_dev/hello
从目录结构看src这层目录并没有写,是因为go编译的时候,会自动去GOPATH下的src目录里找,所以这里不需要写,同时编译的路径只写到hello目录而不是hello.go文件
例二:
在src/go_dev/goroute
目录下创建两个go文件,goroute.go
和main.go
goroute.go文件:
package main
import "fmt"
func test_goroute(a int){
fmt.Println(a)
}
main.go文件:
package main
import (
"time"
)
func main(){
for i := 0; i < 100; i++ {
go test_goroute(i)
}
time.Sleep(time.Second)
}
编译只需要在GOPATH目录下执行:
go build go_dev/goroute
这样就会再GOPATH目录下生成一个可执行文件goroute
例三:
在src/go_dev/sum_test
目录下创建calc目录和main目录,同时在calc下建立sum.go文件,main目录下建立main.go文件
sum.go文件
package calc
func Add(a int,b int,c chan int){
sum := a + b
c <- sum
}
这里需要注意sum.go是作为包写的,所以我们开头是:package calc,即package + sum.go的所在上级目录,并且是sum中定义的函数名首字母要大写,是因为我们定义的包都是要被外部的其他包调用,即我们这里定义的sum.go是要被其他包调用,这个时候只有首字母大写才能被其他包调用到
main.go文件
package main
import (
"fmt"
"go_dev/day01/goroute_test/calc"
)
func main(){
pipe := make(chan int,1)
calc.Add(100,200,pipe)
res :=<- pipe
fmt.Println(res)
}
这次编译的时候指定编译文件生成的目录命令如下:
go build go_dev/sum_test/main
单元测试例子:
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开头 上述测试文件执行结果:
$ go test
PASS
ok go_dev/calc 0.007s
$ go test -v
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
calc_test.go:12: add is Ok
PASS
ok go_dev/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! 你好世界")
}