Go语言基础

这篇具有很好参考价值的文章主要介绍了Go语言基础。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

参考书籍《Go程序设计语言》
学习Go语言基础,并记录相关知识和代码。

第一章 入门

1.1 Hello world

创建helloworld.go

package main

import "fmt"

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

运行
go run helloworld.go
编译
go build helloworld.go

1.2 命令行参数

输出命令行参数

func v1() {
	var s, sep string
    for i := 1; i < len(os.Args); i++ {//i:=1,表示定义i=1,并自动根据初值确定类型,此后为正常变量。
		s += sep + os.Args[i]
		sep = " "
	}
	fmt.Println(s)
}
go run .\2_args.go pwd hello run

使用range简化

func v2() {
	var s, sep string
	for _, arg := range os.Args[1:] {// range 返回 key value
		s += sep + arg
		sep = " "
	}
	fmt.Println(s)
}
go run .\2_args.go pwd hello run

使用Join简化

func v3() {
	res := strings.Join(os.Args[1:], " ")
	print(res)
}

1.3 重复行

import (
	"bufio"
	"fmt"
	"os"
)

func dup1() {
	counts := make(map[string]int) //make函数用来创建新的map
	input := bufio.NewScanner(os.Stdin)
	for input.Scan() {
		counts[input.Text()]++
	}
	for line, n := range counts {
		if n >= 1 {
			fmt.Printf("%d\t%s\n", n, line)
		}
	}
}

func main() {
	dup1()
}
go run .\3_duplicate_line.go
hello 
bob
said
by
alice
bob
is
a
good
man
alice
word
world
hello

版本二,文件与命令行

func dup2() {
	counts := make(map[string]int) //make函数用来创建新的map
	files := os.Args[1:]
	if len(files) == 0 {
		countLines(os.Stdin, counts)
	} else {
		for _, arg := range files {
			f, err := os.Open(arg)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Dup2:%v\n", err)
				continue
			}
			countLines(f, counts)
			f.Close()
		}
		for line, n := range counts {
			if n >= 1 {
				fmt.Printf("%d\t%s\n", n, line)
			}
		}

	}
}
func countLines(f *os.File, counts map[string]int) {
	input := bufio.NewScanner(f)
	for input.Scan() {
		counts[input.Text()]++
	}

}
 go run .\3_duplicate_line.go hello.txt

1.4 GIF

package main

import (
	"image"
	"image/color"
	"image/gif"
	"io"
	"log"
	"math"
	"math/rand"
	"net/http"
	"os"
	"time"
)

var palette = []color.Color{color.White, color.Black}

const (
	whiteIndex = 0
	blackIndex = 1
)

func main() {
	rand.Seed(time.Now().UTC().UnixNano())
	if len(os.Args) > 1 && os.Args[1] == "web" {
		handler := func(w http.ResponseWriter, r *http.Request) {
			lissajous(w)
		}
		http.HandleFunc("/", handler)
		log.Fatal(http.ListenAndServe("localhost:8080", nil))
		return
	}
	lissajous(os.Stdout)
}

func lissajous(out io.Writer) {
	const (
		cycles  = 5     // 完整的x振荡器变化的个数
		res     = 0.001 // 角度分辨率
		size    = 100   // 图像画布包含[-size...size]
		nframes = 60    //动画中的帧数
		delay   = 10    // 以10ms为单位的帧间延迟
	)
	freq := rand.Float64() * 3.0
	anim := gif.GIF{LoopCount: nframes}
	phase := 0.0
	for i := 0; i < nframes; i++ {
		rect := image.Rect(0, 0, 2*size+1, 2*size+1)
		img := image.NewPaletted(rect, palette)
		for t := 0.0; t < cycles*2*math.Pi; t += res {
			x := math.Sin(t)
			y := math.Sin(t*freq + phase)
			img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5), blackIndex)
		}
		phase += 0.1
		anim.Delay = append(anim.Delay, delay)
		anim.Image = append(anim.Image, img)
	}
	gif.EncodeAll(out, &anim)

}

go run gif.go web

1.5 获取一个url

go 可以方便的创建服务器,并且有并发性。

package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
)

func main() {
	for _, url := range os.Args[1:] {
		resp, err := http.Get(url)

		if err != nil {
			fmt.Fprintf(os.Stderr, "fetch:%v\n", err)
			os.Exit(1)
		}
		b, err := ioutil.ReadAll(resp.Body)

		resp.Body.Close()
		if err != nil {
			fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
			os.Exit(1)
		}
		fmt.Printf("%s", b)
	}
}
go fetch.go http://www.baidu.com
if _, err := io.Copy(os.Stdout, resp.Body); err != nil {
			log.Fatal(err)
		}

1.6 并发获取多个URL

Go并发获取多个URL

package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"time"
)

func main() {
	start := time.Now()
	ch := make(chan string)
	for _, url := range os.Args[1:] {
		go fetch(url, ch) //启动一个goroutine
	}
	for range os.Args[1:] {
		fmt.Println(<-ch)
	}
	fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}

func fetch(url string, ch chan<- string) {
	start := time.Now()
	resp, err := http.Get(url)

	if err != nil {
		ch <- fmt.Sprint(err)
		return
	}
	nbytes, err := io.Copy(ioutil.Discard, resp.Body)
	resp.Body.Close()
	if err != nil {
		ch <- fmt.Sprintf("while reading %s: %v", url, err)
		return
	}
	secs := time.Since(start).Seconds()
	ch <- fmt.Sprintf("%2fs %7d %s", secs, nbytes, url)

}

1.7 一个web服务器

简单服务器

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", handler1)
	log.Fatal(http.ListenAndServe("localhost:8080", nil))
}

func handler1(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)

}

带有并发锁的计数服务器

package main

import (
	"fmt"
	"log"
	"net/http"
	"sync"
)

var mu sync.Mutex
var count int

func main() {
	http.HandleFunc("/", handler)
	http.HandleFunc("/count", counter)
	log.Fatal(http.ListenAndServe("localhost:8080", nil))
}

func handler(w http.ResponseWriter, r *http.Request) {
	mu.Lock()
	count++
	mu.Unlock()
	fmt.Fprintf(w, "URL.Path=%q\n", r.URL.Path)
}

func counter(w http.ResponseWriter, r *http.Request) {
	mu.Lock()
	fmt.Fprintf(w, "Count %d\n", count)
	mu.Unlock()
}

显示相关协议与表单

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {

	http.HandleFunc("/", handler2)
	log.Fatal(http.ListenAndServe("localhost:8080", nil))

}
func handler2(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "%s %s %s", r.Method, r.URL, r.Proto)
	for k, v := range r.Header {
		fmt.Fprintf(w, "Hander[%q]=%q\n", k, v)
	}
	fmt.Fprintf(w, "Host = %q\n", r.Host)
	fmt.Fprintf(w, "RemoteAddr = %q\n", r.RemoteAddr)

	if err := r.ParseForm(); err != nil {
		log.Print(err)
	}
	for k, v := range r.Form {
		fmt.Fprintf(w, "Form[%q]=%q\n", k, v)
	}
}

1.8 others

switch

package main

import (
	"fmt"
	"math/rand"
)

func coinflip() string {
	if rand.Int()%2 == 1 {
		return "heads"
	} else {
		return "tails"
	}
}
func test1() {
	heads, tails := 0, 0
	for i := 0; i < 5; i++ {
		switch coinflip() {
		case "heads":
			heads++
		case "tails":
			tails++
		default:
			fmt.Println("landed on edge!")
		}
	}

	fmt.Printf("in 5 times,\nheads: %d\ntails:%d", heads, tails)
}

func Signum(x int) int {
	switch { //相当于switch true
	case x > 0:
		return +1
	default:
		return 0
	case x < 0:
		return -1

	}
}
func test2() {
	println(Signum(-1))
}

func main() {
	test2()
}

第二章 程序结构

2.1 名称

开头是字母或下划线,后面是数字字母下划线,区分大小写

实体声明在函数外,则对包内可见,同一个package。实体第一个字母大写则对包外可用。

驼峰式命名

2.2 声明

四种实体声明(包级别):

  • 变量: var
  • 常量:const
  • 类型:type
  • 函数:func

package 包声明

import 声明

2.3 变量

var name type = expression

type与expression可以省略一个,不能都省略。

type省略,类型与第一次赋值有关。

expression省略,对应零值。nil,0,false,””。

2.3.1 短变量声明

name := expression

短变量可以只声明部分变量

in, err := os.Open(infile)
out, err := os.Create(outfile)//这里err不是声明,只是赋值

短变量声明至少有一个新变量。

2.3.2 指针

变量的名,操作的直接是变量的值

指针的值是变量的地址。所有变量都有地址。

package main

import "fmt"

func main() {
	x := 1
	p := &x
	fmt.Println(*p)//1
	*p = 2
	println(x)//2

}

指针相等的情况,都为nil,或者,指向同一个地址

func f() *int {
	v := 1
	return &v
}

func pointerTest2() {
	var p = f()
	fmt.Println(*p)         //1
	fmt.Println(f() == f()) //false
}

返回局部变量地址是安全的,

每次调用函数返回的地址都不一样


作为函数变量

func incr(p *int) int {
	*p++
	return *p
}
func pointerTest3() {
	v := 1
	incr(&v)
	fmt.Println(incr(&v))
}

解析命令行参数

var n = flag.Bool("n", false, "omit trailing newline")
var sep = flag.String("s", " ", "separator")

func pointerTest4() {
	flag.Parse()
	fmt.Print(strings.Join(flag.Args(), *sep))
	if !*n {
		fmt.Println()
	}
}
go run echo.go -help
go run echo.go -s - a b c
go run echo.go -n a b d

2.3.3 new函数

创建对应类型的空指针

import "fmt"

func main() {
	p := new(int)
	fmt.Println(*p) //0
	*p = 2
	fmt.Println(*p) //2
}

2.3.4 变量的生命周期

包级别变量的生命周期是整个程序的执行时间。

局部变量:动态生命周期 每次执行声明语句时,创建一个新的实体,变量一直生存到它变得不可访问。

package main

var global *int

func h() {
	var x int
	x = 1
	global = &x//x逃逸了
}
func i() {
	y := new(int)
	*y = 1//y没有再被使用,y分配在栈上
}
func main() {
	h()
}

2.4 赋值

=

支持

++
--
*=
+=
..

2.4.1 多重赋值

x,y = y,x
a[i],a[j] = a[j],a[i]

func gcd(x,y int) int{
    for y != 0{
        x,y = y,x%y
    }
    return x
}

v,ok = m[key] //map查询
v,ok = x.(T) //类型断言
v,ok = <-ch  //通道接收

隐式赋值

  • 传参
  • 返回值
  • 复合类型声明

2.5 类型声明

type name underlyin-type

起别名

type Celsius float64

type Fahrenheit float64

const (
	AbsoluteZeroC Celsius = -273.15
	FreezingC     Celsius = 0
	BoilingC      Celsius = 100
)

func CToF(c Celsius) Fahrenheit {
	return Fahrenheit(c*9/5 + 32)
}
func FtoC(f Fahrenheit) Celsius {
	return Celsius((f - 32) * 5 / 9)
}

