大部分的例子引用于 A Tour of Go,非常棒的Go语言介绍。
如果你是Go的新手,请认真阅读Tour。
- 强类型语言
- 静态类型
- 语法类似于C (更少的括号和分号) 和 Oberon-2的struct结构体
- 编译为本地代码(没有java虚拟机)
- 没有类,但有带方法的结构
- 接口
- 没有实现继承,只有内嵌组合就够了
- 函数是一等公民
- 函数可以返回多值
- 有闭包
- 有指针,但没有指针的运算
- 内置的并发原语:协程和通道
文件 hello.go
:
package main
import "fmt"
func main() {
fmt.Println("Hello Go")
}
$ go run hello.go
运算符号 | 描述 |
---|---|
+ |
加 |
- |
减 |
* |
乘 |
/ |
商 |
% |
取余 |
& |
与 |
| |
或 |
^ |
位异或 |
&^ |
位与非 |
<< |
左移 |
>> |
右移 |
运算符 | 描述 |
---|---|
== |
等于 |
!= |
不等于 |
< |
小于 |
<= |
小于等于 |
> |
大于 |
>= |
大于等于 |
运算符 | 描述 |
---|---|
&& |
逻辑与 |
|| |
逻辑或 |
! |
逻辑非 |
运算符 | 描述 |
---|---|
& |
对象地址 / 创建指针对象 |
* |
指针解引用 |
<- |
发送 / 接收 运算符 (可查看文章下面的Channel) |
具体类型需要写在识别符之后!
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"
// 一个简单的函数
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和s会被return
return
}
var x, str = returnMulti2()
func main() {
// 赋值函数给一个变量
add := func(a, b int) int {
return a + b
}
// 用变量名字调用函数
fmt.Println(add(3, 4))
}
// 闭包,词法作用域:其他函数可以访问在scope函数里定义的值
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
}
// 闭包:不会改变函数外的变量,而是重新声明它们!
func outer() (func() int, int) {
outer_var := 2
inner := func() int {
outer_var += 99 // 试图在作用域中改变outer_var
return outer_var // => 101 (outter_var是重新声明的变量
// 变量只能在函数内部可使用)
}
return inner, outer_var // => 101, 2 (outer_var 一直是2, 没有被闭包内部改变!)
}
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
}
// 使用 ... 可以在最后一个参数的类型名之前替代0个或更多的参数。
// 这个函数跟其他函数一样可以被调用并且可以传递更多的参数
func adder(args ...int) int {
total := 0
for _, v := range args { // Iterates over the arguments whatever the number.
total += v
}
return total
}
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8的别名
rune // int32的别名 ~= 一个字符(Unicode编码点)-非常(霸道)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
包里 - 约定: 包名 == import路径的最后一个词名
- 大写字母标识符: 公开的 (外部包可引用的)
- 小写字母标识符: 私有的(外部包不可引用的)
func main() {
// 基础的
if x > 0 {
return x
} 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)
}
// switch 语句
switch operatingSystem {
case "darwin":
fmt.Println("Mac OS Hipster")
// cases会自动break, 而不是通过defaultbreak
case "linux":
fmt.Println("Linux Geek")
default:
// Windows, BSD, ...
fmt.Println("Other")
}
// 与for和if一样,可以在switch值之前加一个赋值语句
switch os := runtime.GOOS; os {
case "darwin": ...
}
// 可以在 switch cases中做比较
number := 42
switch {
case number < 42:
fmt.Println("Smaller")
case number == 42:
fmt.Println("Equal")
case number > 42:
fmt.Println("Greater")
}
// 在case列表中可以用多个用逗号分隔
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
var b = a[1:4] // 索引从1到3的切片
var b = a[:3] // 省略前面的索引意指0
var b = a[3:] // 省略后面的索引意指len(a)
a = append(a,17,3) // 在切片a后面追加item
c := append(a,b...) // 连接切片a和b
// 用make创建切片
a = make([]byte, 5, 5) // 第一个参数代表长度,第二个代表容量
a = make([]byte, 5) // 容量是可选参数
// 从数组里面创建切片
x := [3]string{"Лайка", "Белка", "Стрелка"}
s := x[:] // 一个切片引用了x的存储容量
len(a)
可以算出一个数组或切片的长度,它是内建函数,而不是一个数组的属性/方法
// 对一个数组/一个切片的循环
for i, e := range a {
// i 是索引, e 是元素
}
// 如果只要e:
for _, e := range a {
// e 是元素
}
// 如果只需要索引
for i := range a {
}
// 在Go1.4之前,如果你不使用i和e,会得到一个编译错误。
// Go1.4引入了一种无变量形式,就可以这样使用
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
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
这里没有类,只有结构体,结构体可以有方法
// 结构体是一种类型. 代表字段的集合
// 声明
type Vertex struct {
X, Y int
}
// 创建
var v = Vertex{1, 2}
var v = Vertex{X: 1, Y: 2} // 通过key-value创建一个结构体实体
var v = []Vertex{{1,2},{5,2},{5,5}} // 初始化结构体 切片
// 访问字段
v.X = 4
// 您可以在struct中声明方法。想要声明的结构体方法(接收类型)在func关键字和
// 方法名之间。结构体会复制在每个方法()调用之前
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// 调用方法
v.Abs()
// 对于改变变量的方法,需要通过使用结构体指针作为类型。这样在调用过程中结构体的值将不会被拷贝【所谓的值拷贝】
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 是一个 Vertex
q := &p // q 是指向 Vertex的一个指针
r := &Vertex{1, 2} // r 也是指向 Vertex的一个指针
// Vertex 的指针类型是 *Vertex
var s *Vertex = new(Vertex) // new 创建了一个指针指向了新的结构体实体
// 接口声明
type Awesomizer interface {
Awesomize() string
}
// 类型没有声明实现的接口
type Foo struct {}
// 相反,如果它们实现了所有必需的方法,类型就会隐式地满足接口
func (foo Foo) Awesomize() string {
return "Awesome!"
}
在Go中没有子类,而是interface和struct的内嵌组合。
// ReadWriter 的实现必须满足Reader和Writer
type ReadWriter interface {
Reader
Writer
}
// Server 公开了Logger所有的方法
type Server struct {
Host string
Port int
*log.Logger
}
// 按照通常的方式初始化组合类型
server := &Server{"localhost", 80, log.New(...)}
// 可以使用在嵌入式结构中实现的方法
server.Log(...) // 调用 server.Logger.Log(...)
// 嵌入式结构的字段名是它的类型名称(在Logger中)
var logger *log.Logger = server.Logger
没有异常处理 . 可能产生错误的函数只是声明了 Error
类型的返回值. 下面是 Error
的interface:
type error interface {
Error() string
}
下面方法可能会返回一个err:
func doStuff() (int, error) {
}
func main() {
result, err := doStuff()
if err != nil {
// 处理错误
} else {
// 无错误,使用正确结果
}
}
Goroutines 是轻量级的线程 (被Go操纵管理,而不是操作系统线程). go f(a, b)
开启了一个新的运行f函数的goroutine (f
是个函数).
// 只是一个函数(会被作为一个goroutine开始执行)
func doStuff(s string) {
}
func main() {
// 用一个已命名的函数开启goroutine
go doStuff("foobar")
// 用一个匿名开启goroutine
go func (x int) {
// 方法体放在这里
}(42)
}
ch := make(chan int) // 创建一个int类型的channel
ch <- 42 // 给channel ch发送一个值
v := <-ch // 从ch接受一个值
// 无缓冲的channle会阻塞. 没有值可读的时候会阻塞,写也是,直到有值可读。
// 创建一个带缓冲的channel,如果写的未读值小于buffer大小,那么写入缓冲通道就不会阻塞。
ch := make(chan int, 100)
close(ch) // 关闭通道(只有发送方能关闭)
// 如果channel已经关闭,可以从通道中读取和测试
v, ok := <-ch
// 如果ok是false,说明channel已经关闭
// 一直从channel里读直到关闭
for i := range ch {
fmt.Println(i)
}
// 在多个通道操作上select ,如果一个没有阻塞,与之对应的case将被执行
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")
}
}
-
给一个nil的channel发送永远会阻塞
var c chan string c <- "Hello, World!" // fatal error: all goroutines are asleep - deadlock!
-
从一个nil的channel接受也会永远阻塞
var c chan string fmt.Println(<-c) // fatal error: all goroutines are asleep - deadlock!
-
给一个已关闭的channel发送会panic
var c = make(chan string, 1) c <- "Hello, World!" close(c) c <- "Hello, Panic!" // panic: send on closed channel
-
从一个关闭的channel接受会立刻返回一个0值
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, 你好, नमस्ते, Привет, ᎣᏏᏲ") // 基本的 print, 会自动换行
p := struct { X, Y int }{ 17, 2 }
fmt.Println( "My point:", p, "x coord=", p.X ) // print 结构体,整型等等
s := fmt.Sprintln( "My point:", p, "x coord=", p.X ) // print 字符串变量
fmt.Printf("%d hex:%x bin:%b fp:%f sci:%e",17,17,17,17.0,17.0) // c-ish 格式化
s2 := fmt.Sprintf( "%d %f", 17, 17.0 ) // 格式化输出字符串变量
hellomsg := `
"Hello" in Chinese is 你好 ('Ni Hao')
"Hello" in Hindi is नमस्ते ('Namaste')
` // 多行的字符串, 用``
package main
import (
"fmt"
"net/http"
)
// 定义了一个响应体struct
type Hello struct{}
// 让Hello去实现ServeHTTP 方法 (在 interface 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)
}
// 这里是 http.ServeHTTP的方法署名:
// type Handler interface {
// ServeHTTP(w http.ResponseWriter, r *http.Request)
// }