基础
Go语言是由Google公司于2009年推出的一种开源编程语言,它结合了静态类型语言的安全性和高效性,以及动态类型语言的易读性和易用性。Go语言支持并发编程,具有轻量级线程——Goroutine,并提供了一套完善的并发编程模型,使得编写高并发程序变得更加简单。
Go语言的语法简单、结构清晰、便于阅读,且语法设计上强调可读性和简洁性,尤其适合于大规模的网络服务器应用程序开发。Go语言还拥有强大的标准库和丰富的第三方库,以及开发工具链的支持,使得开发者能够快速、高效地完成开发任务。
Go语言在多个领域得到了广泛的应用,特别是在网络编程、云计算、容器化、大数据、区块链等领域,越来越受到开发者的欢迎。
特点
挑战
- 多核硬件架构
- 超大规模分布式计算集群
- Web模式导致的前所未有的开发规模和更新速度
基本特点
- 简单:关键字少,只有25个
- 高效:支持垃圾回收的同时支持指针直接访问
- 生产力:语法简洁
- 适合云计算、区块链
其它特点
- 不支持指针运算
hello world
package main // 包
import (
"fmt"
"os"
) // 代码依赖
// 功能实现
func main() {
fmt.Println("Hello World")
os.Exit(0)
}
- 包必须是package main;
- 方法必须是func main();
- 文件名不一定是main.go
- 不支持返回值,通过os.Exit返回状态,通过os.Args获得参数
运算符
- 算术运算符:+,-,*,/,%,++,--,
- 其中++,--只有后置,没有前置
- 比较运算符:==,!=,>,<,>=,<=
- 当数组维数相同时,可以通过==进行比较,这是其它语言不支持的,其它语言的数组存放的一般是指针/应用
- 逻辑运算符:&&,||,!
- 位运算符:&与,|或,^异或,<<左移,>>右移
- &^:按位清零;
- 当右边值为1时,左侧对应位清零,否则左侧对应位不变;
- 确实也等于先进行与再进行异或;其中与是找出需要清零的位,异或再对这些位进行清零。
循环结构
- 只有for一个关键字,如果省掉布尔条件,则为无限循环;
n := 0 for n < 5 { fmt.Println(n) n++ } for n := 0; n < 5; n++ { fmt.Println(n) } for idx, n := range [5]int{} { fmt.Println(idx, n) }
条件结构
- 布尔条件可以分成两段式,在前一段执行一个命令,再得到布尔条件;
if n := 0; n == 1 { fmt.Print("this 1\n") } else if n = 2; n == 2 { fmt.Print("this 2\n") } else { fmt.Print("this 3\n") }
- switch条件语句的每个case不需要接break,默认自动提供break;
- switch条件语句也可以不直接跟判断变量,而在case中补充,作用类似于if语句;
- case的判断条件可以用逗号分割多个满足的条件
switch n := 4; n { case 1: fmt.Println("a") case 2: fmt.Println("b") default: fmt.Println("d") } switch { case n == 1: fmt.Println("a") case n == 2, n == 3: fmt.Println("b") default: fmt.Println("d") }
数据结构
基本数据结构
- 基本数据类型bool,int,uint,float,byte,complex等常见类型
- 不支持隐式类型转换,包括原类型和别名类型之间也不支持
数组
- 声明&初始化&切片
a := [3]int{1, 3, 3} b := [...]float32{1., 4.6} // ...表示自动推断长度 c := [2][2]int{{1, 2}, {3, 4}} a[1] = 2 var d [4]int // 未指定的元素,声明后为默认值 fmt.Println(a, b, c, d, a[1:])
- 切片不支持负值
- 切片是一个三元素的数据结构,包含切片的起始指针、可访问容量len、数组实际占用容量cap;
- 切片的声明与数组类似,许多性质也相似,但是是不同的数据结构;数组容量不可变,切片可变;数组可用==比较,切片不能直接用==比较;
- 切片可以通过append添加元素,当len小于cap时,可以不用增加cap,直接增长len即可,当len超过cap时,cap会倍增;
e := []int{1, 2, 3, 4} fmt.Println(e, len(e), cap(e)) e = append(e, 5) fmt.Println(e, len(e), cap(e))
- 从数组中切片得到的切片结构与原数组是共享内存的,但是当切片操作在原内存上无法完成,需要扩展内存时,就会建立新的位置,与原来的空间互不影响;
f := []int{1, 2, 3, 4, 5, 6, 7} g := f[2:5] h := f[3:] fmt.Println(g, len(g), cap(g)) fmt.Println(h, len(h), cap(h)) fmt.Println(f) g[2] = 0 fmt.Println(f) h = append(h, 8) h[0] = 0 fmt.Println(f, g, h)
字符串
- string的默认值是””,而不是nil;
- 可以看成是只读的数组
- rune可以将字符转化为编码值
- 格式化输出时%T表示变量的类型,%v表示变量的值
s := "hello" fmt.Println(s, len(s)) s = s[2:] fmt.Println(s, len(s)) s = "中国" c := []rune(s) fmt.Println(s, len(s), c, len(c)) s = "a,b,c,d" for idx, s_i := range strings.Split(s, ",") { fmt.Println(idx, s_i) } fmt.Println(strings.Join(strings.Split(s, ","), "+")) fmt.Println("str:" + strconv.Itoa(99)) if num, e := strconv.Atoi("7"); e == nil { fmt.Println(88 - num) } s := "888" i := 666 fmt.Printf("变量的类型%T,变量的值%v, %v", s, s, i)
Map
- 初始化时指定key和value的类型,用make初始化时可以指定初始容量,避免在添加元素的过程中持续扩容。
- 当key不存在时,默认返回初始化值,如整数是0,可以通过同时返回的布尔值判断key是否存在;
m := map[string]int{"a": 1, "b": 2, "c": 3} fmt.Println(m, len(m)) if v, e := m["d"]; e { fmt.Println("d is", v) } else { fmt.Println("d is not exist") } for k, v := range m { fmt.Println(k, v) } m2 := make(map[string]int, 10) fmt.Println(m2, len(m2)) m2["a"] = 1 fmt.Println(m2, len(m2))
- map的value也可以是函数
m := map[int]func(op int) int{} m[0] = func(op int) int { return op } fmt.Println(m, len(m), m[0](10))
- 可以通过map来实现set
m := map[int]bool{} m[1] = true m[2] = true fmt.Println(m, len(m)) m[1] = false fmt.Println(m, len(m)) delete(m, 1) fmt.Println(m, len(m))
自定义类型
type new_type int
var a new_type = 4
fmt.Println(a)
函数
函数的定义、返回值、函数式编程
- 可以有多个返回值
- 函数都是值传递
- 函数可以作为变量的值、参数、返回值(很多特性是Python也具备的)
func fun1() (int, int) { return rand.Intn(20), rand.Intn(5) } func fun2(op int) int { return 2 * op } func time_count(fun func(op int) int) func(op int) int { return func(op int) int { start := time.Now() ret := fun(op) fmt.Println("time cost:", time.Since(start).Seconds()) return ret } } func main() { fmt.Println(fun1()) fmt.Println(fun2(10)) fmt.Println(time_count(fun2)(10)) }
参数
- 可变长参数是通过转化成数组实现,与Python类似,但是Python的list的元素可以是不同类型
func fun1(ops ...int) int { s := 0 for _, op := range ops { s += op } return s } func fun2(start int, ops ...int) int { for _, op := range ops { start += op } return start } func main() { fmt.Println(fun1(1, 2, 3, 4)) fmt.Println(fun1(1, 2, 3, 4, 5)) fmt.Println(fun2(1, 2, 3, 4, 5)) }
延迟执行
- 通过defer可以实现函数的延迟执行,可以通过它来声明一些资源清理、关闭等操作,即使后面忘记添加或发生错误也可以继续执行;
func clear() { fmt.Println("clear resources") } func fun() { defer clear() fmt.Println("do something") panic("err") } func main() { fun() }
面向对象
实例初始化
- 实例和指针都可以直接通过.来访问成员
- 传递指针可以更加节省空间,传递实例会增加赋值
type class_test struct { att1 int att2 string } func main() { c := class_test{10, "one"} fmt.Println(c) d := new(class_test) fmt.Println(d) d.att1 = 20 d.att2 = "two" fmt.Println(d, *d) }
接口
- 接口为非侵入式,不用像其它语言一样进行严格的绑定,只需要实现响应功能就可以使用接口;
type programmer interface { writehelloworld() string } type goprogrammer struct { } func (p *goprogrammer) writehelloworld() string { return "fmt.Println(\"Hello World\")" } func main() { var i programmer i = new(goprogrammer) fmt.Println(i.writehelloworld()) }
- 可以定义一个什么内容都没有的空接口,可以往空接口中存入不同类型的数据
var any interface{} fmt.Println(any) any = 6 fmt.Println(any) any = "888" fmt.Println(any)
- 还可以通过空接口实现接受不同类型参数的功能
func empty_interface(ei interface{}) { if v, ok := ei.(int); ok { fmt.Println("数据类型是int,值是", v) } else if v, ok := ei.(string); ok { fmt.Println("数据类型是string,值是", v) } else { fmt.Println("unknow") } } func main() { empty_interface(10) empty_interface("666") empty_interface(false) }
- 接口尽量使用小接口,即每个接口的方法比较少,例如一个,以减少开发者的负担;
- 大接口可以通过小接口组合而成;
- 方法在实现功能的时候也尽量只依赖必要功能的最小接口;
扩展&复用
// pet作为一般的父类
type pet struct {
}
func (d *pet) speak() {
fmt.Println("pet")
}
func (d *pet) speak_to(s string) {
d.speak()
fmt.Println(s)
}
// dog想继承,但是go不支持继承
// 可以用一些其它方法达到类似效果
// 如在子类中包含父类,以使用父类的方法
type dog1 struct {
p *pet
}
// 还可以通过匿名嵌套可以直接使用另一个类的方法
// 子类可以覆盖父类原有方法,但是不能达到重载的效果,如果调用父类方法,父类还是使用原来的方法
type dog2 struct {
pet
}
func (d *dog2) speak() {
fmt.Println("dog2")
}
func main() {
// 父类
p := new(pet)
p.speak()
p.speak_to("wan")
// 包含父类
d1 := new(dog1)
d1.p.speak()
d1.p.speak_to("wan")
// 匿名嵌套
d2 := new(dog2)
d2.speak()
d2.speak_to("wan")
}
异常
异常程序编写
- 没有专门的异常机制,因为创始人认为程序员对于异常的使用对代码不利
func abnormal(n int) (int, error) { if n < 0 || n > 1e5 { return n, errors.New("n must in [0,10000]") } return n * n, nil } func main() { if v, err := abnormal(-10); err != nil { fmt.Println(err) } else { fmt.Println(v) } }
- 编写处理错误的代码时,要避免嵌套,优先处理错误,让代码更加清晰。
异常退出
- panic一般用于严重错误,会输出调用栈信息,执行defer代码
- os.Exit返回错误码,不输出调用栈信息,不执行defer代码
错误恢复
- recover,保持程序继续运行,类似于其它语言中的catch所有错误;
- 可以放在defer代码中捕捉错误;
- 但是不应该忽略错误,随意使用
包
- 首字母大写的模块可以被包外代码访问
并发
协程
- 更加轻量级的线程,在用户态实现切换
- 在函数执行前加入go实现
func groutine(num int) {
fmt.Println(num)
}
func main() {
for i := 0; i < 10; i++ {
go func(num int) {
fmt.Println(num)
}(i)
}
time.Sleep(time.Millisecond * 50)
}
锁
- sync.Mutex,用于设置原子操作,实现协程安全
func main() { var mut sync.Mutex counter := 0 for i := 0; i < 10000; i++ { go func() { defer mut.Unlock() mut.Lock() counter++ }() } time.Sleep(time.Millisecond * 50) fmt.Println(counter) }
并行等待
- sync.WaitGroup,等待并行协程全部执行完
func main() { var mut sync.Mutex var wg sync.WaitGroup counter := 0 for i := 0; i < 10000; i++ { wg.Add(1) go func() { defer mut.Unlock() mut.Lock() counter++ wg.Done() }() } wg.Wait() fmt.Println(counter) }
CSP
- 通过通道channel来实现不同并发程序之间的异步通讯
- channel容量有限,松耦合
- 当channel不带buff时,必须生产者和消费者同时准备好,同步进行;当有足够buff时可以异步进行。
func fun1(c chan string) { fmt.Println("fun1 start") c <- "message" time.Sleep(time.Millisecond * 200) // c <- "message" fmt.Println("fun1 end") } func fun2(c chan string) { fmt.Println("fun2 start") time.Sleep(time.Millisecond * 100) fmt.Println(<-c) fmt.Println("fun2 end") } func main() { start_time := time.Now() // var ch = make(chan string) var ch = make(chan string, 1) var wg sync.WaitGroup wg.Add(1) go func() { fun1(ch) wg.Done() }() wg.Add(1) go func() { fun2(ch) wg.Done() }() wg.Wait() fmt.Println("time cost:", time.Since(start_time).Milliseconds()) }
- 当一些对象(连接数据库等工具类)会被经常使用到时,可以通过channel建立一个对象池,在需要的时候从中取出对象并在用完后再放回去。
- sync.pool也可以作为对象缓存,同时可以加入一个新建方法,当pool为空是自动返回一个新建的方法。
多通道选择
- 根据返回通道选择执行代码
- 在没有通道返回的时候,如果有default就执行default,否则阻塞
- 通过多通道选择可以实现超时控制
- case之间没有先后顺序
func fun1(c chan string) {
fmt.Println("fun1 start")
time.Sleep(time.Millisecond * 300)
c <- "message 1"
fmt.Println("fun1 end")
}
func fun2(c chan string) {
fmt.Println("fun2 start")
time.Sleep(time.Millisecond * 300)
c <- "message 2"
fmt.Println("fun2 end")
}
func main() {
start_time := time.Now()
var ch1 = make(chan string, 1)
var ch2 = make(chan string, 1)
go func() {
fun1(ch1)
}()
go func() {
fun2(ch2)
}()
select {
case ret := <-ch1:
fmt.Println("result is :", ret)
case ret := <-ch2:
fmt.Println("result is :", ret)
case <-time.After(time.Millisecond * 200):
errors.New("time out")
// panic("time out")
default:
fmt.Println("default result")
}
fmt.Println("time cost:", time.Since(start_time).Milliseconds())
}
- 消费者往往不能判断消息传递是否结束,这个时候可以让生产者在发完信息后关闭通道,消费者可以在读信息时判断是否关闭。
func main() { start_time := time.Now() var wg sync.WaitGroup var ch = make(chan int, 1) wg.Add(1) go func() { for i := 0; i < 10; i++ { ch <- i } close(ch) wg.Done() }() wg.Add(1) go func() { for { if v, ok := <-ch; ok { fmt.Println(v) } else { break } } wg.Done() }() wg.Wait() fmt.Println("time cost:", time.Since(start_time).Milliseconds()) }
单例模式
- 通过sync.Once下的Do实现在多线程环境下只被调用一次
type singleton struct { } var singleton_instance *singleton var one sync.Once func get_singleton() *singleton { one.Do(func() { fmt.Println("creat singleton") singleton_instance = new(singleton) }) return singleton_instance } func test_single() { var wg sync.WaitGroup for i := 0; i < 10; i++ { wg.Add(1) go func() { fmt.Printf("%x\n", unsafe.Pointer(get_singleton())) wg.Done() }() } wg.Wait() } func main() { test_single() }
任意返回
- 通过channel返回最早执行完的一个结果
func only_one(wg sync.WaitGroup) string { numsofrunner := 10 var ch chan string = make(chan string, numsofrunner) for i := 0; i < numsofrunner; i++ { wg.Add(1) go func(i int) { time.Sleep(time.Millisecond * 100) ch <- ("result of " + strconv.Itoa(i)) wg.Done() }(i) } return <-ch } func main() { start_time := time.Now() var wg sync.WaitGroup fmt.Println(only_one(wg)) wg.Wait() fmt.Println("time cost:", time.Since(start_time).Milliseconds()) }
测试
反射
// reflect.ValueOf()
// reflect.TypeOf()
// .FieldByName()
// .MethodByName()
type Test_reflect struct {
att int
}
func (p *Test_reflect) fun1(i int) int {
return i * i
}
func main() {
a := &Test_reflect{1}
fmt.Println(a.att)
fmt.Println(a.fun1(2))
fmt.Println(reflect.ValueOf(*a))
fmt.Println(reflect.TypeOf(*a))
fmt.Println(reflect.ValueOf(*a).FieldByName("att"))
fmt.Println(reflect.TypeOf(*a).FieldByName("att"))
fmt.Println(reflect.ValueOf(a).MethodByName("fun1"))
fmt.Println(reflect.TypeOf(a).MethodByName("fun1"))
fmt.Println(reflect.ValueOf(*a).MethodByName("fun1").Call([]reflect.Value{reflect.ValueOf(2)}))
fmt.Println(reflect.TypeOf(a).MethodByName("fun1").Call(2))
// 以上写法需调整
}