2.6 包和文件

一个包的源代码可以在多个.go文件中,

package name

2.6.1 导入

import 

2.6.2 包初始化

包级别变量,顺序初始化,遇到依赖,先解析依赖。


var a = b+c //最后把a初始化为3
var b = f() // 通过调用f把b初始化为2
var c = 1 // 首先初始化为1

2.7 作用域

语法块

for {}
if {}
switch {}
select {}
{}

语法块决定了作用域。

内层覆盖外层

第三章 基本数据

四大类:

  • 基础类型:数字、字符串(string)、布尔类型(boolean)
  • 聚合类型:数组(array)、结构体(struct)
  • 引用类型:指针(pointer)、slice、map、function、channel
  • 接口类型

3.1 整型

var a int8 = 1
var b int16 = 2
var c int32 = 3
var d int64 = 4
var e int = 5 //原生
println(a,b,c,d,e)
rune 类型等于 int32 类型
byte 类型等于 uint8 类型
uintptr 大小不明,但可以放完整指针
intint32 类型不同,需要转换

运算符优先级

* / % << >> & &^
+ - | ^ == != < <= > >= 
&& 
||

位运算

&
|
^
&^
<<
>>

3.2 浮点数

float32 3.4e38
float64 1.7e308
package main

import (
	"fmt"
	"math"
	"os"
)

const (
	width, height = 600, 320
	cells         = 100
	xyrange       = 30.0
	xyscale       = width / 2 / xyrange
	zscale        = height * 0.4
	angle         = math.Pi / 6
)

var sin30, cos30 = math.Sin(angle), math.Cos(angle)

func main() {
	res := ""
	res += fmt.Sprintf("<svg xmlns='http://www.w3.org/2000/svg'"+
		"style='stroke:grey;fill:white;stroke-width:0.7'"+
		"width='%d' height='%d'>", width, height)
	for i := 0; i < cells; i++ {
		for j := 0; j < cells; j++ {
			ax, ay := corner(i+1, j)
			bx, by := corner(i, j)
			cx, cy := corner(i, j+1)
			dx, dy := corner(i+1, j+1)
			res += fmt.Sprintf("<polygon points='%g,%g %g,%g %g,%g %g,%g'/>\n",
				ax, ay, bx, by, cx, cy, dx, dy)
		}
	}
	res += "</svg>"
	file, err := os.Create("sin.svg")
	if err != nil {
		panic(err)
	}
	defer file.Close()
	_, err = file.WriteString(res)
	if err != nil {
		panic(err)
	}
}

func corner(i, j int) (float64, float64) {
	x := xyrange * (float64(i)/cells - 0.5)
	y := xyrange * (float64(j)/cells - 0.5)

	z := f(x, y)
	sx := width/2 + (x-y)*cos30*xyscale
	sy := height/2 + (x+y)*xyscale - z*zscale
	return sx, sy
}
func f(x, y float64) float64 {
	r := math.Hypot(x, y)
	return math.Sin(r)
}

3.3 复数

x := 1+2i
cmplx包
cmplx.Sqrt(-1)
package main

import (
	"image"
	"image/color"
	"image/png"
	"math/cmplx"
	"os"
)

func main() {
	const (
		xmin, ymin, xmax, ymax = -2, -2, +2, +2
		width, height          = 1024, 1024
	)
	img := image.NewRGBA(image.Rect(0, 0, width, height))
	for py := 0; py < height; py++ {
		y := float64(py)/height*(ymax-ymin) + ymin
		for px := 0; px < width; px++ {
			x := float64(px)/width*(xmax-xmin) + xmin
			z := complex(x, y)
			img.Set(px, py, mandelbrot(z))
		}
	}
	file, err := os.Create("a1.png")
	if err != nil {
		panic(err)
	}
	defer file.Close()
	if err := png.Encode(file, img); err != nil {
		panic(err)
	}
}
func mandelbrot(z complex128) color.Color {
	const iterations = 200
	const contrast = 15
	var v complex128
	for n := uint8(0); n < iterations; n++ {
		v = v*v + z
		if cmplx.Abs(v) > 2 {
			return color.Gray{255 - contrast*n}
		}
	}
	return color.Black

}

Go语言基础

3.4 布尔值

bool只有ture和false两种可能。

&& 优先级高于||

3.5 字符串

字符串是不可变的的字节序列。

len(s)返回的是字节数。

可以切片

s[0:5] //[0,5)

+号可以产生新的字符串,可以做比较按字节比较。

func main() {
	s := "left foot"
	t := s
	s += ",right foot" //并不改变原来字符串的值,只是将新的字符串赋值给了s。
	println(t)
	println(s)
	s[0] = 'a' //编译报错
}

不可变意味着底层可共用内存。

3.5.1 字符串字面量

字符串都可写作,string literal

"hello,世界"

Go按照UTF-8编码用双引号,原生字符串字面量用

`content`

3.5.2 Unicode

Go语言为Unicode编码数字取名为rune

四个字节天然适合int32,

int32 = rune

3.5.3 UTF-8

UTF-8以字节为单位对Unicode码作变长编码。

func test2() {
	s := "Hello,世界"
	fmt.Println(len(s))
	fmt.Println(utf8.RuneCountInString(s))
	for i := 0; i < len(s); {
		r, size := utf8.DecodeRuneInString(s[i:])
		fmt.Printf("%d\t%c\n", i, r)
		i += size
	}
	//在range内解码
	for i, r := range s {
		fmt.Printf("%d\t%q\t%d\n", i, r, r)
	}
	//统计字符数
	n := 0
	for range s {
		n++
	}
	fmt.Printf("%d\n", n)
}

如果遇到不合理的字符会解码为

\uFFFD

用rune数组可以很方便的访问每个utf8编码

func test3() {
	s := "こんにちは世界"
	fmt.Printf("%x\n", s)
	r := []rune(s)
	fmt.Printf("%x\n", r)
	fmt.Println(string(r))
	fmt.Println(string('\ufffd'))
	fmt.Println(string(65))
}

3.5.4 字符串和字节slice

四个包:

  • bytes
  • strings
  • strconv
  • unicode

模仿basenaem

func basename(s string) string {
	//去掉最后一个/之前的所有内容
	for i := len(s) - 1; i >= 0; i-- {
		if s[i] == '/' {
			s = s[i+1:]
			break
		}
	}
	//保留最后一个.之前的所有内容
	for i := len(s) - 1; i >= 0; i-- {
		if s[i] == '.' {
			s = s[:i]
			break
		}
	}
	return s
}

func test4() {
	fmt.Println(basename("a/b/c.go"))
	fmt.Println(basename("c.d.go"))
	fmt.Println(basename("abc"))
}

“12312312”->“12,312,312”

func comma(s string) string {
	n := len(s)
	if n <= 3 {
		return s
	}
	return comma(s[:n-3]) + "," + comma(s[n-3:])
}

字节slice可以随意修改,字符串不行,但是可以相互转换

func test5() {
	s := "abc"
	b := []byte(s)
	s2 := string(b)
	println(s2)
}

在go语言中,strings和bytes都提供了六个函数

func Contains(s,substr string)bool

他们唯一的不同是,bytes的操作对象是字节slice,而strings是存在字符串。

为了高效处理字节slice,bytes提供了Buffer类型,无需初始化。

func intsToString(values []int) string {

	var buf bytes.Buffer
	buf.WriteByte('[')
	for i, v := range values {
		if i > 0 {
			buf.WriteString(", ")
		}
		fmt.Fprintf(&buf, "%d", v)
	}
	buf.WriteByte(']')
	return buf.String()

}
func test6() {
	println(intsToString([]int{1, 2, 3, 4}))
}

3.5.5 字符串和数字的相互转换

strconv包

func test7() {
	x := 123
	y := fmt.Sprintf("%d", x)
	fmt.Println(y, strconv.Itoa(x))
	println("-----------")
	fmt.Println(strconv.FormatInt(int64(x), 2))
	println("-----------")
	s := fmt.Sprintf("x=%b", x)
	println(s)
	println("-----------")
	x, err := strconv.Atoi("123")
	if err != nil {
		panic(err)
	}
	println(x)
	if y, err := strconv.ParseInt("2123", 10, 64); err != nil {
		println(y)
	} //最长64位

}

3.6 常量

所有常量本质上是基本类型:布尔、字符串、数字

const pi = 3.14159
const (
	e = 1
    b = 2
)

常量生成器iota

枚举

type Weekday int
const(
	Sunday Weekday= iota//从0开始加
	Monday
	Tuesday
	Wednesday
	Thursday
	Friday
	Saturday
)
const (
	Sunday Weekday = 1<<iota
    ...
)

go的无类型常量

ZiB YiB
math.Pi

第四章 符合数据类型

4.1 数组

package main

import (
	"crypto/sha256"
	"fmt"
)

func test1() {
	var a [3]int
	fmt.Println(a[0])
	fmt.Println(a[len(a)-1])
	for i, v := range a {
		fmt.Printf("%d %d\n", i, v)
	}
	var q [3]int = [3]int{1, 2, 3}
	for _, v := range q {
		fmt.Printf("%d \t", v)
	}
	println()
	r := [...]int{2, 3, 4, 5}
	for _, v := range r {
		fmt.Printf("%d \t", v)
	}

}

func test2() {
	type Currency int
	const (
		USD Currency = iota
		EUR
		GBP
		RMB
	)
	symbol := [...]string{USD: "$", EUR: "€", GBP: "£", RMB: "¥"}
	fmt.Println(RMB, symbol[RMB])
}
func test3() {
	r := [...]int{99: -1} //100个数,最后一个为-1
	for _, v := range r {
		fmt.Printf("%d ", v)
	}
	a := [2]int{1, 2}
	b := [...]int{1, 2}
	c := [2]int{1, 3}
	fmt.Println(a == b, a == c, b == c) //ture false false
	//d:=[3]int{1,2,}
	//fmt.Println(a==d)//编译错误,长度不同
}
func test4() {
	c1 := sha256.Sum256([]byte("x"))
	c2 := sha256.Sum256([]byte("x"))
	fmt.Printf("%x\n%x\n%t\n,%T\n", c1, c2, c1 == c2, c1)
	//2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881
	//2d711642b726b04401627ca9fbac32f5c8530fb1903cc4db02258717921a4881
	//true
	//,[32]uint8

}
func test5() {
	a := [...]int{31: 0}
	one(&a)
	for _, v := range a {
		fmt.Printf("%d ", v)
	}
	fmt.Println(len(a))
}
func main() {
	test5()
}

统计hash结果不同的个数

func PopCount(a [32]uint8, b [32]uint8) int {
	count := 0
	for i := range a {
		if a[i] != b[i] {
			count++
		}
	}
	return count
}

4.2 slice

slice是轻量级数据结构,底层是数组。

slice的三个属性:指针、长度、容量

定义

[]T

slice的操作符

