zl程序教程

您现在的位置是:首页 >  后端

当前栏目

Go - 从0学习Go的第一课

Go学习 第一课
2023-06-13 09:15:32 时间

前言

1.查看语言版本

go version

2.下载编辑器,Atom在github上是开源的,官网:https://github.com/atom

3.第一个Go程序

package main

import "fmt"

func main(){
  fmt.Println("hello,world")
}

基本程序结构

1.测试脚本,执行脚本命令go test -v xxx_test.go

package try_test

import "testing"

func TestFirstTry( t*testing.T){
  t.Log("My Frist Try");
}

执行结果:

➜  test go test -v zc_test.go
=== RUN   TestFirstTry
    zc_test.go:6: My Frist Try
--- PASS: TestFirstTry (0.00s)
PASS
ok  	command-line-arguments	0.006s

2.声明常量

package main

import "fmt"

func main() {
   const LENGTH int = 10
   const WIDTH int = 5  
   var area int
   const a, b, c = 1, false, "str" //多重赋值

   area = LENGTH * WIDTH
   fmt.Printf("面积为 : %d", area)
   println()
   println(a, b, c)  
}

常量还可以用作枚举:

package const_test

import "testing"

func TestConst(t *testing.T) {
  const (
      Unknown = 0
      Female = 1
      Male = 2
  )
  t.Log(Unknown,Female,Male);
}

3.数据类型

  • Go语言不支持隐式转换
  • 字符串初始化是空字符串
  • 指针类型不可以赋值
package type_test

import "testing"

func TestType(t *testing.T)  {
    var a int = 1;
    var b int64
    b = int64(a)
    t.Log(a,b);
}

func TestPoint(t *testing.T)  {
  var a int = 1;
  aPtr := &a;
  t.Log(a,aPtr);
  t.Logf("%T %T",a,aPtr);
}

func TestString(t *testing.T)  {
  var string string
  t.Log("*"+string+"*");
}

4.条件和循环

4.1 for循环

package loop_test

import "testing"

func TestLoopNumber(t *testing.T){
  var number int = 0
  for number < 5 {
    t.Log(number);
    number++;
  }
}

4.2 if条件语句

if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
}

if 布尔表达式 {
   /* 在布尔表达式为 true 时执行 */
} else {
  /* 在布尔表达式为 false 时执行 */
}

常用集合

1.数组声明和遍历

声明数组 var variable_name [SIZE] variable_type

package array_test

import "testing"

func TestArrayInt(t *testing.T)  {
  var arr [3]int
  arr1 := [4]int{1,2,3,4}
  arr3 := [...]int{1,3,4,5}
  arr1[1] = 5

  t.Log(arr[1],arr[2]);
  t.Log(arr1,arr3);
}

func TestArrayLoop( t *testing.T )  {
  arr1 := [4]int{1,2,3,4}

  var count int = len(arr1);
  for index := 0;  index < count; index ++ {
    t.Log(index,arr1[index]);
  }

  for index,value := range arr1 {
    t.Log(index,value)
  }

  for _,value := range arr1 {
    t.Log(value)
  }
}

2.数组截取

a[开始索引(包含),结束索引(不包含)]

a := [...]int {1,2,3,4,5}

a[1:2] // 2
a[1:3] // 2,3
a[1:len(a)] // 2,3,4,5
a[1:] //2,3,4,5
a[:3] // 1,2,3
func TestArraySlice(t *testing.T)  {
  arr := [5]int{0,2,3,4,5}
  t.Log(arr[0:2])
}

3.切片类型

3.1切片声明:

package slice_test

import "testing"

func TestSliceInit(t *testing.T)  {
  var s0 []int
  s0 = append(s0,1)
  s := []int{}
  s1 := []int{1,2,3}
  s2 := make([]int, 2,4)
  t.Log(len(s0),cap(s0));
  t.Log(len(s),cap(s));
  t.Log(len(s1),cap(s1));
  t.Log(len(s2),cap(s2));
}

4.数组和切片的异同点:

  • 容量是否可伸缩
  • 是否可进行比较

5.Map声明和遍历

package map_test

import "testing"

func TestMapInit(t *testing.T)  {
    m := map[string]int{"one":1,"two":2,"three":3}
    m1 := map[string]int{}
    m2 := make(map[string]int, 10)
    t.Log(m,m1,m2);
}

func TestMapLoop(t *testing.T)  {
   m := map[string]int{"one":1,"two":2,"three":3}
   for key,value := range m {
     t.Log(key,value);
   }
}

与其他语言的差异:在访问的key不存在时,扔会返回0值,不能通过返回nil来判断元素是否存在。

6.字符串(与其他主要编程语言的差异)

  • string是引用类型,不是指针类型
  • string是只读的byte slice,len函数可以所包含的byte
  • string的byte数组可以存放任何数据

函数

与其他主要编程语言的差异

  • 可以有多个返回值
  • 所有参数都是值传递:slice,map,channel会有传引用的错觉
  • 函数可以作为变量的值
  • 函数可以作为参数和返回值
