参考书籍《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 大小不明,但可以放完整指针
int 与 int32 类型不同,需要转换
运算符优先级
* / % << >> & &^
+ - | ^ == != < <= > >=
&&
||
位运算
&
|
^
&^
<<
>>
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
}
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)
}
可以作为参数和返回值
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)
同样的对于
w= new(bytes.Buffer)
对于
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
相当于
接口的比较,取决于接口的类型是否可以比较。
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并发读写同一个变量并且至少其中一个是写入时。
三种解决方法:
-
不要写变量
-
避免多个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变量 } }
-
互斥
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包提供,它定义了两个重要类型:Type 和Value
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不用。文章来源:https://www.toymoban.com/news/detail-505425.html
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包提供,它定义了两个重要类型:Type 和Value
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模板网!