s[i:j](0<=i<=j<=cap(s))
func sliceTest1() {
	month := []string{1: "January", 2: "February", 3: "March", 4: "April", 5: "May",
		6: "June", 7: "July", 8: "August", 9: "September", 10: "October", 11: "November", 12: "December"}
	fmt.Println(month[4:7])
	summer := month[6:9]
	//fmt.Println(summer[:10])    //报错,索引不能超过
	endlessSummer := summer[:7] //赋值可以扩容6-》12
	fmt.Println(endlessSummer)
	nop := summer[:8] //报错,超出原切片大小
	fmt.Println(nop)
}

字符串与字节slice操作相识,结果上一个返回字符串,一个返回slice。

反转

func reverse(s []int) {
	for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
		s[i], s[j] = s[j], s[i]
	}
}
func main() {
	s := []int{0, 1, 2, 3, 4, 5}
	reverse(s[:2])
	reverse(s[2:])
	reverse(s)
	fmt.Println(s)
}

slice 只能直接与nil比较是否相等。

func equal(x, y []string) bool {
	if len(x) != len(y) {
		return false
	}
	for i := range x {
		if x[i] != y[i] {
			return false
		}
	}
	return true
}

内置函数make

make([]T,len)
make([]T,len,cap)

make创建无名数组,并返回他的一个slice。

4.2.1 append函数

func sliceTest3() {
	var runes []rune
	for _, r := range "Hello, 世界" {
		runes = append(runes, r)
	}
	fmt.Printf("%q\n", runes)
}

关于int的append

func appendInt(x []int, y int) []int {
	var z []int
	zlen := len(x) + 1
	if zlen <= cap(x) {
		z = x[:zlen]
	} else {
		zcap := zlen
		if zcap < 2*len(x) {
			zcap = 2 * len(x)

		}
		z = make([]int, zlen, zcap)
		copy(z, x)
	}
	z[len(x)] = y
	return z
}
func sliceTest4() {
	var x, y []int
	for i := 0; i < 10; i++ {
		y = appendInt(x, i)
		fmt.Printf("%d cap=%d\t%v\n", i, cap(y), y)
		x = y
	}
}
func main() {
	sliceTest4()
}

4.2.2 slice 就地修改

func nonempty(strings []string) []string {
	i := 0
	for _, s := range strings {
		if s != "" {
			strings[i] = s
			i++
		}
	}
	return strings[:i]

}
func sliceTest5() {
	data := []string{"one", "", "three"}
	data = nonempty(data)
	fmt.Printf("%q\n", data)
}

4.3 map

散列表,他是一个拥有键值对元素的无序集合。

在Go中,map是散列表的引用,map的类型是map[k]v,所有的键都有用相同的类型,所有的值都拥有相同的类型。

func mapTest1() {
	//ages := make(map[string]int)//方法一
	ages := map[string]int{ //方法二
		"alice":   31,
		"charlie": 34,
	}
	for k, v := range ages { //遍历的顺序是随机的
		fmt.Printf("%q->%d\n", k, v)
	}
	fmt.Println(ages["bob"])
	delete(ages, "alice")
	delete(ages, "bob")
	ages["bob"]++
	//&ages["bob"]//不可以获取地址
}

func main() {
	mapTest1()
}

map的零值是nil,且零值的map不能赋值

func mapTest2() {
	var ages map[string]int
	fmt.Println(ages == nil)    //true
	fmt.Println(len(ages) == 0) //true
	//ages["bob"] = 21//报错
	//必须先初始化
	ages = make(map[string]int)
	age, ok := ages["bob"]
	if !ok {
		fmt.Println("bob is not in the map")
	}
	fmt.Println(age)
	
}

map与slice一样,都无法通过==来判断是否相同。

func mapEqual(x, y map[string]int) bool {
	if len(x) != len(y) {
		return false
	}
	for k, xv := range x {
		if yv, ok := y[k]; !ok || yv != xv {//因为存在零值
			return false
		}
	}
	return true
}

Go没有集合数据类型,可以用map模拟

func dedup() {
	seen := make(map[string]bool)
	input := bufio.NewScanner(os.Stdin)
	for input.Scan() {
		line := input.Text()
		if !seen[line] {
			seen[line] = true
			fmt.Println(line)
		}
	}
	if err := input.Err(); err != nil {
		fmt.Fprintf(os.Stderr, "dedup:%v\n", err)
		os.Exit(1)
	}
}

直接将slice作为键是不可行的,因为slice无法比较,

var m = make(map[string]int)

func help(list []string) string {
	return fmt.Sprintf("%q", list)
}
func Add(list []string) {
	m[help(list)]++
}
func Count(list []string) int {
	return m[help(list)]
}
func mapTest3() {
	list := []string{
		"hello", "world", "hi", "bob", "hello",
	}
	Add(list)
	fmt.Println(Count(list))
}

func main() {
	mapTest3()

}

map的类型本身可以是复合类型

var graph = make(map[string]map[string]bool)

func addEdge(from, to string) {
	edges := graph[from]
	if edges == nil {
		edges = make(map[string]bool)
		graph[from] = edges
	}
	edges[to] = true
}
func hasEdge(from, to string) bool {
	return graph[from][to]
}
func mapTest4() {
	addEdge("a", "b")
	addEdge("a", "c")
	println(hasEdge("a", "c"))
}
func main() {
	mapTest4()

}

4.4 结构体

将多个任意类型变量组合成一个。

package main

import (
	"fmt"
	"time"
)

type Employee struct {
	ID        int
	Name      string
	Address   string
	Dob       time.Time
	Position  string
	Salary    int
	ManagerID int
	Say       func()
} //所有成员都是变量

func structTest1() {
	var dilbert Employee
	dilbert.ID = 1                    //所以可以直接访问
	position := &dilbert.Position     //可以获取地址
	*position = "Senior " + *position //指针访问
	employeeOfTheMonth := &dilbert
	employeeOfTheMonth.Position += " {proactive team player}" //可以直接用.访问等同于(*xxx).Position += ""
	fmt.Println(dilbert)
	fmt.Println(EmployeeByID(dilbert.ID).Position) //可以直接访问
	id := dilbert.ID
	EmployeeByID(id).Salary = 0 //直接访问
}
func EmployeeByID(id int) *Employee {
	//do...
	return nil
}


func main() {
	structTest1()
}

结构体中大写的变量可导出,小写的不行。

同一个结构体内不允许有与结构体同名,但可以有指针

type tree struct {
	value       int
	left, right *tree
}

func Sort(values []int) {
	var root *tree
	for _, v := range values {
		root = add(root, v)
	}
	appendValues(values[:0], root)
}

//中序遍历
func appendValues(values []int, t *tree) []int {
	if t != nil {
		values = appendValues(values, t.left)
		values = append(values, t.value)
		values = appendValues(values, t.right)
	}
	return values
}

//构造排序树
func add(t *tree, value int) *tree {
	if t == nil {
		t = new(tree)
		t.value = value
		return t
	}
	if value < t.value {
		t.left = add(t.left, value)

	} else {
		t.right = add(t.right, value)
	}
	return t
}

func main() {
	data := []int{2, 3, 1, 6, 7, 9, 4, 0, 5}
	Sort(data)
	fmt.Println(data)

}

4.4.1 结构体字面量

结构体类型的值可以通过结构体字面量来设置,

func structTest3() {
	a := Point{2, 3} //按顺序赋值,不太推荐,除非清晰明白
	fmt.Println(a)
	b := gif.GIF{LoopCount: 2} //指定赋值
	fmt.Println(b)
}

Go语言基础

可以作为参数和返回值

func Scale(p Point, factor int) Point {
	return Point{p.x * factor, p.y * factor}
}

但通常都是传指针

func Bonus(e *Employee, percent int) int {
	return e.Salary * percent / 100
}

4.4.2 结构体的比较

如果结构体的成员变量是可比较的,则可比较。否则不行。可比较则可作为map的key

	test := make(map[Point]int)
	fmt.Println(test)

4.4.3 结构体嵌套和匿名成员

Go有不同寻常的结构体嵌套机制

定义了圆和车轮

func testStruct4() {
	type Circle struct {
		X, Y, Radius int
	}
	type Wheel struct {
		X, Y, Radius, Spokes int
	}
	var w Wheel
	w.X = 9
	w.Y = 9
	w.Radius = 5
	w.Spokes = 20
}

重复太高了于是

func testStruct5() {
	type Point struct {
		X, Y int
	}
	type Circle struct {
		Center Point
		Radius int
	}
	type Wheel struct {
		Circle Circle
		Spokes int
	}
	var w Wheel
	w.Circle.Center.X=9
	w.Circle.Center.Y=9
	w.Circle.Radius = 5
	w.Spokes = 20
}

上面让程序变得清晰了,但是访问其成员太复杂了。

Go运行我们不带名称访问成员

func testStruct6() {
	type Point struct {
		X, Y int
	}
	type Circle struct {
		Point
		Radius int
	}
	type Wheel struct {
		Circle
		Spokes int
	}
	var w Wheel
	w.X = 9
	w.Y = 9
	w.Radius = 5
	w.Spokes = 20
	//w = Wheel{8, 8, 5, 20}                       //编译错误
	//w = Wheel{X: 8, Y: 8, Radius: 5, Spokes: 20} //编译错误
}

以下两种等价

w = Wheel{Circle{Point{8, 8}, 5}, 20}
w = Wheel{
    Circle: Circle{
        Point:  Point{X: 8, Y: 8},
        Radius: 5,
    },
    Spokes: 20,
}
fmt.Printf("%#v\n", w)

4.5 JSON

标准库支持,

GO to JSON

package main

import (
	"encoding/json"
	"fmt"
	"log"
)

type Movie struct {
	Title string
	Year  int  `json:"released"`
	Color bool `json:"color,omitempty"`
	Actor []string
}

var movies = []Movie{
	{Title: "Casablanca", Year: 1942, Color: false,
		Actor: []string{"Humphrey Bogart", "Ingrid Bergman"}},
	{Title: "Cool Hand Luke", Year: 1967, Color: true,
		Actor: []string{"Paul Newman"}},
	{Title: "Bullitt", Year: 1968, Color: true,
		Actor: []string{"Steve McQueen", "Jacqueline Bisset"}},
}

func testJson1() {
	data, err := json.Marshal(movies)
	if err != nil {
		log.Fatal("JSON marshaling failed:%s", err)
	}
	fmt.Printf("%s\n", data)
	//[{"Title":"Casablanca","released":1942,"Actor":["Humphrey Bogart","Ingrid Bergman"]},{"Title":"Cool Hand Luke","released":1967,"color":true,"Actor":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true,"Actor":["Steve McQueen","Jacqueline Bisset"]}]
	data, err = json.MarshalIndent(movies, "", " ")
	if err != nil {
		log.Fatal("JSON marshaling failed:%s", err)
	}
	fmt.Printf("%s", data)

}

MarshalIndent(),可以带缩进显示。

Go通过反射,可以导出成员为JSON字段,取别名

Year int `json:"released"`
Color bool `json:"color,omitempty"`//为空就不转到json中去

解码

