Go


基础

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))
	// 以上写法需调整
}

文章作者: 万川
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 万川 !
  目录