Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。
官方网站的文档 https://golang.org/doc/
[TOC]
基本语法
Hello World
File hello.go
:
package main
import "fmt"
func main() {
fmt.Println("Hello Go")
}
$ go run hello.go
操作符
运算
运算符 | 描述 |
---|---|
+ |
加 |
- |
减 |
* |
乘 |
/ |
除 |
% |
除余 |
& |
逻辑与 |
` | ` |
^ |
逻辑异或 |
&^ |
位清除 (取反) |
<< |
左位移 |
>> |
右位移 |
比较
操作符 | 描述 |
---|---|
== |
等于 |
!= |
不等于 |
< |
小于 |
<= |
小于等于 |
> |
大于 |
>= |
大于等于 |
逻辑
操作符 | 描述 |
---|---|
&& |
逻辑与 |
` | |
! |
逻辑否 |
其它
操作符 | 描述 |
---|---|
& |
地址 / 指什 |
* |
解引用指针 |
<- |
发送 /接收操作符 (可以看下面的 '通送' ) |
声明
类型跟在标识符之后!
var foo int // 没有初始化的声明
var foo int = 42 // 初始化值的声明
var foo, bar int = 42, 1302 // 声明和初始化多个变量
var foo = 42 // 类型省略, 会自动识别
foo := 42 // 短写开工, 只在函数体内, 省略了var关键字, 类型通常是严格的
const constant = "This is a constant"
// iota 用来自增, 从0开始
const (
_ = iota
a
b
c = 1 << iota
d
)
fmt.Println(a, b) // 1 2 (0 is skipped)
fmt.Println(c, d) // 8 16 (2^3, 2^4)
函数
// 简单的函数
func functionName() {}
// 带参数的函数 (再强调下, 类型跟在变量标识之后)
func functionName(param1 string, param2 int) {}
// 同一参数类型的多个参数
func functionName(param1, param2 int) {}
// 返回值类型的声明
func functionName() int {
return 42
}
// 可以返回多个值
func returnMulti() (int, string) {
return 42, "foobar"
}
var x, str = returnMulti()
// 用return简化返回多组值
func returnMulti2() (n int, s string) {
n = 42
s = "foobar"
// n and s will be returned
return
}
var x, str = returnMulti2()
函数作为返值和闭包
func main() {
// 将函数赋给变量
add := func(a, b int) int {
return a + b
}
// 使用变量名称去调用方法
fmt.Println(add(3, 4))
}
// 闭包,按词法范围:定义函数时,函数可以访问范围内的值
func scope() func() int{
outer_var := 2
foo := func() int { return outer_var}
return foo
}
func another_scope() func() int{
// 无法编译,因为在此范围内未定义outer_var和foo
outer_var = 444
return foo
}
// Closures
func outer() (func() int, int) {
outer_var := 2
inner := func() int {
outer_var += 99 // 来自外部作用域的external_var发生了改变
return outer_var
}
inner()
return inner, outer_var // 返回内部函数和改变的external_var
}
可变函数
func main() {
fmt.Println(adder(1, 2, 3)) // 6
fmt.Println(adder(9, 9)) // 18
nums := []int{10, 20, 30}
fmt.Println(adder(nums...)) // 60
}
// 通过在最后一个参数的类型名称前使用...,可以指示它接受零个或多个这些参数。
// 该函数与其他函数一样被调用,除了我们可以传递任意数量的参数。
func adder(args ...int) int {
total := 0
for _, v := range args { // 遍历参数不考虑数量
total += v
}
return total
}
内置类型
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32 ~= a character (Unicode code point) - very Viking
float32 float64
complex64 complex128
类型转换
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
// 替代语法
i := 42
f := float64(i)
u := uint(f)
包
- 每个源文件顶部的包声明
- 可执行文件在包
main
中 - 约定:包名称==导入路径的最后一个名字(导入路径
math / rand
=>包rand
) - 大写字母标识符:可导出(在其他包中可见)
- 小写字母标识符:私有(在其他软件包中不可见)
控制结构
If
func main() {
// 最基本的
if x > 10 {
return x
} else if x == 10 {
return 10
} else {
return -x
}
// 您可以在条件之前加一个声明
if a := b + c; a < 42 {
return a
} else {
return a - 42
}
// 在if中断言
var val interface{}
val = "foo"
if str, ok := val.(string); ok {
fmt.Println(str)
}
}
循环
// 只有 `for`, 没有 `while`, 也没有 `until`
for i := 1; i < 10; i++ {
}
for ; i < 10; { // 相尖于while循环
}
for i < 10 { // 如果只有条件,可以省略分号
}
for { // 可以省略条件,相尖于 while (true)
}
// 使用 break/continue 跳出当前循环
// 使用 break/continue 和标记 here 跳出到循环外
here:
for i := 0; i < 2; i++ {
for j := i + 1; j < 3; j++ {
if i == 0 {
continue here
}
fmt.Println(j)
if j == 2 {
break
}
}
}
there:
for i := 0; i < 2; i++ {
for j := i + 1; j < 3; j++ {
if j == 1 {
continue
}
fmt.Println(j)
if j == 2 {
break there
}
}
}
Switch分支
// switch statement
switch operatingSystem {
case "darwin":
fmt.Println("Mac OS Hipster")
// 默认情况下没有失败, 分支自动 break
case "linux":
fmt.Println("Linux Geek")
default:
// Windows, BSD, ...
fmt.Println("Other")
}
// 与for和if一样,在switch值之前可以有一个赋值语句
switch os := runtime.GOOS; os {
case "darwin": ...
}
// 您还可以在switch 分支语名中进行比较
number := 42
switch {
case number < 42:
fmt.Println("Smaller")
case number == 42:
fmt.Println("Equal")
case number > 42:
fmt.Println("Greater")
}
// 分支条件可以用逗号分隔的列表表示
var char byte = '?'
switch char {
case ' ', '?', '&', '=', '#', '+', '%':
fmt.Println("Should escape")
}
数组,切片,范围
数组
var a [10]int // 声明一个长度为10的int数组。数组长度是该类型的一部分!
a[3] = 42 // 修改值
i := a[3] // 读取值
// 声明和初始化
var a = [2]int{1, 2}
a := [2]int{1, 2} //便捷写法
a := [...]int{1, 2} // 省略号->编译器计算出数组长度
切片
var a []int // 声明一个切片-与数组相似,但是长度未指定
var a = []int {1, 2, 3, 4} // 声明并初始化切片(由隐式给出的数组支持)
a := []int{1, 2, 3, 4} // 便捷写法
chars := []string{0:"a", 2:"c", 1: "b"} // ["a", "b", "c"]
var b = a[lo:hi] // 创建一个切片 (数组维度) 从索引 lo 到 hi-1
var b = a[1:4] // 索引 1 to 3
var b = a[:3] // 缺少低位索引 ,暗指0
var b = a[3:] // 缺少高位索引,暗指 len(a)
a = append(a,17,3) // 切片中增加项目
c := append(a,b...) // 连接切片a和b
// 使用make创建切片
a = make([]byte, 5, 5) // first arg length, second capacity
a = make([]byte, 5) // capacity is optional
// create a slice from an array
x := [3]string{"Лайка", "Белка", "Стрелка"}
s := x[:] // a slice referencing the storage of x
数组和切片中的操作符
len(a)
数组/切片的长度。这是一个内置函数,而不是数组的属性/方法
// loop over an array/a slice
for i, e := range a {
// i is the index, e the element
}
// if you only need e:
for _, e := range a {
// e is the element
}
// ...and if you only need the index
for i := range a {
}
// In Go pre-1.4, you'll get a compiler error if you're not using i and e.
// Go 1.4 introduced a variable-free form, so that you can do this
for range time.Tick(time.Second) {
// do it once a sec
}
映射
var m map[string]int
m = make(map[string]int)
m["key"] = 42
fmt.Println(m["key"])
delete(m, "key")
elem, ok := m["key"] // 测试键“ key”是否存在,如果有就返回它
// map literal
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
// iterate over map content
for key, value := range m {
}
结构体
没有类,只有结构。结构可以有方法。
// 结构体是一种类型。这也是字段的集合
// 声明
type Vertex struct {
X, Y int
}
// 创建
var v = Vertex{1, 2}
var v = Vertex{X: 1, Y: 2} // 通过使用键定义值来创建结构
var v = []Vertex{{1,2},{5,2},{5,5}} // 初始化结构片段
// 访问元素
v.X = 4
// You can declare methods on structs. The struct you want to declare the
// method on (the receiving type) comes between the the func keyword and
// the method name. The struct is copied on each method call(!)
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// 调用方法
v.Abs()
// For mutating methods, you need to use a pointer (see below) to the Struct
// as the type. With this, the struct value is not copied for the method call.
func (v *Vertex) add(n float64) {
v.X += n
v.Y += n
}
匿名的结构体:
使用 map[string]interface{}
更简单安全
point := struct {
X, Y int
}{1, 2}
指针
p := Vertex{1, 2} // p is a Vertex
q := &p // q 为指向 Vertex的指针
r := &Vertex{1, 2} // r 也是指向 Vertex的指针
// Vertex的指针类型为 *Vertex
var s *Vertex = new(Vertex) // 创建一个指向新结构实例的指针
接口
// 接口声明
type Awesomizer interface {
Awesomize() string
}
// 类型不能声明实现接口
type Foo struct {}
// 换言之, 如果类型实现所有必需的方法,则它们隐式满足接口
func (foo Foo) Awesomize() string {
return "Awesome!"
}
嵌入
Go语言没有子类。而是有接口和结构嵌入。
// ReadWriter 的实现必须都满足 Reader 和 Writer
type ReadWriter interface {
Reader
Writer
}
// Logger它有的方法, Server暴露所有的方法
type Server struct {
Host string
Port int
*log.Logger
}
// 初始化嵌入类型的常用方法
server := &Server{"localhost", 80, log.New(...)}
// 通过嵌入式结构实现的方法
server.Log(...) // calls server.Logger.Log(...)
// 嵌入类型的字段名称是其类型名称(在本例中为Logger)
var logger *log.Logger = server.Logger
错误
没有异常处理。可能会产生错误的函数只需声明类型为Error
的附加返回值即可。这是Error
接口:
type error interface {
Error() string
}
A function that might return an error:
func doStuff() (int, error) {
}
func main() {
result, err := doStuff()
if err != nil {
// handle error
} else {
// all is good, use result
}
}
并发
Goroutines
Goroutines 是轻量的线程 (managed by Go, not OS threads). go f(a, b)
会启动一个新的goroutine 并运行函数 f
// 只是一个函数(可以稍后作为goroutine启动)
func doStuff(s string) {
}
func main() {
// 在goroutine中使用命名函数
go doStuff("foobar")
// 在goroutine中使用匿名内部函数
go func (x int) {
// function body goes here
}(42)
}
通道
ch := make(chan int) // 创建一个 int类型的通道
ch <- 42 // 给ch通道发送值
v := <-ch // 接收来自ch的值
// 非缓冲通道阻塞。如果没有可用值,则读取块,直到发生读取为止,写入块。
// 创建一个缓冲通道。如果已写入小于<buffer size>的未读值,则不会阻塞对缓冲通道的写入。
ch := make(chan int, 100)
close(ch) // 关闭频道(只有发送者应该关闭)
// 从通道读取并测试是否已关闭
v, ok := <-ch
// 如果 ok 为false, 频道则已关闭
// 从通道读取直到关闭
for i := range ch {
fmt.Println(i)
}
// 在多个通道操作中选择块,如果一个解除阻塞,则执行相应的情况
func doStuff(channelOut, channelIn chan int) {
select {
case channelOut <- 42:
fmt.Println("We could write to channelOut!")
case x := <- channelIn:
fmt.Println("We could read from channelIn")
case <-time.After(time.Second * 1):
fmt.Println("timeout")
}
}
Channel Axioms
- 发送到 nil通道,一直阻塞下去
var c chan string
c <- "Hello, World!"
// fatal error: all goroutines are asleep - deadlock!
- 从nil通道读到, 一直阻塞下去
var c chan string
fmt.Println(<-c)
// fatal error: all goroutines are asleep - deadlock!
- 发送到已关闭的通道
var c = make(chan string, 1)
c <- "Hello, World!"
close(c)
c <- "Hello, Panic!"
// panic: send on closed channel
- 来自封闭通道的接收立即返回零值
var c = make(chan int, 2)
c <- 1
c <- 2
close(c)
for i := 0; i < 3; i++ {
fmt.Printf("%d ", <-c)
}
// 1 2 0
打印
fmt.Println("Hello, 你好, नमस्ते, Привет, ᎣᏏᏲ") // 最基本的打印,新行输出
p := struct { X, Y int }{ 17, 2 }
fmt.Println( "My point:", p, "x coord=", p.X ) // print structs, ints, etc
s := fmt.Sprintln( "My point:", p, "x coord=", p.X ) // 打印为字符串
fmt.Printf("%d hex:%x bin:%b fp:%f sci:%e",17,17,17,17.0,17.0) // c风格格式
s2 := fmt.Sprintf( "%d %f", 17, 17.0 ) // 格式化打印到字符串变量
hellomsg := `
"Hello" in Chinese is 你好 ('Ni Hao')
"Hello" in Hindi is नमस्ते ('Namaste')
` // 多行字符串文字,在开头和结尾使用反引号
反射
类型开关
类型开关类似于常规switch语句,但是类型开关中的个案指定类型(不是值),并将这些值与给定接口值所保存的值的类型进行比较。
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
func main() {
do(21)
do("hello")
do(true)
}
代码片断
HTTP 服务端
package main
import (
"fmt"
"net/http"
)
// 声明响应结构体
type Hello struct{}
// 让该类型实现ServeHTTP方法(在接口http.Handler中定义)
func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello!")
}
func main() {
var h Hello
http.ListenAndServe("localhost:4000", h)
}
// Here's the method signature of http.ServeHTTP:
// type Handler interface {
// ServeHTTP(w http.ResponseWriter, r *http.Request)
// }