var titles []struct{ Title string }
if err := json.Unmarshal(data, &titles); err != nil {
    log.Fatal("JSON unmarshaling failed: %s", err)
}
fmt.Println(titles)//[{Casablanca} {Cool Hand Luke} {Bullitt}]

用Go解析一个请求

const IssuesURL = "https://api.github.com/search/issues"

type IssuesSearchResult struct {
	TotalCount int `json:"total_count"`
	Items      []*Issue
}
type Issue struct {
	Number    int
	HTMLURL   string `json:"html_url"`
	Title     string
	State     string
	User      *User
	CreatedAt time.Time `json:"created_at"`
	Body      string
}
type User struct {
	Login   string
	HTMLURL string `json:"html_url"`
}

func SearchIssues(terms []string) (*IssuesSearchResult, error) {
	q := url.QueryEscape(strings.Join(terms, " "))
	resp, err := http.Get(IssuesURL + "?q=" + q)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != http.StatusOK {
		resp.Body.Close()
		return nil, fmt.Errorf("search query faild:%s", resp.Status)
	}

	var result IssuesSearchResult
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { //流式解码Decode
		resp.Body.Close()
		return nil, err
	}
	resp.Body.Close()
	return &result, nil
}

func main() {
	//testJson1()
	result, err := SearchIssues(os.Args[1:])
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%d issues:\n", result.TotalCount)
	for _, item := range result.Items {
		fmt.Printf("#%-5d %9.9s %.55s\n", item.Number, item.User.Login, item.Title)

	}
}

调用

go run .\5_json.go repo:golang/go is:open json decoder

4.6 文本和HTML模板

模板是一个字符串或文件-{{…}}被称为操作。

package main

import (
	"fmt"
	"golearn/git"
	"log"
	"os"
	"text/template"
	"time"
)

const templ = `{{.TotalCount}} issues:
{{range.Items}}--------------------------
Number: {{.Number}}
User: {{.User.Login}}
Title: {{.Title | printf "%.64s"}}
Age:   {{.CreatedAt | daysAgo}} days
{{end}}` // |的输出当下一个函数的输入,print 与fmt.Sprintf同义
func daysAgo(t time.Time) int {
	return int(time.Since(t).Hours() / 24)
}
func new1() {
	report, err := template.New("report").
		Funcs(template.FuncMap{"daysAgo": daysAgo}).Parse(templ)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println(report)
}

//template.Must解决解析错误问题
var report = template.Must(template.New("issuelist").
	Funcs(template.FuncMap{"daysAgo": daysAgo}).Parse(templ))

func main() {
	result, err := git.SearchIssues(os.Args[1:])
	if err != nil {
		log.Fatal(err)
	}
	if err := report.Execute(os.Stdout, result); err != nil {
		log.Fatal(err)
	}
}

第五章 函数

本章的案例是爬虫。

5.1 函数声明

func name(parameter-list)(result-list){//没有返回值()可以省略
	body
}

形参声明类型指定。

func f(i,j,k int,s,t string){}

返回值

func add(x int,y int)(z int){
	z = x+y
	return 
}

5.2 递归

使用了golang.org/x/net/html


5.3 多返回值

func findLinks(url string) ([]string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		resp.Body.Close()
		return nil, fmt.Errorf("getting %s:%s", url, resp.Status)
	}
	return nil, nil
}

5.4 错误

一般的

value,ok := cache.Lookup(key)
if !ok{
	// ...cache[key] ...
}

5.4.1 错误处理策略

  • 错误传播

    resp,err := http.Get(url)
    if err != nil{
    	return nil,err
    }
    
  • 短时重复,超过次数再报错

    func WaitForServer(url string) error {
    	const timeout = 1 * time.Minute
    	deadline := time.Now().Add(timeout)
    	for tries := 0; time.Now().Before(deadline); tries++ {
    		_, err := http.Head(url)
    		if err != nil {
    			return nil
    		}
    		log.Printf("Server not responding (%s); retrying...", err)
    		time.Sleep(time.Second << uint(tries)) //指数退避策略
    	}
    	return fmt.Errorf("Server %s failed to respond after %s", url, timeout)
    }
    
    
    
  • 错误可输出,然后停止

    func testErr1() {
    	if err := WaitForServer("WWW.rao2gang1.top"); err != nil {
    		//fmt.Println(err)
    		//os.Exit(1)
    		log.Fatalf("Site is down:%v\n", err)//等价
    	} else {
    		fmt.Println("OK")
    	}
    }
    
    log.SetPrefix("wait: ")
    log.SetFlags(0)
    
  • 只记录错误,继续运行

    if err := Ping(); err != nil {
        log.Printf("ping failed %v", err)
    }
    
    

5.4.2 文件结束标识

var EOF = errors.New("EOF")

in := bufio.NewReader(os.Stdin)

for {
r, _, err := in.ReadRune()
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("read failed:%v", err)
}
// 使用
fmt.Println(r)
}

5.5 函数变量

函数是一种变量

package main

import "fmt"

func square(n int) int {
	return n * n
}
func negative(n int) int {
	return -n
}
func product(m, n int) int {
	return m * n
}
func testFun1() {
	f := square
	fmt.Println(f(3))
	fmt.Println(f)
	f = negative
	fmt.Println(f(3))
	fmt.Printf("%T\n", f)
	//f = product //f 类型已定,不能将func(int,int)int 赋值给func(int)int
	var f1 func(int) int
	//f1(3)//调用空函数,宕机
	if f1 != nil {
		println(f1(3))
	}
}
func main() {
	testFun1()
}

函数本身不可比,所以不能当map的key

函数本身可以作为参数传递

func add1(r rune) rune { return r + 1 }
func funcTest2() {
	fmt.Println(strings.Map(add1, "HAL-9000"))
	fmt.Println(strings.Map(add1, "VMS"))
	fmt.Println(strings.Map(add1, "Adminx"))
}
func plusTwo(n int) int {
	return n + 2
}
func handler(n int, f func(int) int) {
	fmt.Println(f(n))
}
func testFun3() {
	handler(2, plusTwo)
}

5.6 匿名函数

func testAno1() {
	fmt.Println(strings.Map(func(r rune) rune {
		return r + 1
	}, "hello"))
}

闭包

func squares() func() int {
	var x int
	return func() int {
		x++
		return x * x
	}
}
func testAno2() {
	f := squares()
	fmt.Println(f()) //1
	fmt.Println(f()) //4
	fmt.Println(f()) //9
}
func main() {
	testAno2()
}

拓扑排序例子

var prereqs = map[string][]string{
	"algorithms": {"data structures"},
	"calculus":   {"linear algebra"},
	"compilers": {
		"data structures",
		"formal languages",
		"computer organization",
	},
	"data structures":       {"discrete math"},
	"databases":             {"data structures"},
	"discrete math":         {"intro to programming"},
	"formal languages":      {"discrete math"},
	"networks":              {"operating systems"},
	"operating systems":     {"data structures", "computer organization"},
	"programming languages": {"data structures", "computer organization"},
}

