《Go入门指南》笔记
Map 删除:delete(map1, "Washington")
假设我们想获取一个 map 类型的切片,我们必须使用两次 make()
函数,第一次分配切片,第二次分配 切片中每个 map 元素
container:list/ring
runtime
-
reflect
: 实现通过程序运行时反射,让程序操作任意类型的变量。 -
runtime
: Go 程序运行时的交互操作,例如垃圾回收和协程创建。 - regex: 正则
- sync.Mutex:加锁
- godoc -http=:6060 -goroot="." 可以查看文档
如何强制使用工厂方法?
Go 语言不支持面向对象编程语言中那样的构造子方法,但是可以很容易的在 Go 中实现 “构造子工厂” 方法。为了方便通常会为类型定义一个工厂,按惯例,工厂的名字以 new 或 New 开头。
将对象声明称私有(即小写开头),调用Newxx()方法。
// 注意为私有方法
type matrix struct {
...
}
func NewMatrix(params) *matrix {
m := new(matrix) // 初始化 m
return m
}
匿名字段和内嵌结构体?
当两个字段拥有相同的名字(可能是继承来的名字)时该怎么办呢?
- 外层名字会覆盖内层名字(但是两者的内存空间都保留),这提供了一种重载字段或方法的方式;
- 如果相同的名字在同一级别出现了两次,如果这个名字被程序使用了,将会引发一个错误(不使用没关系)。没有办法来解决这种问题引起的二义性,必须由程序员自己修正。
在一个对象 obj 被从内存移除前执行一些特殊操作?
如果需要在一个对象 obj 被从内存移除前执行一些特殊操作,比如写到日志文件中,可以通过如下方式调用函数来实现:
runtime.SetFinalizer(obj, func(obj *typeObj))
func(obj *typeObj)
需要一个 typeObj
类型的指针参数 obj
,特殊操作会在它上面执行。func
也可以是一个匿名函数。
在对象被 GC 进程选中并从内存中移除以前,SetFinalizer
都不会执行,即使程序正常结束或者发生错误
接口命名约定?
(按照约定,只包含一个方法的)接口的名字由方法名加 [e]r
后缀组成,例如 Printer
、Reader
、Writer
、Logger
、Converter
等等。还有一些不常用的方式(当后缀 er
不合适时),比如 Recoverable
,此时接口名以 able
结尾,或者以 I
开头(像 .NET
或 Java
中那样)
一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。
接口类型判断?
Type-switch
func classifier(items ...interface{}) {
for i, x := range items {
switch x.(type) {
case bool:
fmt.Printf("Param #%d is a bool\n", i)
case float64:
fmt.Printf("Param #%d is a float64\n", i)
case int, int64:
fmt.Printf("Param #%d is a int\n", i)
case nil:
fmt.Printf("Param #%d is a nil\n", i)
case string:
fmt.Printf("Param #%d is a string\n", i)
default:
fmt.Printf("Param #%d is unknown\n", i)
}
}
}
如何使用接口来获取通用型?
func Sort(data Sorter) {
for pass := 1; pass < data.Len(); pass++ {
for i := 0;i < data.Len() - pass; i++ {
if data.Less(i+1, i) {
data.Swap(i, i + 1)
}
}
}
}
type Sorter interface {
Len() int
Less(i, j int) bool
Swap(i, j int)
}
现在如果我们想对一个 int
数组进行排序,所有必须做的事情就是:为数组定一个类型并在它上面实现 Sorter
接口的方法:
type IntArray []int
func (p IntArray) Len() int { return len(p) }
func (p IntArray) Less(i, j int) bool { return p[i] < p[j] }
func (p IntArray) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
data := []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}
a := sort.IntArray(data) //conversion to type IntArray from package sort
sort.Sort(a)
切片注意事项?
复制数据切片至空接口切片,必须要显式复制
切片的底层指向一个数组,该数组的实际容量可能要大于切片所定义的容量。只有在没有任何切片指向的时候,底层的数组内存才会被释放,这种特性有时会导致程序占用多余的内存。
Map注意事项?
Map使用make进行初始化 不要使用new;假设我们想获取一个 map 类型的切片,我们必须使用两次 make()
函数,第一次分配切片,第二次分配 切片中每个 map 元素
判断key是否存在 : val1, isPresent = map1[key1]
map中删除key: delete(map1, key1)
Map排序:如果你想为 map 排序,需要将 key(或者 value)拷贝到一个切片,再对切片排序(使用 sort 包),然后可以使用切片的 for-range 方法打印出所有的 key 和 value
方法相关小技巧?
在 Go 中,类型就是类(数据和关联的方法)。Go 不知道类似面向对象语言的类继承的概念。继承有两个好处:代码复用和多态。
在 Go 中,代码复用通过组合和委托实现,多态通过接口的使用来实现:有时这也叫 组件编程(Component Programming)。
许多开发者说相比于类继承,Go 的接口提供了更强大、却更简单的多态行为。
读写数据?
如何读写一个文件?
从命令行读入数据?
文件拷贝 io.Copy()
从命令行读取参数 : os.Args[1:]
os.OpenFile outputWriter := bufio.NewWriter(outputFile)
https://learnku.com/docs/the-way-to-go/122-file-reading-and-writing/3662
错误处理?
永远不要忽略错误,否则可能会导致程序崩溃!!
创建error: fmt.Errorf() / errors.New()
当发生像数组下标越界或类型断言失败这样的运行错误时,Go 运行时会触发运行时 panic,伴随着程序的崩溃抛出一个 runtime.Error
接口类型的值。这个错误值有个 RuntimeError()
方法用于区别普通错误。
panic 会导致栈被展开直到 defer 修饰的 recover () 被调用或者程序中止。
package main
import (
"fmt"
)
func badCall() {
panic("bad end")
}
func test() {
defer func() {
if e := recover(); e != nil {
fmt.Printf("Panicing %s\r\n", e)
}
}()
badCall()
fmt.Printf("After bad call\r\n") // <-- wordt niet bereikt
}
func main() {
fmt.Printf("Calling test\r\n")
test()
fmt.Printf("Test completed\r\n")
}
Calling test
Panicing bad end
Test completed
自定义包中的错误处理和 panicking?
这是所有自定义包实现者应该遵守的最佳实践:
1)在包内部,总是应该从 panic 中 recover:不允许显式的超出包范围的 panic ()
2)向包的调用者返回错误值(而不是 panic)。
在包内部,特别是在非导出函数中有很深层次的嵌套调用时,对主调函数来说用 panic 来表示应该被翻译成错误的错误场景是很有用的(并且提高了代码可读性)。
包内panic;包外error
// parse.go
package parse
import (
"fmt"
"strings"
"strconv"
)
// A ParseError indicates an error in converting a word into an integer.
type ParseError struct {
Index int // The index into the space-separated list of words.
Word string // The word that generated the parse error.
Err error // The raw error that precipitated this error, if any.
}
// String returns a human-readable error message.
func (e *ParseError) String() string {
return fmt.Sprintf("pkg parse: error parsing %q as int", e.Word)
}
// Parse parses the space-separated words in in put as integers.
func Parse(input string) (numbers []int, err error) {
defer func() {
if r := recover(); r != nil {
var ok bool
err, ok = r.(error)
if !ok {
err = fmt.Errorf("pkg: %v", r)
}
}
}()
fields := strings.Fields(input)
numbers = fields2numbers(fields)
return
}
func fields2numbers(fields []string) (numbers []int) {
if len(fields) == 0 {
panic("no words to parse")
}
for idx, field := range fields {
num, err := strconv.Atoi(field)
if err != nil {
panic(&ParseError{idx, field, err})
}
numbers = append(numbers, num)
}
return
}
使用一种用闭包处理错误的模式?
func check(err error) { if err != nil { panic(err) } }
// errorhandler:这是一个包装函数。接收一个 fType1 类型的函数 fn 并返回一个调用 fn 的函数。里面就包含有 defer/recover 机制
func errorHandler(fn fType1) fType1 {
return func(a type1, b type2) {
defer func() {
if err, ok := recover().(error); ok {
log.Printf(“run time panic: %v”, err)
}
}()
fn(a, b)
}
}
如何启动外部命令和程序?
os 包有一个 StartProcess
函数可以调用或启动外部系统命令和二进制可执行文件;它的第一个参数是要运行的进程,第二个参数用来传递选项或参数,第三个参数是含有系统环境基本信息的结构体。
这个函数返回被启动进程的 id(pid),或者启动失败返回错误。
exec 包中也有同样功能的更简单的结构体和函数;主要是 exec.Command(name string, arg ...string)
和 Run()
。首先需要用系统命令或可执行文件的名字创建一个 Command
对象,然后用这个对象作为接收者调用 Run()
单元测试和基准测试?
写测试用例
不要通过共享内存来通信,而通过通信来共享内存
xxx
通道channel
一个无缓冲通道只能包含 1 个元素,有时显得很局限。我们给通道提供了一个缓存,可以在扩展的 make
命令中设置它的容量
func compute(ch chan int){
ch <- someComputation() // when it completes, signal on the channel.
}
func main(){
ch := make(chan int) // allocate a channel.
go compute(ch) // stat something in a goroutines
doSomethingElseForAWhile()
result := <- ch
}
https://learnku.com/docs/the-way-to-go/142-covariance-channel/3686
学习研究下素数打印这个函数
后台服务模式:
// Backend goroutine.
func backend() {
for {
select {
case cmd := <-ch1:
// Handle ...
case cmd := <-ch2:
...
case cmd := <-chStop:
// stop server
}
}
}
协程与恢复
func server(workChan <-chan *Work) {
for work := range workChan {
go safelyDo(work) // start the goroutine for that work
}
}
func safelyDo(work *Work) {
defer func() {
if err := recover(); err != nil {
log.Printf("Work failed with %s in %v", err, work)
}
}()
do(work)
}
使用锁还是通道?
- 使用锁的情景:
- 访问共享数据结构中的缓存信息
- 保存应用程序上下文和状态信息数据
- 使用通道的情景:
- 与异步操作的结果进行交互
- 分发任务
- 传递数据所有权
惰性生成器的实现?
package main
import (
"fmt"
)
type Any interface{}
type EvalFunc func(Any) (Any, Any)
func main() {
evenFunc := func(state Any) (Any, Any) {
os := state.(int)
ns := os + 2
return os, ns
}
even := BuildLazyIntEvaluator(evenFunc, 0)
for i := 0; i < 10; i++ {
fmt.Printf("%vth even: %v\n", i, even())
}
}
func BuildLazyEvaluator(evalFunc EvalFunc, initState Any) func() Any {
retValChan := make(chan Any)
loopFunc := func() {
var actState Any = initState
var retVal Any
for {
retVal, actState = evalFunc(actState)
retValChan <- retVal
}
}
retFunc := func() Any {
return <- retValChan
}
go loopFunc()
return retFunc
}
func BuildLazyIntEvaluator(evalFunc EvalFunc, initState Any) func() int {
ef := BuildLazyEvaluator(evalFunc, initState)
return func() int {
return ef().(int)
}
}
使用通道实现Future模式。类似于java里面的future?
每一个协程执行完返回一个channel对象,然后最后从每一个channel中读取数据。
package gorout
type Matrix struct {
}
func InverseProduct(a Matrix, b Matrix) Matrix{
a_inv_future := InverseFuture(a) // start as a goroutine
b_inv_future := InverseFuture(b) // start as a goroutine
a_inv := <-a_inv_future
b_inv := <-b_inv_future
return Product(a_inv, b_inv)
}
func InverseFuture(a Matrix) (chan Matrix){
future := make(chan Matrix)
go func() {
future <- Inverse(a)
}()
return future
}
func Inverse(a Matrix) Matrix{
return a
}
func Product(a,b Matrix) (c Matrix){
return c
}
如何设计良好的错误处理,避免错误检测使代码变得混乱?
使用recover来终止panic。
使用闭包的方式解决错误。这样子啊子程序中错误处理简化成一个check(); 最后包装一层。
func check(err error) { if err != nil { panic(err) } }
func errorHandler(fn fType1) fType1 {
return func(a type1, b type2) {
defer func() {
if err, ok := recover().(error); ok {
log.Printf(“run time panic: %v”, err)
}
}()
fn(a, b)
}
}
结构体初始化?
当结构体的命名以大写字母开头时,该结构体在包外可见。
通常情况下,为每个结构体定义一个构建函数,并推荐使用构建函数初始化结构体
如何通过一个通道让主程序等待直到协程完成?(信号量模式)
ch := make(chan int) // Allocate a channel.
// Start something in a goroutine; when it completes, signal on the channel.
go func() {
// doSomething
ch <- 1 // Send a signal; value does not matter.
}()
doSomethingElseForAWhile()
<-ch // Wait for goroutine to finish; discard sent value.
简单的超时模版?
timeout := make(chan bool, 1)
go func() {
time.Sleep(1e9) // one second
timeout <- true
}()
select {
case <-ch:
// a read from ch has occurred
case <-timeout:
// the read from ch has timed out
}
如何使用输入通道和输出通道代替锁?
func Worker(in, out chan *Task) {
for {
t := <-in
process(t)
out <- t
}
}
如何取消取消耗时很长的同步调用?
ch := make(chan error, 1)
go func() { ch <- client.Call("Service.Method", args, &reply) } ()
select {
case resp := <-ch
// use resp and reply
case <-time.After(timeoutNs):
// call timed out
break
}
如何在程序出错时,终止程序?
if err != nil {
fmt.Printf(“Program stopping with error %v”, err)
os.Exit(1)
}
// or
if err != nil {
panic(“ERROR occurred: “ + err.Error())
}