package func_test

import "testing"
import "math/rand"

func returnParams()  (int ,int){
  return rand.Intn(10),rand.Intn(20)
}

func TestFuncParams(t *testing.T)  {
  a,b := returnParams();
  t.Log(a,b);
}

可变参数:

package func_test

import "testing"

func Sum(ops ...int) int{
  ret := 0
  for _,op := range ops {
    ret += op
  }
  return ret;
}

func TestSumParams(t *testing.T)  {
  t.Log(Sum(1,2,3))
  t.Log(Sum(1,2,3,4,5))
}

defer延迟执行特性:

package func_test

import "testing"
import "fmt"

func Clear()  {
  fmt.Println("clear....");
}

func TestDeferFunc(t *testing.T)  {
    defer Clear()
    fmt.Println("start");
}

面向对象编程

1.数据的封装(结构体)

type Employee struct {
    Id string
    Name string
    Age int
}

代码实例:

package struct_test

import (
  "testing"
)

type Employee struct {
    Id string
    Name string
    Age int
}

func TestNewObject(t *testing.T)  {
  e := Employee{"0","Bob",20}
  e1 := Employee{Name:"Mike",Age:30}
  e2 := new(Employee) // 返回指针
  t.Log(e)
  t.Log(e1)
  t.Log(e2)
}

与其他主要编程语言的差异:

package struct_test

import (
  "testing"
  "fmt"
  "unsafe"
)

func TestVarAddr(t *testing.T)  {
  e := Employee{"0","Bob",20}
  fmt.Printf("Address is %x \n",unsafe.Pointer(&e.Name))
  t.Log(e.getString());
}

//第一种定义方式在对应方法被调用时,实例的成员会进行赋值
func (e *Employee) getString() string  {
  return fmt.Sprintf("ID:%s-Name:%s-Age:%d",e.Id,e.Name,e.Age);
}

//通常情况下为了避免内存拷贝我们使用第二种方式定义
func (e *Employee) getAddrString() string  {
  return fmt.Sprintf("ID:%s-Name:%s-Age:%d",e.Id,e.Name,e.Age);
}

2.Go语言的接口与依赖

接口:与其他主要编程语言的差异

  • 接口为非入侵性,实现不依赖于接口定义
  • 所有接口的定义可以包含在接口使用者包内
package interface_test

import (
  "testing"
)

type params interface {
  HelloWorld() string
}

type GoParams struct {

}

func (g *GoParams ) HelloWorld() string {
  return "fmt.Println(\"Hello,world\")";
}

func TestClient(t *testing.T){
  var p params
  p = new(GoParams)
  t.Log(p.HelloWorld());
}

3.扩展与复用

package extension_test

import (
  "testing"
  "fmt"
)

type Pet struct {

}

func (p *Pet) Speak()  {
  fmt.Println("...");
}

func (p *Pet) SpeakTo(host string) {
  //p.Speak();
  fmt.Println(" ",host)
}


type Dog struct {
  Pet
}

func TestExtens(t *testing.T){
  dog := new(Dog)
  dog.GetDoger();
}

func (d *Dog) GetDoger()  {
  d.SpeakTo("host");
  p := new(Pet);
  p.SpeakTo("ssss");
}

4.空接口与断言

  • 空接口可以表示任何类型
  • 通过断言来将空接口转换为制定类型
v,ok := p.(int)
package empty_interface

import (
  "testing"
  "fmt"
)

func DoSomething(p interface{}){
  if i,ok := p.(int); ok {
    fmt.Println("Intager",i);
    return
  }
  
  if s,ok := p.(string); ok {
    fmt.Println("String",s);
    return
  }
  fmt.Println("Unknow Type")
}

func TestEmptyInterface(t *testing.T)  {
    DoSomething(10)
    DoSomething("stark")
}

Go接口最佳实践:

倾向于使用小的接口定义,很多接口只包含一个方法。

type Reader interface {
  Read(p []byte)(n int,err error)
}

type Writer interface {
  Writer(p []byte)(n int,err error)
}

较大的接口定义,可以由多个小接口定义组合而成。

type ReadWrite interface {
  Read(p []byte)(n int,err error)
  Writer(p []byte)(n int,err error)
}

只依赖于必要功能的最小接口。

错误处理

与其他主要编程语言的差异

  • 没有异常机制
  • error类型实现了error接口
  • 可以通过errors.New来快速创建错误实例
type error interface {
  Error() string
}

errors.New("n must be in the range [0,10]");

2.panic

  • panic用于不可以恢复的错误
  • panic退出前会执行defer指定的内容

3.recover

在不确定的情况下,可以重启来恢复程序

包和依赖管理

1.package

  • 基本复用模块单元(以首字母大写来表明可被包外代码访问)
  • 代码的package可以和所在的目录不一致
  • 同一目录里的Go代码的package要保持一致

