上班两个月,写了 C++,写了 Pathon,最近又要开始写 Go,唯独没有用我最擅长的 Java。。。
Go 开发环境的安装
下载地址:https://golang.org/dl/,选择对应的操作系统。
例如 Mac 系统选择 go1.12.darwin-amd64.pkg
,安装完成后添加到环境变量:
export PATH=$PATH:/usr/local/go/bin
通过执行 go version
来确定安装是否完成:
Go HelloWorld
创建 go_hello_world.go
:
package main
import "fmt"
func main() {
/* Go HelloWorld */
fmt.Println("Hello, World!")
}
通过执行 go run go_hello_world.go
来运行:
分析上面的代码:
-
package main
为包声明,指明这个文件属于哪个包。package main
表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为main
的包。 -
import "fmt"
为引入包声明。告诉 Go 编译器这个程序需要使用fmt
包,fmt
包实现了格式化 IO(输入/输出)的函数。 -
func main() {
为函数。main
函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有init()
函数则会先执行该函数)。 - 可以使用
/.../
或者/*...*/
来定义注释。 - 当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:
Println
,那么使用这种形式的标识符的对象就可以被外部包的代码所使用,这被称为导出(像面向对象语言中的public
);标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的protected
)。 - 需要注意的是
{
不能单独放在一行。
Go 基础语法
Go 标记
Go 程序可以由多个标记组成,可以是关键字,标识符,常量,字符串,符号。
行分隔符
在 Go 程序中,一行代表一个语句结束。
每个语句不需要像 C 家族中的其它语言一样以分号 ;
结尾。
标识符
标识符用来命名变量、类型、函数名、结构字段等程序实体。
一个标识符实际上就是一个或是多个字母、数字、下划线组成的序列,但是第一个字符必须是字母或下划线而不能是数字。
关键字
具体参见:https://golang.org/ref/spec#Keywords
Go 数据类型
- 布尔型
-
true
false
-
- 数字类型
-
uint8
uint16
uint32
uint64
-
int8
int16
int32
int64
-
float32
float64
complex64
complex128
-
byte
rune
uint
int
uintptr
-
- 字符串类型
- Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。。
- 派生类型
- 指针类型(Pointer)
- 数组类型
- 结构化类型(struct)
- Channel 类型
- 函数类型
- 切片类型
- 接口类型(interface)
- Map 类型
Go 变量
声明变量的一般形式是使用 var
关键字。
第一种,指定变量类型,声明后若不赋值,使用默认值。
var i int
i = 1
第二种,根据值自行判定变量类型。
var i = 1
第三种,省略 var
,使用 :=
。将由编译器自动推断类型,只能在函数体中出现。
i := 1
值类型和引用类型:
- 所有像
int
、float
、bool
和string
这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值。例如:
func main() {
var i = 1
var j = i
fmt.Println(&i) // 取地址
fmt.Println(&i, " != ", &j) // 地址不同
}
- 一个引用类型的变量
r1
存储的是r1
的值所在的内存地址(数字),或内存地址中第一个字所在的位置。例如:
并行赋值:
例如:
func main() {
var a, b int
var c string
a, b, c = 1, 2, "abc"
// 或者 a, b, c := 1, 2, "abc"
fmt.Println(a, b, c);
}
- 如果你想要交换两个变量的值,则可以简单地使用
a, b = b, a
,两个变量的类型必须是相同 - 并行赋值的一个作用:比如这里的
val
和错误err
是通过调用Func1
函数同时得到:val, err = Func1(...)
。 - 空白标识符
_
也被用于抛弃值,如值1
在:_, i = 1, 2
中被抛弃。-
_
实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。 - 例如我们可能不需要使用函数范围的错误
err
,那么可以使用val, _ = Func1(...)
。
-
Go 常量
定义格式:
const identifier [type] = value
。可以省略类型 [type]
,因为编译器可以根据变量的值来推断其类型。
func main() {
const i int = 1
const j = "abc"
fmt.Println(i, j);
}
Go 运算符
跟 Java 差不多,具体参见:
- 算数运算符:https://golang.org/ref/spec#Arithmetic_operators
- 关系运算符:https://golang.org/ref/spec#Comparison_operators
- 逻辑运算符:https://golang.org/ref/spec#Logical_operators
- 位运算符:https://golang.org/ref/spec#Arithmetic_operators
- 赋值运算符
- 其他运算符
-
&
返回变量存储地址 -
*
指针变量,例如:
-
func main() {
var i int = 1
var p *int
p = &i
fmt.Println(i, *p, p); // 1 1 0xc210000018
}
Go 条件语句
跟 Java 差不多。
func main() {
var i int = 1
if i > 0 { // 注意不要用括号 if (i > 0)
fmt.Println("i > 0")
} else { // 注意 else 不能换行
fmt.Println("i <= 0")
}
var j int = 2
switch j { // 注意不要用括号 switch (j)
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
}
}
Go 循环语句
跟 Java 差不多。
func main() {
for n := 0; n <= 5; n++ { // 注意不要用括号 for (n := 0; n <= 5; n++)
if n%2 == 0 {
continue
}
fmt.Println(n)
}
}
Go 函数
Go 语言最少有个 main()
函数。
函数定义:
func function_name( [parameter list] ) [return_types] {
函数体
}
示例:
// 一个返回值
func f1(i int, j int) int {
return i + j
}
// 多个返回值
func f2(i int, j int) (int, int) {
return i + j, i - j
}
func main() {
var m = f1(1, 2)
fmt.Println(m) // 3
var p, q = f2(1, 2)
fmt.Println(p, q) //3 -1
}
Go 变量作用域
变量可以在三个地方声明:
-
函数内定义的变量称为局部变量
- 它们的作用域只在函数体内,参数和返回值变量也是局部变量。
-
函数外定义的变量称为全局变量
- 全局变量可以在任何函数中使用。
- 全局变量可以在整个包甚至外部包(被导出后)使用。
- Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。
var g = 1
func main() {
fmt.Println(g)
}
- 函数定义中的变量称为形式参数
- 形式参数会作为函数的局部变量来使用。
不同类型的局部和全局变量默认值为:
-
int
-0
-
float32
-0
-
pointer
-nil
Go 数组
数组定义:
var variable_name [SIZE] variable_type
示例:
func main() {
var a [5] int
fmt.Println(a) // [0 0 0 0 0]
a[4] = 100
fmt.Println(a[4]) // 100
fmt.Println(a) // [0 0 0 0 100]
b := [5] int {1, 2, 3, 4, 5}
fmt.Println(b) // [1 2 3 4 5]
var twoD [2][3] int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println(twoD) // [[0 1 2] [1 2 3]]
}
Go 指针
指针定义:
var var_name *var-type
示例:
func zeroval(ival int) {
ival = 0
}
func zeroptr(iptr *int) {
*iptr = 0
}
func main() {
i := 1
fmt.Println("initial:", i) // 1
zeroval(i)
fmt.Println("zeroval:", i) // 1
zeroptr(&i)
fmt.Println("zeroptr:", i) // 0
fmt.Println("pointer:", &i) // 0xc210000018
}
当一个指针被定义后没有分配到任何变量时,它的值为 nil
。
nil
指针也称为空指针。
Go 结构体
定义结构体:
type struct_variable_type struct {
member definition;
member definition;
...
member definition;
}
- 使用结构体进行变量的声明和定义:
variable_name := structure_variable_type {value1, value2...valuen}
variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}
- 如果要访问结构体成员,需要使用点号
.
操作符 - 可以定义指向结构体的指针
示例:
type person struct {
name string
age int
}
func main() {
fmt.Println(person{"Bob", 20}) // {Bob 20}
fmt.Println(person{name: "Alice", age: 30}) // {Alice 30}
fmt.Println(person{name: "Fred"}) // {Fred 0}
fmt.Println(&person{name: "Ann", age: 40}) // &{Ann 40}
s := person{name: "Sean", age: 50}
fmt.Println(s.name) // Sean
sp := &s
fmt.Println(sp.age) // 50
sp.age = 51
fmt.Println(sp.age) // 51
}
Go 切片(Slice)
Go 语言切片是对数组的抽象。
Go 数组的长度不可改变,在特定场景中这样的集合就不太适用。
Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
可以声明一个未指定大小的数组来定义切片:
var identifier [] type
使用 make()
函数来创建指定 初始长度 len
的切片:
var slice1 [] type = make([]type, len)
slice2 := make([]type, len)
var slice3 [] type = make([]type, len, capacity) // capacity 为容量,为可选参数
切片初始化:
func main() {
// 直接初始化切片,[]表示是切片类型,{1,2,3}初始化值依次是1,2,3。其 cap=len=3
s1 :=[] int {1,2,3}
fmt.Println(s1) // [1 2 3]
// 定义一个数组
arr := [10] int {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 初始化切片,是数组 arr 的引用
s2 := arr[:]
fmt.Println(s2) // [1 2 3 4 5 6 7 8 9 10]
// 将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片
s3 := arr[1 : 5]
fmt.Println(s3) // [2 3 4 5]
// 缺省 endIndex 时将表示一直到 arr 的最后一个元素
s4 := arr[1 : ]
fmt.Println(s4) // [2 3 4 5 6 7 8 9 10]
// 缺省 startIndex 时将表示从 arr 的第一个元素开始
s5 := arr[ : 5]
fmt.Println(s5) // [1 2 3 4 5]
}
- 切片是可索引的,并且可以由
len()
方法获取长度。 - 切片提供了计算容量的方法
cap()
可以测量切片最长可以达到多少。 - 一个切片在未初始化之前默认为
nil
,长度为0
。 - 如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来,使用
append()
和copy()
方法。示例:
func main() {
var numbers [] int
fmt.Println(numbers) // []
numbers = append(numbers, 1)
fmt.Println(numbers) // [1]
numbers = append(numbers, 2, 3, 4)
fmt.Println(numbers) // [1 2 3 4]
// 创建切片 numbers1 是之前切片的两倍容量
numbers1 := make([]int, len(numbers), (cap(numbers))*2)
copy(numbers1, numbers)
fmt.Println(numbers1) // [1 2 3 4]
}
Go 范围(Range)
Go 语言中 range
关键字用于 for
循环中迭代数组(array
)、切片(slice
)、通道(channel
)或集合(map
)的元素。在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对的 key 值。
示例:
func main() {
// 定义一个数组
nums := [] int{2, 3, 4}
sum := 0
// 使用 range 将传入 index 和值两个变量
for i, num := range nums {
fmt.Println("index = ", i, "value = ", num)
}
// 求和,使用空白标识符"_"来忽略索引
for _, num := range nums {
sum += num
}
fmt.Println("sum:", sum) // 9
// 用在 map 的键值对上。
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}
// 用来枚举 Unicode 字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。
for i, c := range "go" {
fmt.Println(i, c)
}
}
Go Map(集合)
Map 的定义:
// 声明变量,默认 map 是 nil
var map_variable map[key_data_type]value_data_type
// 使用 make 函数
map_variable := make(map[key_data_type]value_data_type)
示例:
func main() {
m := make(map[string]int)
m["k1"] = 7
m["k2"] = 13
fmt.Println("map:", m) // map[k1:7 k2:13]
v1 := m["k1"]
fmt.Println("v1: ", v1) // v1: 7
fmt.Println("len:", len(m)) // len: 2
delete(m, "k2")
fmt.Println("map:", m) // map: map[k1:7]
_, prs := m["k2"]
fmt.Println("prs:", prs) // prs: false
}
Go 类型转换
类型转换:type_name(expression)
示例:
func main() {
var sum int = 17
var count int = 5
var mean float32
mean = float32(sum)/float32(count)
fmt.Printf("mean 的值为: %f\n", mean) // 3.400000
}
Go 接口
接口的定义:
type interface_name interface {
method_name1 [return_type]
method_name2 [return_type]
method_name3 [return_type]
...
method_namen [return_type]
}
示例:
import "fmt"
import "math"
type geometry interface {
area() float64
perim() float64
}
type rect struct {
width, height float64
}
type circle struct {
radius float64
}
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}
measure(r)
measure(c)
}
Go 错误处理
通过内置的错误接口 error
,定义:
type error interface {
Error() string
}
我们可以在编码中通过实现 error
接口类型来生成错误信息。
函数通常在最后的返回值中返回错误信息。使用 errors.New
可返回一个错误信息。
示例:
import "fmt"
import "errors"
func f1(arg int) (int, error) {
if arg == 42 {
return -1, errors.New("can't work with 42")
}
return arg + 3, nil
}
type argError struct {
arg int
prob string
}
func (e *argError) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.prob)
}
func f2(arg int) (int, error) {
if arg == 42 {
return -1, &argError{arg, "can't work with it"}
}
return arg + 3, nil
}
func main() {
for _, i := range []int{7, 42} {
r, e := f1(i)
if e != nil {
fmt.Println("f1 failed:", e)
} else {
fmt.Println("f1 worked:", r)
}
}
for _, i := range []int{7, 42} {
r, e := f2(i)
if e != nil {
fmt.Println("f2 failed:", e)
} else {
fmt.Println("f2 worked:", r)
}
}
}
Go 并发
通过 go
关键字来开启 goroutine
。
goroutine
是轻量级线程,goroutine
的调度是由 Golang 运行时进行管理的。
goroutine
定义:
go 函数名( 参数列表 )
示例:
import "fmt"
import "time"
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world") // go 语句开启一个新的运行期线程, 即 goroutine
say("hello")
}
输出:
hello
world
hello
world
hello
world
hello
world
hello
通道(channel)可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
操作符 <-
用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
// 把 v 发送到通道 ch
ch <- v
// 从 ch 接收数据,并把值赋给 v
v := <- ch
示例:
func f(messages chan string) {
messages <- "ping"
}
func main() {
// 使用 chan 关键字创建通道
messages := make(chan string)
// go 语句开启一个新的运行期线程, 即 goroutine
go f(messages)
// 将 messages 传到主线程
msg := <- messages
fmt.Println(msg) // ping
}
更多关于通道的示例:
- Channel Buffering
- Channel Synchronization
- Channel Directions
- Non-Blocking Channel Operations
- Closing Channels
- Range over Channels
字符串格式化
参考:Go by Example: String Formatting
package main
import "fmt"
import "os"
type point struct {
x, y int
}
func main() {
p := point{1, 2}
fmt.Printf("%v\n", p) // {1 2}
// The `%+v` variant will include the struct's field names. 打印字段名
fmt.Printf("%+v\n", p) // {x:1 y:2}
// The `%#v` variant prints a Go syntax representation of the value.
fmt.Printf("%#v\n", p) // main.point{x:1, y:2}
// To print the type of a value, use `%T`. 打印类型
fmt.Printf("%T\n", p) // main.point
// Formatting booleans is straight-forward.
fmt.Printf("%t\n", true) // true
// Use `%d` for standard, base-10 formatting. 打印十进制
fmt.Printf("%d\n", 123) // 123
// This prints a binary representation. 打印二进制
fmt.Printf("%b\n", 14) // 1110
// `%x` provides hex encoding. 打印十六进制
fmt.Printf("%x\n", 456) // 1c8
// This prints the character corresponding to the given integer. (ASCII Table)
fmt.Printf("%c\n", 33) // !
// 打印浮点数
fmt.Printf("%f\n", 78.9) // 78.900000
// 科学计数法
fmt.Printf("%e\n", 123400000.0) // 1.234000e+08
fmt.Printf("%E\n", 123400000.0) // 1.234000E+08
// 打印字符串
fmt.Printf("%s\n", "\"string\"") // "string"
// To double-quote strings as in Go source, use `%q`. 自动双引号包裹
fmt.Printf("%q\n", "\"string\"") // "\"string\""
// `%x` renders the string in base-16, with two output characters per byte of input.
fmt.Printf("%x\n", "hex this") // 6865782074686973
// To print a representation of a pointer, use `%p`. 打印指针
fmt.Printf("%p\n", &p) // 0x414020
// 指定数字宽度,默认右对齐
fmt.Printf("|%6d|%6d|\n", 12, 345) // | 12| 345|
// 指定数字小数点位数
fmt.Printf("|%6.2f|%6.2f|\n", 1.2, 3.45) // | 1.20| 3.45|
// 指定左对齐
fmt.Printf("|%-6.2f|%-6.2f|\n", 1.2, 3.45) // |1.20 |3.45 |
// 指定字符串宽度
fmt.Printf("|%6s|%6s|\n", "foo", "b") // | foo| b|
// 指定左对齐
fmt.Printf("|%-6s|%-6s|\n", "foo", "b") // |foo |b |
// Sprintf 格式化字符串并返回,但是不会打印到 os.Stdout
s := fmt.Sprintf("a %s", "string")
fmt.Println(s) // a string
// 使用 fmt.Fprintf 打印到 io.Writers,而不是 os.Stdout
fmt.Fprintf(os.Stderr, "an %s\n", "error") // an error
}
参考:
Go 语言教程
Go by Example