变量和类型
变量
1
2
3
4var i int //声明不赋值,默认为0
var i int = 1 //声明时赋值
var i = 1 //声明时赋值
i := 1 //声明时赋值基本类型
空值:nil
整型:int(与操作系统有关,32位还是64位),int8,int16,int32,int64,uint8,uint16… ,默认为0
浮点型:float32,float64
布尔型:bool(ture, false),默认为false
字节:byte
字符串:string,默认为””
1
2
3
4
5var a int8 = 8
var b float32 = 3.2
var c byte = 'b'
var d string = "string"
var e bool = true1
2
3
4
5
6
7z := "中文"
fmt.Println(z[1], string(z[1]), len(z)) //184 ¸ 6
//go中字符串默认使用utf-8来存储。字符串是以 byte 数组形式保存的,类型是 uint8,占1个 byte,打印时需要用 string 进行类型转换,否则打印的是编码值。也正因为存储是以byte,uint8存储所以 z[1] 的值不是”文“,可以使用rune来处理。
fmt.Println(reflect.TypeOf(z[1]).Kind()) // reflect.TypeOf展示数据类型,uint8
runeArr := []rune(z)
fmt.Println(reflect.TypeOf(runeArr[1]).Kind()) // int32
fmt.Println(runeArr[1], string(runeArr[1])) //25991 文
数组和切片
数组(array):长度不可变
1
2
3
4
5
6
7
8
9
10
11
12//声明
var arr1 [3]int //一维
var arr2 [3][3]int //二维
//初始化
arr := [3]int{1,2,3}
//var arr = [3]int{1,2,3}
//赋值操作
arr[0] = 4
//遍历
for i := 0; i < len(arr); i++ {
fmt.Println(arr[i])
}切片(slice):数组的抽象
1
2
3
4
5
6
7
8
9
10
11
12
13//声明
slice2 := make([]float32, 3, 5) // [0 0 0] 长度为3容量为5的切片
fmt.Println(len(slice2), cap(slice2)) // 3 5
//赋值操作
// 添加元素,切片容量可以根据需要自动扩展
slice2 = append(slice2, 1, 2, 3, 4) // [0, 0, 0, 1, 2, 3, 4] 容量不够时,会自动扩容
fmt.Println(len(slice2), cap(slice2)) // 7 12
// 子切片 [start, end)
sub1 := slice2[3:] // [1 2 3 4]
sub2 := slice2[:3] // [0 0 0]
sub3 := slice2[1:4] // [0 0 1]
// 合并切片
combined := append(sub1, sub2...) // [1, 2, 3, 4, 0, 0, 0] sub2...为切片解构写法,将切片解构为N个独立的单元
字典(map)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//声明
m1 := make(map[string]string)
//初始化
m2 := map[string]string{
"key1": "value1",
"key2": "value2",
}
//操作
fmt.Println(m2["key1"])
m2["key2"] = "value"
fmt.Println(m2["key2"])
//遍历
for key,value := range m2{
fmt.Println(key, value)
}指针:指保存了变量的内存地址,使用符号
*
,对一个已经存在的变量,使用&
获取该变量的地址。go的参数都是值传递,当需要引用传递时要使用指针。1
2
3
4str := "go"
var p *string = &str // p 是指向 str 的指针
*p = "hello"
fmt.Println(str) // hello 修改了 p,str 的值也发生了改变
流程控制(if,for,switch)
1 | func iterator(start, end int) int { |
函数、结构体、方法和接口
函数(func):使用关键字
func
,参数可以有多个,返回值也支持有多个1
2
3
4//在 Go 中,首字母大写的名称是被导出的(类似public)。Swap 和 SWAP 都是被导出的名称。名称 swap 是不会被导出的(类似private)
func swap(x, y string) (string, string) {
return y, x
}结构体(struct)
1
2
3
4
5//定义
type Student struct {
name string
age int
}方法
1
2
3
4
5
6
7
8
9
10
11
12//在结构体Student 添加方法(s *Student)
func (s *Student) getName() string {
return s.name
}
//操作
func main() {
//age未被赋值,默认值 0
stu := &Student{
name: "zhangsan",
}
fmt.Println(stu.getName) // zhangsan
}接口(interface):不需要显式地声明实现了哪一个接口,只需要直接实现该接口对应的方法即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19type Person interface {
getName() string
}
func main() {
//实例化 Student后,强制类型转换为接口类型 Person
var p Person = &Student{
name: "lisi",
age: 18,
}
fmt.Println(p.getName()) // lisi
s Student = p.(Student) //显示转换,将Person转换为Student
//空接口interface{},可以存储任何类型的数据
m := make(map[string]interface{})
m["name"] = "Tom"
m["age"] = 18
m["scores"] = [3]int{98, 99, 85}
fmt.Println(m) // map[age:18 name:Tom scores:[98 99 85]]
}
并发
关键字go
,在需要执行的方法函数前添加go
。
同一个Goroutine线程内部,程序是顺序一致性的。但是不同的Goroutine之间,并不满足顺序一致性内存模型。
sync
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22import (
"fmt"
"sync"
"time"
)
//sync.WaitGroup
var wg sync.WaitGroup
//执行下载操作
func download(url string) {
fmt.Println("start to download", url)
time.Sleep(time.Second) // 模拟耗时操作
wg.Done() //减去一个计数
}
func main() {
for i := 0; i < 3; i++ {
wg.Add(1) //为 wg 添加一个计数
go download("a.com/" + string(i+'0')) //开启新线程去执行函数
}
wg.Wait() //等待所有的协程执行结束
fmt.Println("Done!")
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import (
"sync"
)
//sync.Mutex 加锁
var total struct {
sync.Mutex
value int
}
func worker(wg *sync.WaitGroup) {
defer wg.Done()
for i := 0; i <= 100; i++ {
//加锁
total.Lock()
total.value += i
total.Unlock()
}
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go worker(&wg)
go worker(&wg)
wg.Wait()
fmt.Println(total.value)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import (
"sync"
"sync/atomic"
)
//sync/atomic
var total uint64
func worker(wg *sync.WaitGroup) {
defer wg.Done()
var i uint64
for i = 0; i <= 100; i++ {
//原子操作
atomic.AddUint64(&total, i)
}
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
go worker(&wg)
go worker(&wg)
wg.Wait()
}channel
对于从无缓冲Channel,进行的接收发生在对该Channel进行的发送完成之前。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30import (
"fmt"
"math/rand"
"time"
)
func boring(msg string, c chan string) {
for i := 0; ; i++ {
// 发送结果到channel中
// 会等待接收端就绪
c <- fmt.Sprintf("%s %d", msg, i)
time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
}
}
func main() {
// 定义channel
c := make(chan string)
// 调用boring函数
go boring("boring!", c)
for i := 0; i < 5; i++ {
// <-c 从boring函数读取值
// <-c 等待获取值
fmt.Printf("You say: %q\n", <-c)
}
//当接收到5条消息就打印
fmt.Println("You're boring. I'm leaving")
}对于带缓冲的Channel,对于Channel的第
K
个接收完成操作发生在第K+C
个发送操作完成之前,其中C
是Channel的缓存大小。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//根据控制Channel的缓存大小来控制并发执行的Goroutine的最大数目
var limit = make(chan int, 3)
func main() {
for _, w := range work {
go func() {
limit <- 1
w()
<-limit
}()
}
//select{}一个空的管道选择语句,该语句会导致main线程阻塞,从而避免程序过早退出。
//还有for{}、<-make(chan int)等诸多方法可以达到类似的效果。因为main线程被阻塞了,如果需要程序正常退出的话可以通过调用os.Exit(0)实现。
select{}
}