brew install go
然后查看安装情况
➜ ~ go env
GOARCH="amd64"
GOBIN="/Users/xingqi/go/bin"
GOCACHE="/Users/xingqi/Library/Caches/go-build"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/xingqi/go"
GOPROXY=""
GORACE=""
GOROOT="/usr/local/Cellar/go/1.12.9/libexec"
GOTMPDIR=""
GOTOOLDIR="/usr/local/Cellar/go/1.12.9/libexec/pkg/tool/darwin_amd64"
GCCGO="gccgo"
CC="clang"
CXX="clang++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/gl/sppvbbjj6tb33_qf8r9vlmwr0000gp/T/go-build041361416=/tmp/go-build -gno-record-gcc-switches -fno-common"
关注我们的GOPATH="/Users/xingqi/go",因为我们的项目必须运行在该目录下,可以接受多个路径,并且路径和路径之间用冒号分割。
GOPATH:为我们开发常用的目录,建议不要和Go的安装目录一致,在该文件夹下又有三个文件夹:src、pkg、bin,这里src是自己新建的,pkg和bin是后面生成的。
src:主要存放我们的源代码
bin:存放编译后生成的可执行文件,可以自己执行
pkg: 编译后生成的文件(.a文件)(非main函数的文件在go install后生成)
GOBIN:是GOPATH下的bin目录
PATH:环境变量,需要go-bin目录加入到path路径下,生成可执行文件就可以直接运行了。
/**
每个Go源代码文件的开头都是一个package声明,表示该Go代码所属的包。
包是Go语言里最基本的分发单位,也是工程管理中依赖关系的体现。
要生成Go可执行程序,必须建立一个名字为main的包,并且在该包中包含一个叫main()的函数(该函数是Go可执行程序的执行起点)。
*/
package main
/**
在包声明之后,是一系列的import语句,用于导入该程序所依赖的包。
由于本示例程序用到了Println()函数,所以需要导入该函数所属的fmt包。
有一点需要注意,不得包含在源代码文件中没有用到的包,否则Go编译器会报编译错误。
*/
import "fmt"
/**
Go语言的main()函数不能带参数,也不能定义返回值。
命令行传入的参数在os.Args变量中保存。
如果需要支持命令行开关,可使用flag包。
*/
func main() {
fmt.Println("Hello, world. 你好,世界!")
}
所有Go函数(包括在对象编程中会提到的类型成员函数)以关键字func开头。
一个常规的函数定义包含以下部分:
func 函数名(参数列表)(返回值列表) { // 函数体
}
对应的一个实例如下:
func Compute(value1 int, value2 float64)(result float64, err error) { // 函数体
}
Go支持多个返回值。以上的示例函数Compute()返回了两个值,一个叫result,另一个是
err。并不是所有返回值都必须赋值。在函数返回时没有被明确赋值的返回值都会被设置为默认值,比如result会被设为0.0,err会被设为nil。
下面 我们运行一下这个程序,进入该文件目录,执行以下命令
➜ learn-go go run Hello.go
Hello, world. 你好,世界!
这个命令,会将编译、链接和运行3个步骤合并为一步,运行完后在当前目录下也看不到任何中间文件和最终的可执行文件。如果要只生成编译结果而不自动运行,我们也可以使用 Go 命令行工具的build命令
➜ learn-go go build Hello.go
➜ learn-go ./Hello
Hello, world. 你好,世界!
当然你也可以选择IDE
如:https://www.jetbrains.com/go/
变量相当于是对一块数据存储 空间的命名,程序可以通过定义一个变量来申请一块数据存储空间,之后可以通过引用变量名来 使用这块存储空间。
Go语言的变量声明方式与C和C++语言有明显的不同。对于纯粹的变量声明,Go语言引入了 关键字var,而类型信息放在变量名之后,示例如下:
var v1 int
var v2 string
var v3 [10]int // 数组
var v4 []int // 数组切片
var v5 struct {
f int
}
var v6 *int // 指针
var v7 map[string]int // map,key为string类型,value为int类型
var v8 func(a int) int
变量声明语句不需要使用分号作为结束符。与C语言相比,Go语言摒弃了语句必须以分号作 为语句结束标记的习惯。
var关键字的另一种用法是可以将若干个需要声明的变量放置在一起,免得程序员需要重复 写var关键字,如下所示
var (
v1 int
v2 string
)
var v1 int = 10 // 正确的使用方式1
var v2 = 10 // 正确的使用方式2,编译器可以自动推导出v2的类型
v3 := 10 // 正确的使用方式3,编译器可以自动推导出v3的类型
以上三种用法的效果是完全一样的。
指定类型已不再是必需的,Go编译器可以从初始化表达式的右值推导出该变量应该声明为 4
哪种类型,这让Go语言看起来有点像动态类型语言,尽管Go语言实际上是不折不扣的强类型语 言(静态类型语言)。
当然,出现在:=左侧的变量不应该是已经被声明过的,否则会导致编译错误。
在Go语法中,变量初始化和变量赋值是两个不同的概念。下面为声明一个变量之后的赋值过程:
var v10 int
v10 = 123
Go语言的变量赋值与多数语言一致,但Go语言中提供了C/C++程序员期盼多年的多重赋值功 能,比如下面这个交换i和j变量的语句:
i, j = j, i
我们在使用传统的强类型语言编程时,经常会出现这种情况,即在调用函数时为了获取一个 值,却因为该函数返回多个值而不得不定义一堆没用的变量。在Go中这种情况可以通过结合使用多重返回和匿名变量来避免这种丑陋的写法,让代码看起来更加优雅。
假设GetName()函数的定义如下,它返回3个值,分别为firstName、lastName和nickName:
func GetName() (firstName, lastName, nickName string) {
return "May", "Chan", "Chibi Maruko"
}
若只想获得nickName,则函数调用语句可以用如下方式编写:
_, _, nickName := GetName()
这种用法可以让代码非常清晰,基本上屏蔽掉了可能混淆代码阅读者视线的内容,从而大幅 降低沟通的复杂度和代码维护的难度。
在Go语言中,常量是指编译期间就已知且不可改变的值。常量可以是数值类型(包括整型、
浮点型和复数类型)、布尔类型、字符串类型等。
所谓字面常量(literal),是指程序中硬编码的常量,如:
-12
3.14159265358979323846 // 浮点类型的常量
3.2+12i // 复数类型的常量
true // 布尔类型的常量
"foo" // 字符串常量
通过const关键字,你可以给字面常量指定一个友好的名字:
const Pi float64 = 3.14159265358979323846
const zero = 0.0 // 无类型浮点常量
const u, v float32 = 0, 3 // u = 0.0, v = 3.0,常量的多重赋值
const a, b, c = 3, 4, "foo" //a=3,b=4,c="foo", 无类型整型和字符串常量
Go语言预定义了这些常量:true、false和iota。
iota比较特殊,可以被认为是一个可被编译器修改的常量,在每一个const关键字出现时被重置为0,然后在下一个const出现之前,每出现一次iota,其所代表的数字会自动增1。
package main
import (
"fmt"
)
const (
c0 = iota // iota被重设为0 // c0 == 0
c1 = iota // c1 == 1
c2 = iota // c2 == 2
)
const (
a = 1 << iota
b = 1 << iota
c = 1 << iota
)
const (
u = iota * 42
v float64 = iota * 42
w = iota * 42
)
const x = iota
const y = iota
const (
c3 = iota
c4
c5
)
const (
d = 1 << iota
e
f
)
func main() {
fmt.Println("c0 =", c0)
fmt.Println("c1 =", c1)
fmt.Println("c2 =", c2)
fmt.Println("a =", a)
fmt.Println("b =", b)
fmt.Println("c =", c)
fmt.Println("u =", u)
fmt.Println("v =", v)
fmt.Println("w =", w)
fmt.Println("x =", x)
fmt.Println("y =", y)
fmt.Println("c3 =", c3)
fmt.Println("c4 =", c4)
fmt.Println("c5 =", c5)
fmt.Println("d =", d)
fmt.Println("e =", e)
fmt.Println("f =", f)
}
//结果
c0 = 0
c1 = 1
c2 = 2
a = 1
b = 2
c = 4
u = 0
v = 42
w = 84
x = 0
y = 0
c3 = 0
c4 = 1
c5 = 2
d = 1
e = 2
f = 4
Go语言并不支持众多其他语言明确支持的enum关键字。
下面是一个常规的枚举表示法,其中定义了一系列整型常量:
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
numberOfDays // 这个常量没有导出
)
同Go语言的其他符号(symbol)一样,以大写字母开头的常量在包外可见。
以上例子中numberOfDays为包内私有,其他符号则可被其他包访问。
Go语言内置以下这些基础类型:
此外,Go语言也支持以下这些复合类型:
在这些基础类型之上Go还封装了下面这几种类型:int、uint和uintptr等。这些类型的 特点在于使用方便,但使用者不能对这些类型的长度做任何假设。对于常规的开发来说,用int 和uint就可以了,没必要用int8之类明确指定长度的类型,以免导致移植困难。
在Go语言中,数组长度在定义后就不可更改,在声明时长度可以为一个常量或者一个常量 表达式(常量表达式是指在编译期即可计算结果的表达式)。数组的长度是该数组类型的一个内 置常量,可以用Go语言的内置函数len()来获取。
需要特别注意的是,在Go语言中数组是一个值类型(value type)。所有的值类型变量在赋值和作为参数传递时都将产生一次复制动作。如果将数组作为函数的参数类型,则在函数调用时该 参数将发生数据复制。因此,在函数体中无法修改传入的数组的内容,因为函数内操作的只是所 传入数组的一个副本。
所以Go语言提供数组切片(slice),数组切片的数据结构可以抽象为以下3个变量:
从现有的数组创建
package main
import "fmt"
func main() {
var myArray [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
var mySlice []int = myArray[:5]
fmt.Println("Elements of myArray: ")
for _, v := range myArray {
fmt.Print(v, " ")
}
fmt.Println("\nElements of mySlice: ")
for _, v := range mySlice {
fmt.Print(v, " ")
}
fmt.Println()
}
//result
Elements of myArray:
1 2 3 4 5 6 7 8 9 10
Elements of mySlice:
1 2 3 4 5
Go语言支持用myArray[first:last]这样的方式来基于数组生成一 个数组切片,而且这个用法还很灵活,比如下面几种都是合法的。
基于myArray的所有元素创建数组切片:
mySlice = myArray[:]
基于myArray的前5个元素创建数组切片:
mySlice = myArray[:5]
基于从第5个元素开始的所有元素创建数组切片:
mySlice = myArray[5:]
直接创建
Go语言提供的内置函数make()可以用于灵活地创建数组切片。
//创建一个初始元素个数为5的数组切片,元素初始值为0:
mySlice1 := make([]int, 5)
for _, v := range mySlice1 {
fmt.Print(v, " ")
}
fmt.Print(len(mySlice1), " ")
fmt.Print(cap(mySlice1), " ")
fmt.Println()
//在后面追加
mySlice1 = append(mySlice1, 11, 22)
for _, v := range mySlice1 {
fmt.Print(v, " ")
}
fmt.Print(len(mySlice1), " ")
fmt.Print(cap(mySlice1), " ")
fmt.Println()
//创建一个初始元素个数为5的数组切片,元素初始值为0,并预留10个元素的存储空间:
mySlice2 := make([]int, 5, 10)
for _, v := range mySlice2 {
fmt.Print(v, " ")
}
fmt.Print(len(mySlice2), " ")
fmt.Print(cap(mySlice2), " ")
fmt.Println()
//直接创建并初始化包含5个元素的数组切片:
mySlice3 := []int{1, 2, 3, 4, 5}
for _, v := range mySlice3 {
fmt.Print(v, " ")
}
fmt.Print(len(mySlice3), " ")
fmt.Print(cap(mySlice3), " ")
fmt.Println()
//result
0 0 0 0 0 5 5
0 0 0 0 0 11 22 7 10
0 0 0 0 0 5 10
1 2 3 4 5 5 5
在Go中,使用map不需要引入任何库,并且用起来也更加方便。
map是一堆键值对的未排序集合。比如以身份证号作为唯一键来标识一个人的信息。