//拓扑排序
func topoSort(m map[string][]string) []string {
	var order []string                //记录顺序的表
	seen := make(map[string]bool)     //记录遍历过的元素
	var visitAll func(items []string) //定义函数
	visitAll = func(items []string) { //递归函数
		for _, item := range items { //遍历所有的keys
			if !seen[item] { //如果已经遍历过则返回
				seen[item] = true
				visitAll(m[item])           //递归
				order = append(order, item) //将该项加入到顺序表里
			}
		}
	}
	var keys []string //记录所有的key
	for key := range m {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	visitAll(keys) //调用匿名函数
	return order
}

func testAno3() {
	for i, course := range topoSort(prereqs) {
		fmt.Printf("%d:\t%s\n", i+1, course)
	}
}

func main() {
	testAno3()
}

5.7 变长函数


func sum(vals ...int) int {
	total := 0
	for _, val := range vals {
		total += val
	}
	return total
}
func testLong1() {
	fmt.Println(sum())
	fmt.Println(sum(3))
	fmt.Println(sum(1, 2, 3, 4))
}
func main() {
	testLong1()
}

5.8 延迟调用

defer会延时调用,会在调用它的函数返回前调用该函数

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

hello world

对于多个defer 栈式调用

func main() {
defer fmt.Println("first")
defer fmt.Println("second")
defer fmt.Println("third")
}
third
second
first

5.9 宕机

5.10 恢复

第六章 方法

封装、组合

6.1 方法声明

函数和方法的区别,

方法可以绑定到其他类型上,如切片。

package main

import (
	"fmt"
	"math"
)

type Point struct {
	X, Y float64
}
type Path []Point

func (path Path) Distance() float64 {
	sum := 0.0
	for i := range path {
		if i > 0 {
			sum += path[i-1].Distance(path[i])
		}
	}
	return sum
}

func Distance(p, q Point) float64 {
	return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func (p Point) Distance(q Point) float64 {
	return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func main() {
	p := Point{3, 4}
	q := Point{6, 8}
	fmt.Println(Distance(q, p))
	fmt.Println(q.Distance(p))
	perim := Path{
		{1, 1},
		{5, 1},
		{5, 4},
		{1, 1},
	}
	fmt.Println(perim.Distance())//12

}

6.2 指针接收者

定义指针接收,必须用指针形式的变量调用该方法。

指针接收者会改变原来的值,而普通接收者不会。真正接收者才是真正意义上的this/self指针

package main

import "fmt"

type Point struct {
	X, Y float64
}

func (p *Point) ScaleBy(factor float64) {
	p.X *= factor
	p.Y *= factor
}

func PointerReceiverTest1() {
	r := &Point{1, 2}
	r.ScaleBy(2)
	fmt.Println(*r)
	p := Point{1, 2}
	pptr := &p
	pptr.ScaleBy(2)
	fmt.Println(*pptr)
	q := Point{1, 2}
	(&q).ScaleBy(2)
	fmt.Println(q)
}
func main() {
	PointerReceiverTest1()
}

对于指针变量,可以隐式的转化为非指针变量调用非指针接收者方法。

type Point struct {
	X, Y float64
}

func (p *Point) ScaleBy(factor float64) {
	p.X *= factor
	p.Y *= factor
}
func (p Point) Distance(q Point) float64 {
	return math.Hypot(q.X-p.X, q.Y-p.Y)
}
func PointerReceiverTest2() {
	r := &Point{2, 2.5}
	r.ScaleBy(2)
	s := Point{1, 1}
    fmt.Println(r.Distance(s))//相当于 (*r).Distance(s)
    s.ScaleBy(2)//相当于 (&s).ScaleBy(2)
	fmt.Println(s)
}

nil 是一个合法的接受者

type IntList struct {
	Value int
	Tail  *IntList
}

func (list *IntList) Sum() int {
	if list == nil {
		return 0
	}
	return list.Value + list.Tail.Sum() //递归
}
func PointerReceiverTest3() {
	list := IntList{1, &IntList{2, nil}}
	println(list.Sum())//3
	list2 := new(IntList)
	println(list2.Sum())//0
}

6.3 通过结构体内嵌组成类型

package main

import (
	"fmt"
	"image/color"
	"math"
)

type Point2 struct {
	X, Y float64
}

func (p Point2) Distance(q Point2) float64 {
	return math.Hypot(q.X-p.X, q.Y-p.Y)
}

type ColoredPoint struct {
	Point2
	Color color.RGBA
}

func (p *Point2) ScaleBy(factor float64) {
	p.X *= factor
	p.Y *= factor
}
func InsideTest1() {
	var cp ColoredPoint
	cp.X = 1
	cp.Y = 2
	fmt.Println(cp.Point2.X)
	fmt.Println(cp.Y)
	red := color.RGBA{255, 0, 0, 255}
	blue := color.RGBA{0, 0, 255, 255}
	var p = ColoredPoint{Point2{1, 2}, red}
	var q = ColoredPoint{Point2{2, 3}, blue}
	fmt.Println(p.Distance(q.Point2)) //这里必须是Point类型
	p.ScaleBy(2)
	fmt.Println(p)

}
func main() {
	InsideTest1()
}

6.4 方法变量与表达式

方法变量

p:=Point{1,2}
q:=Point{1,2}
distanceFromP := p.Distance
fmt.Println(distanceFromP(q))

表达式

p:=Point{1,2}
q:=Point{1,2}
distance := Point.Distance
fmt.Println(distance(p,q))

6.5 位向量

位向量使用一个无符号整型值的Slice,每位代表集合的一个元素。

x / 64 , x % 64 , b i t [ x / 64 ] = x % 64 x/64,x\%64,bit[x/64]=x \%64 x/64,x%64,bit[x/64]=x%64

package main

import (
	"bytes"
	"fmt"
)

type IntSet struct {
	words []uint64
}

func (s *IntSet) Has(x int) bool {
	word, bit := x/64, uint(x%64)
	return word < len(s.words) && s.words[word]&(1<<bit) != 0
}
func (s *IntSet) Add(x int) {
	word, bit := x/64, uint(x%64)
	for word >= len(s.words) {
		s.words = append(s.words, 0)
	}
	s.words[word] |= 1 << bit
}
func (s *IntSet) UnionWith(t *IntSet) {
	for i, tword := range t.words {
		if i < len(s.words) {
			s.words[i] |= tword
		} else {
			s.words = append(s.words, tword)
		}
	}
}
func (s *IntSet) String() string {
	var buf bytes.Buffer
	buf.WriteByte('{')
	for i, word := range s.words {
		if word == 0 {
			continue
		}
		for j := 0; j < 64; j++ {
			if word&(1<<uint(j)) != 0 {
				if buf.Len() > len("{") {
					buf.WriteByte(' ')
				}
				fmt.Fprintf(&buf, "%d", 64*i+j)
			}

		}
	}
	buf.WriteByte('}')
	return buf.String()
}
func BitVictorTest() {
	var x, y IntSet
	x.Add(1)
	x.Add(144)
	x.Add(90)
	fmt.Println(x.String())
	y.Add(9)
	y.Add(1)
	fmt.Println(y.String())
	x.UnionWith(&y)
	fmt.Println(x.String())
	fmt.Println(x.Has(9))
}
func main() {
	BitVictorTest()
}
package main

import (
	"bytes"
	"fmt"
)

type IntSet struct {
	words []uint64
}

func (s *IntSet) Has(x int) bool {
	word, bit := x/64, uint(x%64)
	return word < len(s.words) && s.words[word]&(1<<bit) != 0
}
func (s *IntSet) Add(x int) {
	word, bit := x/64, uint(x%64)
	for word >= len(s.words) {
		s.words = append(s.words, 0)
	}
	s.words[word] |= 1 << bit
}
func (s *IntSet) UnionWith(t *IntSet) {
	for i, tword := range t.words {
		if i < len(s.words) {
			s.words[i] |= tword
		} else {
			s.words = append(s.words, tword)
		}
	}
}
func (s *IntSet) String() string {
	var buf bytes.Buffer
	buf.WriteByte('{')
	for i, word := range s.words {
		if word == 0 {
			continue
		}
		for j := 0; j < 64; j++ {
			if word&(1<<uint(j)) != 0 {
				if buf.Len() > len("{") {
					buf.WriteByte(' ')
				}
				fmt.Fprintf(&buf, "%d", 64*i+j)
			}

		}
	}
	buf.WriteByte('}')
	return buf.String()
}
func BitVictorTest() {
	var x, y IntSet
	x.Add(1)
	x.Add(144)
	x.Add(90)
	fmt.Println(x.String())
	y.Add(9)
	y.Add(1)
	fmt.Println(y.String())
	x.UnionWith(&y)
	fmt.Println(x.String())
	fmt.Println(x.Has(9))
}
func main() {
	BitVictorTest()
}

6.6 封装

大小写控制

第七章 接口

其他类型行为的抽象,不用绑定到特定类型上。

Go接口是隐式实现的。

接口是多态的实现?

7.1 接口即约定

接口是抽象类型,不用知道他是什么,只用知道他可以做什么。

为ByteCounter定义方法,可以使用Fprintf向里面输入值。

package main

import "fmt"

type ByteCounter int

func (c *ByteCounter) Write(p []byte) (int, error) {
	*c += ByteCounter(len(p))
	return len(p), nil
}

func testByteCounter() {
	var c ByteCounter
	c.Write([]byte("hello"))
	fmt.Println(c)
	c = 0
	var name = "Tom"
	fmt.Fprintf(&c, "hello, %s", name)

	fmt.Println(c)
}

func main() {
	testByteCounter()
}

定义一个接口

type Stringer interface {
	String() string
}

7.2 接口类型

声明,注意,实现一个接口,必须实现所有方法。

package main

import "io"

type Reader interface {
	Read(p []byte) (n int, err error)
}
type Closer interface {
	Close() error
}
type ReadWriter interface {
	Reader
	io.Writer
}

type ReadWriterCloser interface {
	Reader
	io.Writer
	Closer
}

7.3 实现接口

接口赋值规则:当一个表达式实现了一个接口时,这个表达式才可以赋值给接口。

func testInterface() {
	var w io.Writer
	w = os.Stdout
	w = new(bytes.Buffer)
	w = time.Second // 错误,time.Duration 没有Write方法
}

空接口: 可以接受任何类型的值,

var any1 interface{}//可以接受任意类型的值

func main() {
	any1 = true
	any1 = 1.2
	any1 = "hello"
	any1 = map[string]int{"one": 1}
	any1 = new(bytes.Buffer)
	fmt.Println(any1)
	
}
type Artifact interface {
	Title() string
	Creators() []string
	Created() time.Time
}
type Text interface {
	Pages() int
	Words() int
	PageSize() int
}
type Audio interface {
	Stream() (io.ReadCloser, error)
	RunningTime() time.Duration
	Format() string
}

7.4 使用flag.Value解析参数

解析命令行参数的功能。

import (
	"flag"
	"fmt"
	"time"
)

func testResolution() {
	var period = flag.Duration("period", 1*time.Second, "sleep period")
	flag.Parse()
	fmt.Printf("Sleepng for %v...", *period)
	fmt.Println()
}
func main() {
	testResolution()//go run sleep -period 2
}

7.5 接口值

接口值,代表着两个含义,一个是该类型的值,一个是具体类型的值,简单来说就是声明一个接口该接口需要有一个类型,同时该接口需要赋一个具体的值。

如下就是三个值

var w io.Writer// nil
w = os.Stdout
w = new(bytes.Buffer)
w = nil
fmt.Print(w)

对于

w=os.Stdout

等价于

w=io.Writer(os.Stdout)

Go语言基础

同样的对于

w= new(bytes.Buffer)

Go语言基础

对于

var X interface{} = time.Now()
fmt.Printf("%T %v", X, X)//time.Time 2023-04-15 14:03:00.1078667 +0800 CST m=+0.001798101

相当于

Go语言基础

接口的比较,取决于接口的类型是否可以比较。

var x interface{} = []int{1, 2, 3}
fmt.Println(x == x)//报错,[]int类型不可比较

7.6 使用sort.Interface来排序

对于一个sort接口需要实现三个方法

type Interface interface{
	Len() int
	Less(i,j int) bool
	Swap(i,j int)
}

例子

package main

import (
	"fmt"
	"sort"
)

type StringSlice []string

func (p StringSlice) Len() int {
	return len(p)
}
func (p StringSlice) Less(i, j int) bool {
	return p[i] < p[j]
}
func (p StringSlice) Swap(i, j int) { //不是指针类型可以吗
	p[i], p[j] = p[j], p[i]
}

func main() {
	names := []string{"Tom", "Bob", "Alice"}
	slic := StringSlice(names)
	sort.Sort(slic)
	fmt.Println(slic)
	fmt.Println(names)
	slic.Swap(0, 1)
	fmt.Println(slic)
	fmt.Println(names)
	//[Alice Bob Tom]
	//[Alice Bob Tom]
	//[Bob Alice Tom]
	//[Bob Alice Tom]

}

绑定了与原来的,同时,非指针接收者也可以改变原来的值,不知道为什么。如下代码不是指针就不行。

type testInt struct {
	value int
}

func (p testInt) scale(factor int) {
	p.value *= factor
}
a := testInt{
		value: 3,
	}
a.scale(3)
fmt.Println(a)

7.7 http.Handler接口

pass

7.8 error接口

定义了error接口,并给出了对于操作方法。

7.9 示例

pass

7.10 类型断言

作用与接口值上的操作,用法x.(T)

package main

import (
	"bytes"
	"fmt"
	"io"
	"os"
)

func main() {
	var w io.Writer
	w = os.Stdout
	f := w.(*os.File)      //成功
	fmt.Printf("%T\n", f)  //*os.File
	c := w.(*bytes.Buffer) //panic: interface conversion: io.Writer is *os.File, not *bytes.Buffer
	fmt.Println(c)
}

健壮性考虑:

import (
	"bytes"
	"fmt"
	"io"
	"os"
)

func main() {
	var w io.Writer
	w = os.Stdout
	if f, ok := w.(*os.File); ok { //成功
		fmt.Printf("%T\n", f) //*os.File

	}
	if c, ok := w.(*bytes.Buffer); ok { //nothing
		fmt.Println(c)
	}

}

第八章 Goroutine和通道

{{}}

Go并发由,Goroutine 加通道实现,支持CSP模型

8.1 Goroutine

可以把他当作多线程,但不是多线程。

以下程序,在主线程结束前,会一直打印-\|/,

package main

import (
	"fmt"
	"time"
)

func testGoroutine() {
	go spinner(100 * time.Millisecond)
	const n = 45
	fibN := fib(n)
	fmt.Printf("\r Fibonacci(%d) = %d", n, fibN)
}

func main() {
	testGoroutine()
}
func spinner(delay time.Duration) {
	for {
		for _, r := range `-\|/` {
			fmt.Printf("\r%c", r)
			time.Sleep(delay)
		}
	}
}
func fib(x int) int {
	if x < 2 {
		return x
	}
	return fib(x-1) + fib(x-2)
}

8.2 并发时钟服务器

先看顺序时钟服务器版本

下面程序会依次为客服端服务,当一个用户退出后才会为下一个用户服务

package main

import (
	"io"
	"log"
	"net"
	"time"
)

func main() {
	listener, err := net.Listen("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println(err)
			conn.Close() //关闭当前错误链接
		}
		handleConn(conn) //处理
	}
}

func handleConn(conn net.Conn) {
	defer conn.Close() //延迟关闭
	for {
		_, err := io.WriteString(conn, time.Now().String()+"\n")
		if err != nil {
			return
		}
		time.Sleep(1 * time.Second)
	}
}

实现一个nc,telnet工具

package main

import (
	"io"
	"log"
	"net"
	"os"
)

func main() {
	conn, err := net.Dial("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	mustCopy(os.Stdout, conn)
}

func mustCopy(dst *os.File, src net.Conn) {
	if _, err := io.Copy(dst, src); err != nil {
		log.Fatal(err)
	}
}

并发版,只需要加上go即可,同时服务

go handleConn(conn) //处理

8.3 回声服务器

即发什么给服务器,服务器返回什么

package main

import (
	"bufio"
	"fmt"
	"log"
	"net"
	"strings"
	"time"
)

func main() {
	listener, err := net.Listen("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println(err)
			conn.Close() //关闭当前错误链接
		}
		go handleConn1(conn) //处理s
	}
}

func echo(c net.Conn, shout string, delay time.Duration) {
	fmt.Fprintln(c, "\t", strings.ToUpper(shout))
	time.Sleep(delay)
	fmt.Fprintln(c, "\t", shout)
	time.Sleep(delay)
	fmt.Fprintln(c, "\t", strings.ToLower(shout))
}

func handleConn1(conn net.Conn) {
	input := bufio.NewScanner(conn)
	for input.Scan() {
		echo(conn, input.Text(), 1*time.Second)
	}
	//忽略错误
	conn.Close()
}

客户端

package main

import (
	"io"
	"log"
	"net"
	"os"
)

func main() {
	conn, err := net.Dial("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	go mustCopy1(os.Stdout, conn)
	mustCopy1(conn, os.Stdin)
}

func mustCopy1(dst io.Writer, src io.Reader) {
	if _, err := io.Copy(dst, src); err != nil {
		log.Fatal(err)
	}
}

8.4 通道

Goroutine是并发体,通道是并发体沟通的桥梁

关键:创建 发送与接收关闭

ch := make(chan int)
x := 2
ch <- x
x = <-ch
close(ch)

以上是无缓冲通道

有缓冲

ch:=make(chan int,3)

8.4.1 无缓冲通道

无缓冲通道也成为同步通道

当消息无内容时,我们一般用空结构体表示。当有内容时称为事件。

package main

import (
	"io"
	"log"
	"net"
	"os"
)

func main() {
	conn, err := net.Dial("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	done := make(chan struct{})
	go func() {
		io.Copy(os.Stdout, conn)
		log.Println("Done")
		done <- struct{}{} //指示goroutine
	}()
	mustCopy2(conn, os.Stdin)
	conn.Close()
	<-done //等待后台goroutine完成
}

func mustCopy2(dst io.Writer, src io.Reader) {
	if _, err := io.Copy(dst, src); err != nil {
		log.Fatal(err)
	}
}

8.4.2 管道

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QDoHWBkg-1687506266635)(./assets/image-20230415155931075.png)]

package main

import (
	"fmt"
	"time"
)

func main() {
	naturals := make(chan int)
	squares := make(chan int)

	//counter
	go func() {
		for x := 0; ; x++ {
			naturals <- x
			time.Sleep(1 * time.Second)
		}
	}()
	//squarer
	go func() {
		for {
			x := <-naturals
			squares <- x * x
		}
	}()
	for {
		fmt.Println(<-squares)
	}
}

关闭通道

package main

import (
	"fmt"
	"time"
)

func main() {
	naturals := make(chan int)
	squares := make(chan int)

	//counter
	go func() {
		for x := 0; x < 10; x++ {
			naturals <- x
			time.Sleep(1 * time.Second)
		}
		close(naturals)
	}()
	//squarer
	go func() {
		for x := range naturals {
			squares <- x * x
		}
		close(squares)
	}()
	for x := range squares {
		fmt.Println(x)
	}
}

8.4.3 单项通道

指数据的方向是固定的

package main

import (
	"fmt"
	"time"
)

func counter(out chan<- int) {
	for x := 0; x < 10; x++ {
		out <- x
		time.Sleep(1 * time.Second)
	}
	close(out)
}
func squarer(out chan<- int, in <-chan int) {
	for v := range in {
		out <- v * v
	}
	close(out)
}
func printer(in <-chan int) {
	for v := range in {
		fmt.Println(v)
	}
}
func main() {
	naturals := make(chan int)
	squarers := make(chan int)
	go counter(naturals)
	go squarer(squarers, naturals)
	printer(squarers)
}

8.4.4 缓冲通道

当缓冲区未满时,无阻塞的传递值,满了才阻塞。

ch = make(chan string,3)
ch<-"A"
ch<-"B"
ch<-"C"

8.5 并行循环

简单来讲,就是for循环中的执行内容(函数)用go包裹。

8.6 爬虫示例

package main

import (
	"fmt"
	"golang.org/x/net/html"
	"log"
	"net/http"
	"os"
)

func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
	if pre != nil {
		pre(n)
	}
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		forEachNode(c, pre, post)
	}
	if post != nil {
		post(n)
	}

}

