后端实践-Go 语言入门指南(下) | 豆包MarsCode AI刷题

59 阅读4分钟

结构体,方法和接口

结构体(struct) 和方法(methods)

结构体类似于其他语言中的 class,可以在结构体中定义多个字段,为结构体实现方法,实例化等。接下来我们定义一个结构体 Student,并为 Student 添加 name,age 字段,并实现 hello() 方法。

type Student struct { 	
   name string 	
   age  int 
}  
func (stu *Student) hello(person string) string { 	
   return fmt.Sprintf("hello %s, I am %s", person, stu.name) 
}  
func main() { 	
   stu := &Student{ 		
       name: "Tom", 	
   } 	
   msg := stu.hello("Jack") 	
   fmt.Println(msg) // hello Jack, I am Tom 
} 
  • 使用 Student{field: value, ...}的形式创建 Student 的实例,字段不需要每个都赋值,没有显性赋值的变量将被赋予默认值,例如 age 将被赋予默认值 0。
  • 实现方法与实现函数的区别在于,func 和函数名hello 之间,加上该方法对应的实例名 stu 及其类型 *Student,可以通过实例名访问该实例的字段name和其他方法了。
  • 调用方法通过 实例名.方法名(参数) 的方式。

除此之外,还可以使用 new 实例化:

func main() { 	
    stu2 := new(Student) 	
    fmt.Println(stu2.hello("Alice")) // hello Alice, I am  , name 被赋予默认值"" 
} 

接口(interfaces)

一般而言,接口定义了一组方法的集合,接口不能被实例化,一个类型可以实现多个接口。

举一个简单的例子,定义一个接口 Person和对应的方法 getName() 和 getAge()

type Person interface { 	
    getName() string 
}  

type Student struct { 	
    name string 	
    age  int 
} 

func (stu *Student) getName() string { 	
    return stu.name 
}  
type Worker struct { 	
    name   string 	
    gender string 
}  
func (w *Worker) getName() string { 	
    return w.name 
}  
func main() { 	
    var p Person = &Student{ 		
        name: "Tom", 		
        age:  18, 	
    }  	
    
    fmt.Println(p.getName()) // Tom
} 
  • Go 语言中,并不需要显式地声明实现了哪一个接口,只需要直接实现该接口对应的方法即可。
  • 实例化 Student后,强制类型转换为接口类型 Person。

在上面的例子中,我们在 main 函数中尝试将 Student 实例类型转换为 Person,如果 Student 没有完全实现 Person 的方法,比如我们将 (*Student).getName() 删掉,编译时会出现如下报错信息。

*Student does not implement Person (missing getName method) 

但是删除 (*Worker).getName() 程序并不会报错,因为我们并没有在 main 函数中使用。这种情况下我们如何确保某个类型实现了某个接口的所有方法呢?一般可以使用下面的方法进行检测,如果实现不完整,编译期将会报错。

var _ Person = (*Student)(nil) var _ Person = (*Worker)(nil) 
  • 将空值 nil 转换为 *Student 类型,再转换为 Person 接口,如果转换失败,说明 Student 并没有实现 Person 接口的所有方法。
  • Worker 同上。

实例可以强制类型转换为接口,接口也可以强制类型转换为实例。

func main() { 	
    var p Person = &Student{ 		
         name: "Tom", 		
         age:  18, 	
    }  	
    
    stu := p.(*Student) // 接口转为实例 	
    fmt.Println(stu.getAge()) 
} 

空接口

如果定义了一个没有任何方法的空接口,那么这个接口可以表示任意类型。例如

func main() { 	
    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]] 
} 

并发编程(goroutine)

sync

Go 语言提供了 sync 和 channel 两种方式支持协程(goroutine)的并发。

例如我们希望并发下载 N 个资源,多个并发协程之间不需要通信,那么就可以使用 sync.WaitGroup,等待所有并发协程执行结束。

import ( 	
   "fmt" 	
   "sync" 
   "time" 
)  

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) 		
       go download("a.com/" + string(i+'0')) 	
   } 	
   wg.Wait() 	
   fmt.Println("Done!")
} 
  • wg.Add(1):为 wg 添加一个计数,wg.Done(),减去一个计数。
  • go download():启动新的协程并发执行 download 函数。
  • wg.Wait():等待所有的协程执行结束。
$  time go run .
start to download a.com/2 
start to download a.com/0 
start to download a.com/1 
Done!  

real    0m1.563s 

可以看到串行需要 3s 的下载操作,并发后,只需要 1s。

