参考文档
1.https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/10.5.md
2.http://tour.studygolang.com/welcome/1
package main
import "fmt"
func main() {
fmt.Println("hello, world")
}
1.1 包的概念、导入与可见性
1>每个程序都由包(通常简称为pkg)的概念组成,可以使用自身的包或者从其它包中导入内容
2>每个 Go 文件都属于且仅属于一个包。一个包可以由许多以 .go 为扩展名的源文件组成
3>必须在源文件中非注释的第一行指明这个文件属于哪个包,如:package main,所有的包名都应该使用小写字母
引入多个包
import "fmt"
import "os"
或
import "fmt"; import "os"
或
import (
"fmt"
"os"
)
可见性规则
1>当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public)
2>标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 private )
1.2. 函数
符合规范的函数一般写成如下的形式:
func functionName(parameter_list) (return_value_list) {
…
}
注:1.函数需要被外部包调用的时候使用大写字母开头,遵循骆驼命名法。
2.左大括号 { 必须与方法的声明放在同一行,这是编译器的强制规定,否则你在使用 gofmt 时就会出现错误提示
`build-error: syntax error: unexpected semicolon or newline before {`
1.3 函数返回值
函数可以拥有多返回值,返回类型之间需要使用逗号分割,并使用小括号 () 将它们括起来
func FunctionName (a typea, b typeb) (t1 type1, t2 type2)
1.4 注释
以 // 开头的单行注释
多行注释也叫块注释,均已以 /* 开头,并以 */ 结尾
1.5 基本类型
int、float、bool、string;结构化的(复合的),如:struct、array、slice、map、channel;只描述类型行为,如:interface
结构化的类型没有真正的值,它使用 nil 作为默认值
1.6 类型转换
Go 语言不存在隐式类型转换,必须显式说明
a := 5.0
b := int(a)
1.5 变量
使用 var 关键字:var identifier type
var a, b *int
var a int
var b bool
var str string
或
var (
a int
b bool
str string
)
赋值
a = 11
var c int = 12
:= 赋值操作符初始化
可以在变量的初始化时省略变量的类型而由系统自动推断
a := 50
1.6 init 函数
1>一类非常特殊的函数,它不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 main 函数高
2>每个源文件都只能包含一个 init 函数。初始化总是以单线程执行,并且按照包的依赖关系顺序执行
可能的用途: 变量初始化 init.go
package trans
import "math"
var Pi float64
func init() {
Pi = 4 * math.Atan(1) // init() function computes Pi
}
1.7 指针
Go 语言为程序员提供了控制数据结构的指针的能力,不允许指针运算->移动指针位置
pointer.go
var i1 = 5
fmt.Printf("An integer: %d, it's location in memory: %p\n", i1, &i1)
-----------------------
可能输出
An integer: 5, its location in memory: 0x6b0820
定义一个指针变量
var intP *int
*intP,将得到这个指针指向地址上所存储的值
2.1 按值传递、按引用传递
1>默认使用按值传递来传递参数,也就是传递参数的副本。函数接收参数副本之后,在使用变量的过程中可能对副本的值进行更改,但不会影响到原来的变量
2> 如果函数直接修改参数的值,而不是对参数的副本进行操作,需要按引用传递,即变量名前面添加&符号
注:函数调用时,像切片(slice)、字典(map)、接口(interface)、通道(channel)这样的引用类型都是默认使用引用传递(即使没有显式的指出指针)
2.2 传递变长参数
函数的最后一个参数是采用 …type 的形式,那么这个函数就可以处理一个变长的参数,这个长度可以为0
func myFunc(a, b, arg ...int) {}
2.3 defer 函数
defer 语句会将函数推迟到外层函数返回之后执行
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用
使用场景:一般用于释放某些已分配的资源
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
---------------------
hello
world
2.4 回调
函数可以作为其它函数的参数进行传递
package main
import (
"fmt"
)
func main() {
callback(1, Add)
}
func Add(a, b int) {
fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
}
func callback(y int, f func(int, int)) {
f(y, 2) // this becomes Add(1, 2)
}
-----------------------------------
The sum of 1 and 2 is: 3
2.5 闭包
匿名函数被称之为闭包
3. 数组、切片、Map
3.1. 数组的长度是其类型的一部分,因此数组不能改变大小
primes := [6]int{2, 3, 5, 7, 11, 13}
3.2. 切片:
切片(slice)是对数组一个连续片段的引用,切片则为数组元素提供动态大小
切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔
primes := [6]int{2, 3, 5, 7, 11, 13} -->数组
var s []int = primes[1:4] ->切片
3.3 maek 创建切片
a := make([]int, 5)
3.3 切片文法
切片文法类似于没有长度的数组文法
q := []int{2, 3, 5, 7, 11, 13}
3.4 for - range
for 循环的 range 形式可遍历切片或映射
for ix, value := range slice1 {
...
}
当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本
package main
import "fmt"
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
}
--------------------
2**0 = 1
2**1 = 2
2**2 = 4
3.5 MAP使用
var map1 map[keytype]valuetype
var map1 map[string]int
测试键值对是否存在及删除元素
val1, isPresent = map1[key1]
isPresent 返回一个 bool 值:如果 key1 存在于 map1,val1 就是 key1 对应的 value 值,并且 isPresent为true;如果 key1 不存在,val1 就是一个空值,并且 isPresent 会返回 false
如果你只是想判断某个 key 是否存在而不关心它对应的值
_, ok := map1[key1] // 如果key1存在则ok == true,否则ok为false
与if 混合使用
if _, ok := map1[key1]; ok {
// ...
}
for-range 的map配套用法
for key, value := range map1 {
...
}
如果只关心值
for _, value := range map1 {
...
}
如果只想获取 key,你可以这么使用:
for key := range map1 {
fmt.Printf("key is: %d\n", key)
}
4.1 if-else结构
Go 完全省略了 if、switch 和 for 结构中条件语句两侧的括号,相比 Java、C++ 和 C# 中减少了很多视觉混乱的因素,同时也使你的代码更加简洁。
if condition {
// do something
} else {
// do something
}
关键字 if 和 else 之后的左大括号 { 必须和关键字在同一行,如果你使用了 else-if 结构,则前段代码块的右大括号 } 必须和 else-if 关键字在同一行。这两条规则都是被编译器强制规定的。
非法的 Go 代码:
if x{
}
else { // 无效的
}
当 if 结构内有 break、continue、goto 或者 return 语句时,Go 代码的常见写法是省略 else 部分。无论满足哪个条件都会返回 x 或者 y 时,一般使用以下写法:
if condition {
return x
}
return y
4.2 switch结构
相比较 C 和 Java 等其它语言而言,Go 语言中的 switch 结构使用上更加灵活。它接受任意形式的表达式:
switch var1 {
case val1:
...
case val2:
...
default:
...
}
4.3 for结构
4.3.1 基于计数器的迭代
基本形式为
for 初始化语句; 条件语句; 修饰语句 {}
4.3.2 基于条件判断
基本形式为
for 条件语句 {}
4.3.3 无限循环
条件语句被省略
4.3.4 这里介绍 Go 特有的一种的for-range迭代结构
for ix, val := range coll { }
要注意的是,val 始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值
4.4 break和continue
break被用于任何形式的for循环以及switch或select语句,continue只能被用于for循环
5.1 结构体定义的一般方式如下:
type identifier struct {
field1 type1
field2 type2
...
}
5.2 使用 new 函数给一个新的结构体变量分配内存,它返回指向已分配内存的指针:var t *T = new(T)
访问结构体变量或赋值
structname.fieldname
structname.fieldname = value
5.3 匿名字段和内嵌结构体
结构体可以包含一个或多个 匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型就是字段的名字。匿名字段本身可以是一个结构体类型,即 结构体可以包含内嵌结构体
package main
import "fmt"
type A struct {
ax, ay int
}
type B struct {
A
bx, by float32
}
func main() {
b := B{A{1, 2}, 3.0, 4.0}
fmt.Println(b.ax, b.ay, b.bx, b.by)
fmt.Println(b.A)
}
--------------------------------------------------
1 2 3 4
{1 2}
5.4 方法
结构体就像是类的一种简化形式,类似于OOP中有方法的实现,Go 方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量。因此方法是一种特殊类型的函数
定义方法的一般格式如下:
func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }
示例:method .go
package main
import "fmt"
type TwoInts struct {
a int
b int
}
func main() {
two1 := new(TwoInts)
two1.a = 12
two1.b = 10
fmt.Printf("The sum is: %d\n", two1.AddThem())
fmt.Printf("Add them to the param: %d\n", two1.AddToParam(20))
two2 := TwoInts{3, 4}
fmt.Printf("The sum is: %d\n", two2.AddThem())
}
func (tn *TwoInts) AddThem() int {
return tn.a + tn.b
}
func (tn *TwoInts) AddToParam(param int) int {
return tn.a + tn.b + param
}
---------------------------------------------
The sum is: 22
Add them to the param: 42
The sum is: 7
内嵌类型的方法和继承
当一个匿名类型被内嵌在结构体中时,匿名类型的可见方法也同样被内嵌,这在效果上等同于外层类型继承了这些方法
示例:method3.go
package main
import (
"fmt"
"math"
)
type Point struct {
x, y float64
}
func (p *Point) Abs() float64 {
return math.Sqrt(p.x*p.x + p.y*p.y)
}
type NamedPoint struct {
Point
name string
}
func main() {
n := &NamedPoint{Point{3, 4}, "Pythagoras"}
fmt.Println(n.Abs()) // 打印5
}
type Namer interface {
Method1(param_list) return_type
Method2(param_list) return_type
...
}
示例 interfaces.go:
package main
import "fmt"
type Shaper interface {
Area() float32
}
type Square struct {
side float32
}
func (sq *Square) Area() float32 {
return sq.side * sq.side
}
func main() {
sq1 := new(Square)
sq1.side = 5
var areaIntf Shaper
areaIntf = sq1
// shorter,without separate declaration:
// areaIntf := Shaper(sq1)
// or even:
// areaIntf := sq1
fmt.Printf("The square has area: %f\n", areaIntf.Area())
}
注:接口变量包含一个指向 Square 变量的引用,通过它可以调用 Square 上的方法 Area(),接口变量里包含了接收者实例的值和指向对应方法表的指针
当前示例即是Go 版本的多态,同一种类型在不同的实例上似乎表现出不同的行为
--------------------------------
The square has area: 25.000000
6.2 接口嵌套
一个接口可以包含一个或多个其他的接口,相当于直接将这些内嵌接口的方法列举在外层接口中一样
type ReadWrite interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type Lock interface {
Lock()
Unlock()
}
type File interface {
ReadWrite
Lock
Close()
}
6.3 反射
反射是用程序检查其所拥有的结构,尤其是类型的一种能力,反射可以在运行时检查类型和变量,例如它的大小、方法和 动态 的调用这些方法
reflect.TypeOf 和 reflect.ValueOf,返回被检查对象的类型和值
示例 reflect1.go
// blog: Laws of Reflection
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
v := reflect.ValueOf(x)
fmt.Println("value:", v)
fmt.Println("type:", v.Type())
fmt.Println("kind:", v.Kind())
fmt.Println("value:", v.Float())
fmt.Println(v.Interface())
fmt.Printf("value is %5.2e\n", v.Interface())
y := v.Interface().(float64)
fmt.Println(y)
}
----------------------------------
type: float64
value: 3.4
type: float64
kind: float64
value: 3.4
3.4
value is 3.40e+00
3.4
package main
import "fmt"
func main() {
fmt.Println("Starting the program")
panic("A severe error occurred: stopping the program!")
fmt.Println("Ending the program")
}
------------------------------------------------
Starting the program
panic: A severe error occurred: stopping the program!
panic PC=0x4f3038
runtime.panic+0x99 /go/src/pkg/runtime/proc.c:1032
runtime.panic(0x442938, 0x4f08e8)
main.main+0xa5 E:/Go/GoBoek/code examples/chapter 13/panic.go:8
main.main()
runtime.mainstart+0xf 386/asm.s:84
runtime.mainstart()
runtime.goexit /go/src/pkg/runtime/proc.c:148
runtime.goexit()
---- Error run E:/Go/GoBoek/code examples/chapter 13/panic.exe with code Crashed
---- Program exited with code -1073741783
7.2 从 panic 中恢复(Recover)
(recover)内建函数被用于从 panic 或 错误场景中恢复:让程序可以从 panicking 重新获得控制权,停止终止过程进而恢复正常执行
使用方式:
recover 只能在 defer 修饰的函数中使用:用于取得 panic 调用中传递过来的错误值,如果是正常执行,调用 recover 会返回 nil
示例: recover.go
func protect(g func()) {
defer func() {
log.Println("done")
// Println executes normally even if there is a panic
if err := recover(); err != nil {
log.Printf("run time panic: %v", err)
}
}()
log.Println("start")
g() // possible runtime-error
}
注:类似于java 等catch操作
go f(x, y, z) 启动一个新的 Go 程并执行
8.1 changel
ch <- v // 将 v 发送至信道 ch。
v := <-ch // 从 ch 接收值并赋予 v。
必须先创建changel
ch := make(chan int)
默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步
8.2 带缓冲区changel
ch := make(chan int, 100)
仅当信道的缓冲区填满后,向其发送数据时才会阻塞。当缓冲区为空时,接受方会阻塞
8.3 range 和 close
发送者可通过 close 关闭一个信道来表示没有需要发送的值了。接收者可以通过为接收表达式分配第二个参数来测试信道是否被关闭:若没有值可以接收且信道已被关闭,那么在执行完
v, ok := <-ch
之后 ok 会被设置为 false
循环 for i := range c 会不断从信道接收值,直到它被关闭
*注意:* 只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引发程序恐慌(panic)。
*还要注意:* 信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再有值需要发送的时候才有必要关闭,例如终止一个 range 循环
8.4 sync.Mutex
Go 标准库中提供了 sync.Mutex 互斥锁类型及其两个方法:Lock 、Unlock
也可以用 defer 语句来保证互斥锁一定会被解锁
package main
import (
"fmt"
"sync"
"time"
)
// SafeCounter 的并发使用是安全的。
type SafeCounter struct {
v map[string]int
mux sync.Mutex
}
// Inc 增加给定 key 的计数器的值。
func (c *SafeCounter) Inc(key string) {
c.mux.Lock()
// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
c.v[key]++
c.mux.Unlock()
}
// Value 返回给定 key 的计数器的当前值。
func (c *SafeCounter) Value(key string) int {
c.mux.Lock()
// Lock 之后同一时刻只有一个 goroutine 能访问 c.v
defer c.mux.Unlock()
return c.v[key]
}
func main() {
c := SafeCounter{v: make(map[string]int)}
for i := 0; i < 1000; i++ {
go c.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(c.Value("somekey"))
}
type struct1 struct {
i1 int
f1 float32
str string
}
继承:
结构体的嵌套模拟继承
type A struct{ //父类
name string
age int
}
type B struct{//子类
a A // 模拟聚合关系 .....}
type C struct{//子类
A // 模拟继承
}
b := B{}
b.a.name
c := C{}
b.name
多态:
接口的所有方法的任何类型都表示隐式实现该接口。类型接口的变量可以保存实现该接口的任何值
package main
import (
"fmt"
)
type Person interface {
SayHello()
}
type Girl struct {
Sex string
}
type Boy struct {
Sex string
}
func (this *Girl) SayHello() {
fmt.Println("Hi, I am a " + this.Sex)
}
func (this *Boy) SayHello() {
fmt.Println("Hi, I am a " + this.Sex)
}
func main() {
g := &Girl{"girl"}
b := &Boy{"boy"}
p := map[int]Person{}
p[0] = g
p[1] = b
for _, v := range p {
v.SayHello()
}
}
-----------------------------------------------
Hi, I am a boy
Hi, I am a gir