func Extract(url string) ([]string, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode != http.StatusOK {
		resp.Body.Close()
		return nil, fmt.Errorf("getting %s: %s", url, resp.Status)
	}
	doc, err := html.Parse(resp.Body)
	resp.Body.Close()
	if err != nil {
		return nil, fmt.Errorf("parsing %s as HTML: %v", url, err)
	}
	var links []string
	visitNode := func(n *html.Node) {
		if n.Type == html.ElementNode && n.Data == "a" {
			for _, a := range n.Attr {
				if a.Key != "href" {
					continue
				}
				link, err := resp.Request.URL.Parse(a.Val)
				if err != nil {
					continue
				}
				links = append(links, link.String())
			}
		}
	}
	forEachNode(doc, visitNode, nil)
	return links, nil
}

func crawl(url string) []string {
	fmt.Println(url)
	list, err := Extract(url)
	if err != nil {
		log.Print(err)
	}
	return list
}

func main() {
	worklist := make(chan []string)
	go func() { worklist <- os.Args[1:] }()
	seen := make(map[string]bool)
	for list := range worklist {
		for _, link := range list {
			if !seen[link] {
				seen[link] = true
				go func(link string) {
					worklist <- crawl(link)
				}(link)
			}
		}
	}
}

没有学完,atom包找不到

8.7 select 多路复用

package main

import (
	"fmt"
	"os"
	Time "time"
)

func main() {
	fmt.Println("Commencing countdown. Press return to abort")

	abort := make(chan struct{})

	go func() {
		os.Stdin.Read(make([]byte, 1))
		abort <- struct{}{}
	}()
	select {
	case <-Time.After(10 * Time.Second):
		//nothing
	case <-abort:
		fmt.Println("Launch aborted!")
		return
	}
	launch()

}

func launch() {
	fmt.Println("Launch")
}

8.8 并发遍历目录

{{}}

package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	Time "time"
)

func walkDir(dir string, fileSize chan<- int64) {
	for _, entry := range dirents(dir) {
		if entry.IsDir() {
			subdir := filepath.Join(dir, entry.Name())
			walkDir(subdir, fileSize)
		} else {
			fileSize <- entry.Size()
		}
	}
}

func dirents(dir string) []os.FileInfo {
	entries, err := ioutil.ReadDir(dir)
	if err != nil {
		fmt.Fprintf(os.Stderr, "du1:%v\n", err)
		return nil
	}
	return entries

}

func testDir() {
	flag.Parse()
	roots := flag.Args()
	if len(roots) == 0 {
		roots = []string{"."}
	}
	fileSize := make(chan int64)

	go func() {
		for _, root := range roots {
			walkDir(root, fileSize)
		}
		close(fileSize)
	}()
	var nfiles, nbytes int64
	for size := range fileSize {
		nfiles++
		nbytes += size
	}
	printDiskUsage(nfiles, nbytes)
}

var verbose = flag.Bool("v", false, "show verbose progress messages")

func main() {
	flag.Parse()
	roots := flag.Args()
	if len(roots) == 0 {
		roots = []string{"."}
	}
	fileSize := make(chan int64)

	go func() {
		for _, root := range roots {
			walkDir(root, fileSize)
		}
		close(fileSize)
	}()
	var tick <-chan Time.Time
	if *verbose {
		tick = Time.Tick(500 * Time.Millisecond)
	}
	var nfiles, nbytes int64

loop:
	for {
		select {
		case size, ok := <-fileSize:
			if !ok {
				break loop
			}
			nfiles++
			nbytes += size

		case <-tick:
			printDiskUsage(nfiles, nbytes)
		}

	}
	printDiskUsage(nfiles, nbytes)

}

func printDiskUsage(nfiles, nbytes int64) {
	fmt.Printf("%d files %.1f GB\n", nfiles, float64(nbytes)/1e9)
}

8.9 取消

8.10 示例:聊天服务器

4个goroutine,

  • 主goroutine
  • 广播goroutine
  • 连接处理goroutine
  • 客户写入goroutine

主goroutine

package main

import (
	"log"
	"net"
)

func main() {
	listener, err := net.Listen("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	go broadcaster()

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println(err)
			continue
		}
		go handleConnChat(conn)
	}
}

handle

package main

import (
	"bufio"
	"fmt"
	"net"
)

func handleConnChat(conn net.Conn) {

	ch := make(chan string)
	go clientWriter(conn, ch)

	who := conn.RemoteAddr().String()
	ch <- "You are" + who
	messages <- who + " has arrived"
	entering <- ch
	input := bufio.NewScanner(conn)
	for input.Scan() {
		messages <- who + ":" + input.Text()
	}
	leaving <- ch
	messages <- who + " has left"
	conn.Close()
}
func clientWriter(conn net.Conn, ch <-chan string) {
	for msg := range ch {
		fmt.Fprintln(conn, msg)
	}
}

broadcaster

package main

type client chan<- string

var (
	entering = make(chan client)
	leaving  = make(chan client)
	messages = make(chan string)
)

func broadcaster() {
	clients := make(map[client]bool)
	for {
		select {
		case msg := <-messages:
			//广播消息
			for cli := range clients {
				cli <- msg
			}
		case cli := <-entering:
			clients[cli] = true
		case cli := <-leaving:
			delete(clients, cli)
			close(cli)
		}

	}
}

client

package main

import (
	"io"
	"log"
	"net"
	"os"
)

func main() {
	conn, err := net.Dial("tcp", "localhost:8000")
	if err != nil {
		log.Fatal(err)
	}
	done := make(chan struct{})
	go func() {
		io.Copy(os.Stdout, conn)
		log.Println("Done")
		done <- struct{}{} //指示goroutine
	}()
	mustCopy2(conn, os.Stdin)
	conn.Close()
	<-done //等待后台goroutine完成
}