2.init方法

  • 在main被执行前,所有依赖的package的init方法都会被执行
  • 不同包的init函数按照包导入的依赖关系决定执行顺序
  • 每个包可以有多个init函数
  • 包的每个源文件也可以有多个init函数,这点比较特殊

package

1.通过 go get 来获取远程依赖

  • go get -u 强制从网络更新远程依赖 2.注意代码在GitHub上的组织形式,以适应go get
  • 直接以代码路径开始,不要有src

依赖管理

并发编程

1.协程

package groutine_test

import (
  "fmt"
  "time"
  "testing"
)

func TestGroutine(t *testing.T)  {
  for i:= 0;i< 10;i++{
    go func (i int)  {
      fmt.Println(i)
    }(i)
    time.Sleep(time.Millisecond * 50)
  }
}

2.协程锁 sync.Mutex

3.CSP并发机制

Csp Vs Actor

  • 和Actor的直接通讯不同,Csp模式则是通过Channel进行通讯的,更松耦合一些。
  • Go中channel是有容量限制并且独立于处理Groutine,Actor模式中的mailbox容量是无限的,接收的进程也总是被动的处理消息。
package channel_test

import (
  "testing"
  "fmt"
  "time"
)

func service () string {
  time.Sleep(time.Millisecond * 50)
  return "Deno"
}

func otherTask(){
  fmt.Println("working on something else")
  time.Sleep(time.Millisecond * 100)
  fmt.Println("Task is done")
}

func TestService(t *testing.T)  {
  t.Log(service ())
  otherTask()
}

Channel异步执行

package channel_test

import (
  "testing"
  "fmt"
  "time"
)

func service () string {
  time.Sleep(time.Millisecond * 50)
  return "Deno"
}

func otherTask(){
  fmt.Println("working on something else")
  time.Sleep(time.Millisecond * 100)
  fmt.Println("Task is done")
}

func AsynService() chan string {
  retCh := make(chan string,1)
  go func(){
    ret := service()
    fmt.Println("returned result")
    retCh <- ret
    fmt.Println("service exited")
  }()
  return retCh
}

func TestAsynService(t *testing.T)  {
  retCh := AsynService()
  otherTask()
  fmt.Println(<-retCh)
  time.Sleep(time.Second * 1)
}

多路选择和超时

select {
    case ret := <- retChannel
}

4.Channel的关闭和广播

  • 向关闭的channel发送数据,会导致panic
  • v,ok <- ch;ok为bool值,true表示正常接收,false表示通道关闭
  • 所有的channel接收者都会在channel关闭时,立刻从阻塞等待中返回且上述ok值为false。这个广播机制常被利用,进行向多个订阅者同时发送信号。比如退出信号。

sync.Pool 对象获取

  • 尝试从私有对象获取
  • 私有对象不存在,尝试从当前Processor的共享池获取
  • 如果当前Processor共享池也是空的,那么就尝试去其他Processor的共享池获取
  • 如果所有子池都是空的,最后就用用户指定的New函数产生一个新的对象返回
  1. sync.Pool 对象的生命周期
  • GC会清除sync.pool缓存的对象
  • 对象的缓存有效期为下一次GC之前

sync.Pool 总结

  • 适用于通过复用,降低复杂对象的创建和GC代价
  • 协程安全,会有锁的开销
  • 生命周期受GC影响,不适合于做连接池等,需自己管理生命周期的资源的池化

测试

1.单元测试

内置单元测试框架

  • Fail,Error: 该测试失败,该测试继续,其他测试继续执行
  • FailNow,Fatal:该测试失败,该测试中止,其他测试继续执行

-v:查看测试结果

-cover:测试代码覆盖率

go test -v -cover type_test.go

2.第三方 BDD框架

项目网站

https://github.com/smartystreets/goconvey

安装

go get -u github.com/smartystreets/goconvey

启动 web Ui

$GOPATH/bin/goconvey

反射和UnSafe

reflect.TypeOf Vs reflect.ValueOf
  • reflect.TypeOf 返回类型(reflect.TypeOf)
  • reflect.ValueOf 返回类型(reflect.Value)
  • 可以从reflect.Value获得类型
  • 通过kind来判断类型

常用架构模式实现

Pipe-Filter模式

  • 非常适合与数据处理及数据分析系统
  • Filter封装数据处理的功能
  • 松耦合:Filter只跟数据(格式)耦合
  • Pipe用于连接Filter传递数据或者在异步处理过程中缓冲数据流进程内同步调用时,pipe演变为数据在方法调用间的传递。

Micro Kernel

特点:

易于扩展、错误隔离、保持架构一致性

要点:

  • 内核包含公共流程和通用逻辑
  • 将可变或可扩展部分规划为扩展点
  • 抽象扩展点行为,定义接口
  • 利用插件进行扩展

常见任务

内置Json解析

type BasicInfo struct {
  Name string `json:"name"`
  Age int `json:"age"`
}