channel

var ch = make(chan string, 10) // 创建大小为 10 的缓冲信道 

func download(url string) { 	
   fmt.Println("start to download", url) 	
   time.Sleep(time.Second) 	
   ch <- url // 将 url 发送给信道
} 

func main() { 	
   for i := 0; i < 3; i++ { 		
       go download("a.com/" + string(i+'0')) 	
   } 	
   for i := 0; i < 3; i++ { 		
       msg := <-ch // 等待信道返回消息。 		
       fmt.Println("finish", msg) 	
   } 	
   fmt.Println("Done!") 
} 

使用 channel 信道,可以在协程之间传递消息。阻塞等待并发协程返回消息。

$ time go run .
start to download a.com/2
start to download a.com/0 
start to download a.com/1 
finish a.com/2 
finish a.com/1 
finish a.com/0 
Done!  

real    0m1.528s 

单元测试(unit test)

假设我们希望测试 package main 下 calc.go 中的函数,要只需要新建 calc_test.go 文件,在calc_test.go中新建测试用例即可。

// calc.go 
package main 

func add(num1 int, num2 int) int { 	
    return num1 + num2 
} 
// calc_test.go 
package main  

import "testing" 

func TestAdd(t *testing.T) { 	
    if ans := add(1, 2); ans != 3 { 		
        t.Error("add(1, 2) should be equal to 3") 	
    } 
} 

运行 go test,将自动运行当前 package 下的所有测试用例,如果需要查看详细的信息,可以添加-v参数。

$ go test -v 
=== RUN   TestAdd 
--- PASS: TestAdd (0.00s)
PASS
ok      example 0.040s 

包(Package)和模块(Modules)

Package

一般来说,一个文件夹可以作为 package,同一个 package 内部变量、类型、方法等定义可以相互看到。

比如我们新建一个文件 calc.go, main.go 平级,分别定义 add 和 main 方法。

// calc.go
package main  

func add(num1 int, num2 int) int { 	
    return num1 + num2 
} 
// main.go 
package main  

import "fmt"  

func main() { 	
    fmt.Println(add(3, 5)) // 8 
} 

运行 go run main.go,会报错,add 未定义:

./main.go:6:14: undefined: add 

因为 go run main.go 仅编译 main.go 一个文件,所以命令需要换成

$ go run main.go calc.go 
8 

$ go run .
8 

Go 语言也有 Public 和 Private 的概念,粒度是包。如果类型/接口/方法/函数/字段的首字母大写,则是 Public 的,对其他 package 可见,如果首字母小写,则是 Private 的,对其他 package 不可见。

Modules

Go Modules 是 Go 1.11 版本之后引入的,Go 1.11 之前使用 $GOPATH 机制。Go Modules 可以算作是较为完善的包管理工具。同时支持代理,国内也能享受高速的第三方包镜像服务。接下来简单介绍 go mod 的使用。Go Modules 在 1.13 版本仍是可选使用的,环境变量 GO111MODULE 的值默认为 AUTO,强制使用 Go Modules 进行依赖管理,可以将 GO111MODULE 设置为 ON。

在一个空文件夹下,初始化一个 Module

$ go mod init example 
go: creating new go.mod: module example 

此时,在当前文件夹下生成了go.mod,这个文件记录当前模块的模块名以及所有依赖包的版本。

接着,我们在当前目录下新建文件 main.go,添加如下代码:

package main  

import ( 	
    "fmt"  
    
    "rsc.io/quote" 
)  

func main() { 	
    fmt.Println(quote.Hello())  // Ahoy, world!
} 

运行 go run .,将会自动触发第三方包 rsc.io/quote的下载,具体的版本信息也记录在了go.mod中:

module example

go 1.13  

require rsc.io/quote v3.1.0+incompatible 

我们在当前目录,添加一个子 package calc,代码目录如下:

demo/    
    |--calc/      
        |--calc.go  
    |--main.go 

在 calc.go 中写入

package calc 

func Add(num1 int, num2 int) int { 	
    return num1 + num2 
} 

在 package main 中如何使用 package cal 中的 Add 函数呢?import 模块名/子目录名 即可,修改后的 main 函数如下:

package main  

import ( 	
    "fmt" 	
    "example/calc"  	
    
    "rsc.io/quote" 
)  

func main() { 	
    fmt.Println(quote.Hello()) 	
    fmt.Println(calc.Add(10, 3)) 
} 
$ go run .
Ahoy, world! 
13 

参考文献