func mustCopy2(dst io.Writer, src io.Reader) {
	if _, err := io.Copy(dst, src); err != nil {
		log.Fatal(err)
	}
}

第九章 使用共享变量实现并发

{{}}

9.1 竞态

goroutine对应的功能是并行运行的,也就是说,对应的变量的访问顺序是未知的。

并发安全:在并发中也能正确的工作

并发问题:死锁、活锁、资源耗尽,

竞态:多个goroutine交错访问,无法正确的返回结果,

例如一个共享账户

package Shared

var balance int

func Deposit(amount int) {
	balance = balance + amount
}

func Balance() int {
	return balance
}

执行以下代码

go func() {
    bank.Deposit(200)
    fmt.Println("=", bank.Balance())
}()

go bank.Deposit(100)

其结果是未知的。

存在两个写,一个读,重复写导致某一个人的写被覆盖。

同样的

var x []int
go func() {x=make([]int,10)}()
go func() {x=make([]int,10000000)}()
x[9999999]=1//可能异常

这段代码可能异常。

数据竞态发生于两个goroutine并发读写同一个变量并且至少其中一个是写入时。

三种解决方法:

  1. 不要写变量

  2. 避免多个goroutine访问同一个变量

    重写银行案例

    package Shared
    
    var balance int
    var deposits = make(chan int) //发送存款
    var balances = make(chan int) //接收余额
    
    func Deposit(amount int) {
    	deposits <- amount
    }
    
    func Balance() int {
    	return <-balances
    }
    
    func teller() {
    	var balance int //被限制在teller goroutine中
    	for {
    		select {
    		case amount := <-deposits:
    			balance += amount
    		case balances <- balance:
    
    		}
    	}
    }
    
    func init() {
    	go teller() //启动监控goroutine
    }
    
    

    串行受限,同一时间只有一个routine访问该地址

    
    type Cake struct {
    	state string
    }
    
    func baker(cooked chan<- *Cake) {
    	for {
    		cake := new(Cake)
    		cake.state = "cooked"
    		cooked <- cake //baker 不再访问 cake变量
    	}
    }
    
    func icer(iced chan<- *Cake, cooked <-chan *Cake) {
    	for cake := range cooked {
    		cake.state = "iced"
    		iced <- cake //icer 不再访问 cake变量
    	}
    }
    
    
  3. 互斥

9.2 互斥锁

使用互斥锁保证同时只有一个线程访问相关资源

var (
	sema    = make(chan struct{}, 1) //用来保证balance的二进制信号
	balance int
)

func Deposit(amount int) {
	sema <- struct{}{} //获取令牌
	balance = balance + amount
	<-sema //释放令牌
}
func Balance() int {
	sema <- struct{}{}
	b := balance
	<-sema
	return b
}

由于互斥锁模式应用广泛,所以sync包单独提供了Mutex类型来支持这种模式。


import "sync"

var (
	mu      sync.Mutex //保护balance
	balance int
)

func Deposit(amount int) {
	mu.Lock() //上锁
	balance = balance + amount
	mu.Unlock() //释放锁
}
func Balance() int {
	mu.Lock()
	b := balance
	mu.Unlock()

	return b
}

lock与unlock之间的代码称之为临界区域

在复杂的环境中,不能保证lock,unlock成对出现,go通过defer解决

func Deposit(amount int) {
	mu.Lock()         //上锁
	defer mu.Unlock() //释放锁
	balance = balance + amount
}

考虑一个提款操作,成功时减少指定数量,返回true,否则无法交易,返回false

func Withdraw(amount int) bool {
	Deposit(-amount)
	if Balance() < 0 {
		Deposit(amount)
		return false
	}
	return true
}

可以正确执行,但对于某一时刻,没有成功-amount但是对于一个更小的支付,也无法成功。因为withdraw不是原子操作。每个操作都有锁,但是整个序列没有锁。

以下代码实现会导致死锁

func Withdraw(amount int) bool {
	mu.Lock()
	defer mu.Unlock()
	Deposit(-amount)
	if Balance() < 0 {
		Deposit(amount)
		return false
	}
	return true
}

9.3 读写互斥锁

小明每秒发送上千次查询余额的请求,导致服务变慢。

Go提供了单写多读锁,只要不同时发生读写,就不互斥

func Balance() int {
	mu.RLock() //读锁
	defer mu.RUnlock()
	return balance
}
mu.Lock()是写锁

9.4 内存同步

{{}}

CPU ->cache->内存

现代CPU结构

一般在cache中,在适当的时候才会刷回内存,这样会导致不一致。

9.5 延迟初始化:sync.Once

简单来说,就是当初始化开销比较高时,我们可以在他被用到时才初始化。

一个例子,加载图片是不安全的。可能存在多次初始化的情况,loadIcons(),可能不为空了但是没有数据的情况等。

import "image"

var icons map[string]image.Image

func loadIcons() {
	icons = map[string]image.Image{
		"spades.png": loadIcon("spades.png"),
		"hearts.png": loadIcon("hearts.png"),
		"clubs.png":  loadIcon("clubs.png"),
	}
}

func loadIcon(s string) image.Image {
	return nil //示例,实际加载图片实现
}

// 并发不安全
func Icon(name string) image.Image {
	if icons == nil {
		loadIcons() //一次性地初始化
	}
	return icons[name]
}

简单做法就是加锁


import (
	"image"
	"sync"
)

var muIcon sync.Mutex
var icons map[string]image.Image

func loadIcons() {
	icons = map[string]image.Image{
		"spades.png": loadIcon("spades.png"),
		"hearts.png": loadIcon("hearts.png"),
		"clubs.png":  loadIcon("clubs.png"),
	}
}

func loadIcon(s string) image.Image {
	return nil //示例,实际加载图片实现
}

// 并发不安全
func Icon(name string) image.Image {
	muIcon.Lock()
	defer muIcon.Unlock()
	if icons == nil {
		loadIcons() //一次性地初始化
	}
	return icons[name]
}

缺点是两个goroutine不能同时访问这个变量,即使当这个变量已经初始化好了,也会如此。

我们可以通过并发读锁解决这个问题。

import (
	"image"
	"sync"
)

var muIcon sync.RWMutex
var icons map[string]image.Image

func loadIcons() {
	icons = map[string]image.Image{
		"spades.png": loadIcon("spades.png"),
		"hearts.png": loadIcon("hearts.png"),
		"clubs.png":  loadIcon("clubs.png"),
	}
}

func loadIcon(s string) image.Image {
	return nil //示例,实际加载图片实现
}

 
func Icon(name string) image.Image {
	muIcon.RLock()
	if icons != nil {
		icon := icons[name]
		mu.RUnlock()
		return icon
	}
	mu.RUnlock()
	muIcon.Lock()
	if icons == nil {
		loadIcons() //一次性地初始化
	}
	muIcon.Unlock()
	return icons[name]
}

这样很好的解决了并发问题,通过创建两个临界区。

Go通过Once更好的实现了以上功能

Once内部实现是,一个布尔变量和一个互斥量,布尔量记录是否初始化,互斥量保护布尔量和客户端数据。

import (
	"image"
	"sync"
)

var loadIconsOnce sync.Once
var icons map[string]image.Image

func Icon(name string) image.Image {
	loadIconsOnce.Do(loadIcons)
	return icons[name]
}

Do每次会检查布尔变量,为假就调用函数,为真则直接进行后续操作。

9.6 竞态检测器

Go提供的分析工具

-race 

命令加入到 go build run test中即可分析,

竞态检测器会研究事件流,找到问题案例,输出报告,用于定位问题。

9.7 示例:并发非阻塞缓存

函数记忆,缓存函数的结果,达到多次调用只计算一次的效果。

请求函数

package main

import (
	"io/ioutil"
	"net/http"
)

func httpGetBody(url string) (interface{}, error) {
	resp, err := http.Get(url)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	return ioutil.ReadAll(resp.Body)
}

记忆实现

package main

type Memo struct {
	f     Func
	cache map[string]result
}
type Func func(key string) (interface{}, error)

type result struct {
	value interface{}
	err   error
}

func New(f Func) *Memo {
	return &Memo{
		f:     f,
		cache: make(map[string]result),
	}
}

// 非并发安全
func (memo *Memo) Get(key string) (interface{}, error) {
	res, ok := memo.cache[key]
	if !ok {
		res.value, res.err = memo.f(key)
		memo.cache[key] = res
	}
	return res.value, res.err
}

示例

package main

import (
	"fmt"
	"log"
	"time"
)

func main() {
	m := New(httpGetBody)
	for _, url := range incomingURLs() {
		strat := time.Now()
		value, err := m.Get(url)
		if err != nil {
			log.Print(err)
		}
		fmt.Printf("%s,%s,%d bytes\n", url, time.Since(strat), len(value.([]byte)))
	}
}

func incomingURLs() []string {
	res := []string{"https://www.baidu.com", "https://www.bilibili.com/", "http://iprivacy.top", "https://www.baidu.com", "https://www.bilibili.com/", "http://iprivacy.top"}
	return res
}

并发访问

func main() {
	m := New(httpGetBody)
	var n sync.WaitGroup

	for _, url := range incomingURLs() {
		n.Add(1)
		go func(url string) {
			strat := time.Now()
			value, err := m.Get(url)
			if err != nil {
				log.Print(err)
			}
			fmt.Printf("%s,%s,%d bytes\n", url, time.Since(strat), len(value.([]byte)))
			n.Done()
		}(url)
	}
	n.Wait()
}

但由于Get不是线程安全的,所以会报错有可能

用-race 来分析

pass

9.8 goroutine与线程

goroutine 与线程是量与质的关系。

9.8.1 可增长的栈

每个OS线程有固定大小的栈内存(2MB),它既太大了,又太小了,

goroutine 开始时只有很小的栈,(2KB),它按需增长与缩小,可以达到1GB

9.8.2 goroutine调度

OS线程由OS内核调度,每隔几毫秒,通过中断实现,一个线程到另一个线程需要一个完整的上下文切换。这太慢了。

Go有自己的调度器,(m:n)调度技术,可以复用,可以调度m个goroutine到n个OS线程。

Go不是通过硬件时钟触发,而是通过Go语言结构触发。

9.8.3 GOMAXPROCS

Go调度器使用GOMAXPROCS参数确定使用多少个OS线程同时执行Go代码。

默认值为机器上的CPU数量。

GOMAXPROCS是m:n调度中的n.

可以显式控制,通过环境变量GOMAXPROCS或runtime.GOMAXPROCS.

9.8.4 goroutine没有标识

在大部分多线程操作系统或编程语言里,当前线程都有一个独特的标识。可以用于构建线程的局部存储,本质上就是一个全局的map。

goroutine没有可提供程序员访问的标识。

第十章 包和go工具

{{}}

Go自带100多个包,

包可以复用代码。

可以在http://godoc.org查找。

go工具。

10.2 导入路径

每个包都通过唯一的字符串进行标识,即导入路径,它们用在import声明中,

import(
	"fmt"
	"math/rand"
	"encoding/json"
	"github.com/go-sql-driver/mysql"
)

10.3 包声明

在go文件开头,每一个文件都需要声明属于哪一个包

例如:math/rand包中的每一个文件开头都是,package rand,访问,rand.Int,rand.Float64

10.4 导入声明

单个导入,多个导入

别名

import(
	"crypto/rand"
	mrand "math/rand"
)

10.5 空导入

导入但没用,会编译报错

import _ "image/png"

10.6 包及其命名

简短,清晰

10.7 go工具

go工具,下载,查询,格式化,构建,测试,安装

10.7.1 go空间组织

大部分用户必须进行唯一的配置是GOPATH

当切换工作空间时,改变GOPATH即可。

10.7.2 包下载

go语言下载依赖包

go get

10.7.3 包构建

10.7.4 包文档化

// 注释
func Fprintf(w io.Writer,format stirng, a...interface{})(int,eror)
go doc xxx.go
godoc -http :8000

10.7.5 内部包

10.7.6 包查询

go list

第十一章 测试

pass

第十二章 反射

{{}}

反射:是Go语言提供的一种机制,在编译时不知道类型的情况下,可更新变量、在运行时查看值、调用方法以及直接对他们的布局进行操作。

使用到反射的包

  • fmt
  • encoding/json,encoding/xml

12.1 为什么使用反射

三个问题:写一个函数有能力统一处理各种值类型的函数,

  • 这些类型可能无法共享同一个接口
  • 布局可能未知
  • 这个类型在我们设计函数的时候还不存在

比如:fmt.Printf中的格式化逻辑,可以输出任意类型的任意值,甚至是用户自定义的值。

简单实现一个Sprint函数:

首先判断是否实现了String方法,实现了直接调用,可以无限case,但不知道一个类型的布局时则无法继续了。


func Sprint(x interface{}) string {
	type stringer interface {
		String() string
	}
	switch x := x.(type) {
	case stringer:
		return x.String()
	case string:
		return x
	case int:
		return strconv.Itoa(x)
	case bool:
		if x {
			return "true"
		} else {
			return "false"
		}
	default:
		// array,chan,func,map,pointer,slice,struct
		return "???"
	}
}

12.2 reflect.Type 和reflect.Value

反射功能由reflect包提供,它定义了两个重要类型:TypeValue

reflect.TypeOf函数接受任何的interface{}参数,并且把接口中的动态类型以reflect.Type形式返回。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	t := reflect.TypeOf(3)
	fmt.Println(t)          //int
	fmt.Println(t.String()) //int
}

reflect.Typeof返回一个接口值对应的动态类型,总是返回的具体类型,而不是接口类型,

例如

var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) //*os.File而不是io.Writer
//使用了reflect.TypeOf
fmt.Printf("%v\n",3)

另一个重要类型是Value,reflect.Value可以包含任意类型的值。

reflect.ValueOf函数接受任意的interface{}并将接口值以动态值以reflect.Value的形式返回。

v := reflect.ValueOf(3)
fmt.Println(v)//3
fmt.Printf("%v\n", v)//3
fmt.Println(v.String())//<int value>

reflect.ValueOf的逆操作是reflect.Value.Interface方法,他返回一个interface{}接口值,与reflect.Value包含同一个值,

x := v.Interface()
fmt.Println(x)        // an interface{} 需要知道它的类型才可以操作
i := x.(int)          // an int
fmt.Printf("%d\n", i) //3

Interface需要知道他的动态类型,才能操作。Value不用。

Value用Kind操作各类型文章来源地址https://www.toymoban.com/news/detail-505425.html

package main

import (
	"fmt"
	"reflect"
	"strconv"
	"time"
)

func Any(value interface{}) string {
	return formatAtom(reflect.ValueOf(value))
}
func formatAtom(v reflect.Value) string {
	switch v.Kind() {
	case reflect.Invalid:
		return "invalid"
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return strconv.FormatInt(v.Int(), 10)
	case reflect.Uint, reflect.Uint8, reflect.Uint32, reflect.Uint64, reflect.Uint16:
		return strconv.FormatUint(v.Uint(), 10)
	case reflect.Bool:
		return strconv.FormatBool(v.Bool())
	case reflect.String:
		return strconv.Quote(v.String())
	case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
		return v.Type().String() + " 0x" + strconv.FormatUint(uint64(v.Pointer()), 16)
	default:
		return v.Type().String() + "value"
	}
}
func main() {
	var x int64 = 1
	var d time.Duration = 1 * time.Nanosecond
	fmt.Println(Any(x))                  // 1
	fmt.Println(Any(d))                  // 1
	fmt.Println(Any([]int64{x}))         //[]int64 0xa09a078
	fmt.Println(Any([]time.Duration{d})) //[]time.Duration 0xa09a098

}

it()
}


但由于Get不是线程安全的,所以会报错有可能

用-race 来分析

pass



## 9.8 goroutine与线程

goroutine 与线程是量与质的关系。

### 9.8.1 可增长的栈

每个OS线程有固定大小的栈内存(2MB),它既太大了,又太小了,

goroutine 开始时只有很小的栈,(2KB),它按需增长与缩小,可以达到1GB

### 9.8.2 goroutine调度

OS线程由OS内核调度,每隔几毫秒,通过中断实现,一个线程到另一个线程需要一个完整的上下文切换。这太慢了。

Go有自己的调度器,(m:n)调度技术,可以复用,可以调度m个goroutine到n个OS线程。

Go不是通过硬件时钟触发,而是通过Go语言结构触发。

### 9.8.3 GOMAXPROCS

Go调度器使用GOMAXPROCS参数确定使用多少个OS线程同时执行Go代码。

默认值为机器上的CPU数量。

GOMAXPROCS是m:n调度中的n.

可以显式控制,通过环境变量GOMAXPROCS或runtime.GOMAXPROCS.

### 9.8.4 goroutine没有标识

在大部分多线程操作系统或编程语言里,当前线程都有一个独特的标识。可以用于构建线程的局部存储,本质上就是一个全局的map。

goroutine没有可提供程序员访问的标识。



# 第十章 包和go工具

{{<align context="2023-04-19 20:05:17">}}

Go自带100多个包,

包可以复用代码。

可以在http://godoc.org查找。

go工具。



## 10.2 导入路径

每个包都通过唯一的字符串进行标识,即导入路径,它们用在import声明中,

```go
import(
	"fmt"
	"math/rand"
	"encoding/json"
	"github.com/go-sql-driver/mysql"
)

10.3 包声明

在go文件开头,每一个文件都需要声明属于哪一个包

例如:math/rand包中的每一个文件开头都是,package rand,访问,rand.Int,rand.Float64

10.4 导入声明

单个导入,多个导入

别名

import(
	"crypto/rand"
	mrand "math/rand"
)

10.5 空导入

导入但没用,会编译报错

import _ "image/png"

10.6 包及其命名

简短,清晰

10.7 go工具

go工具,下载,查询,格式化,构建,测试,安装

10.7.1 go空间组织

大部分用户必须进行唯一的配置是GOPATH

当切换工作空间时,改变GOPATH即可。

10.7.2 包下载

go语言下载依赖包

go get

10.7.3 包构建

10.7.4 包文档化

// 注释
func Fprintf(w io.Writer,format stirng, a...interface{})(int,eror)
go doc xxx.go
godoc -http :8000

10.7.5 内部包

10.7.6 包查询

go list

第十一章 测试

pass

第十二章 反射

{{}}

反射:是Go语言提供的一种机制,在编译时不知道类型的情况下,可更新变量、在运行时查看值、调用方法以及直接对他们的布局进行操作。

使用到反射的包

  • fmt
  • encoding/json,encoding/xml

12.1 为什么使用反射

三个问题:写一个函数有能力统一处理各种值类型的函数,

  • 这些类型可能无法共享同一个接口
  • 布局可能未知
  • 这个类型在我们设计函数的时候还不存在

比如:fmt.Printf中的格式化逻辑,可以输出任意类型的任意值,甚至是用户自定义的值。

简单实现一个Sprint函数:

首先判断是否实现了String方法,实现了直接调用,可以无限case,但不知道一个类型的布局时则无法继续了。


func Sprint(x interface{}) string {
	type stringer interface {
		String() string
	}
	switch x := x.(type) {
	case stringer:
		return x.String()
	case string:
		return x
	case int:
		return strconv.Itoa(x)
	case bool:
		if x {
			return "true"
		} else {
			return "false"
		}
	default:
		// array,chan,func,map,pointer,slice,struct
		return "???"
	}
}

12.2 reflect.Type 和reflect.Value

反射功能由reflect包提供,它定义了两个重要类型:TypeValue

reflect.TypeOf函数接受任何的interface{}参数,并且把接口中的动态类型以reflect.Type形式返回。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	t := reflect.TypeOf(3)
	fmt.Println(t)          //int
	fmt.Println(t.String()) //int
}

reflect.Typeof返回一个接口值对应的动态类型,总是返回的具体类型,而不是接口类型,

例如

var w io.Writer = os.Stdout
fmt.Println(reflect.TypeOf(w)) //*os.File而不是io.Writer
//使用了reflect.TypeOf
fmt.Printf("%v\n",3)

另一个重要类型是Value,reflect.Value可以包含任意类型的值。

reflect.ValueOf函数接受任意的interface{}并将接口值以动态值以reflect.Value的形式返回。

v := reflect.ValueOf(3)
fmt.Println(v)//3
fmt.Printf("%v\n", v)//3
fmt.Println(v.String())//<int value>

reflect.ValueOf的逆操作是reflect.Value.Interface方法,他返回一个interface{}接口值,与reflect.Value包含同一个值,

x := v.Interface()
fmt.Println(x)        // an interface{} 需要知道它的类型才可以操作
i := x.(int)          // an int
fmt.Printf("%d\n", i) //3

Interface需要知道他的动态类型,才能操作。Value不用。

Value用Kind操作各类型

package main

import (
	"fmt"
	"reflect"
	"strconv"
	"time"
)

func Any(value interface{}) string {
	return formatAtom(reflect.ValueOf(value))
}
func formatAtom(v reflect.Value) string {
	switch v.Kind() {
	case reflect.Invalid:
		return "invalid"
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		return strconv.FormatInt(v.Int(), 10)
	case reflect.Uint, reflect.Uint8, reflect.Uint32, reflect.Uint64, reflect.Uint16:
		return strconv.FormatUint(v.Uint(), 10)
	case reflect.Bool:
		return strconv.FormatBool(v.Bool())
	case reflect.String:
		return strconv.Quote(v.String())
	case reflect.Chan, reflect.Func, reflect.Ptr, reflect.Slice, reflect.Map:
		return v.Type().String() + " 0x" + strconv.FormatUint(uint64(v.Pointer()), 16)
	default:
		return v.Type().String() + "value"
	}
}
func main() {
	var x int64 = 1
	var d time.Duration = 1 * time.Nanosecond
	fmt.Println(Any(x))                  // 1
	fmt.Println(Any(d))                  // 1
	fmt.Println(Any([]int64{x}))         //[]int64 0xa09a078
	fmt.Println(Any([]time.Duration{d})) //[]time.Duration 0xa09a098

}

到了这里,关于Go语言基础的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包