Golang学习笔记


前言

2022年寒假,为了学习tinyKV课程而学习Golang,就写一些东西来记录一下吧

大括号不能换行是真的难受!


package

  • package中的函数必须首字母大写才能被其他package调用
  • package可以有init函数,该函数在package被import的时候会调用

变量、数组、切片

语法

var a int = 10
var p *int = &a
a := 10
p := &a
a := [4]int {1, 2, 3, 4}
b := [...]int {2, 3, 4)
a := []int {1, 2, 3, 4)
a := [3][2]string {
{"11", "12"},
{"21", "22"},
{"31", "32"}, //The comma is necessary
}

传递参数

Go中将数组作为参数进行传递的时候执行的是值传递,这与C++不同,需要特别注意。切片可以看成是对原数组的引用,改变切片也会改变原数组。

数组赋值

数组之间可以直接赋值,但数组的类型和长度要一样,如:

a := [5]int {1, 2, 3, 4, 5}
b := [5]int {6, 7, 8, 9, 10}
a = b

数组遍历

可以用for range遍历数组和切片

a := [5]int {1, 2, 3, 4, 5}
for i, v := range a {
	//do something
}

for _, v := range a {
	//do something
}

可以用len(a)获取a数组的长度

for i := 0; i < len(a); i++ {
	fmt.Printf("%d th element is %d\n", i, a[i])
}

创建切片

用slc := a[st: ed]创建数组a在[st, ed)区间的切片

a := [5]int {1, 2, 3, 4, 5}
slc := a[1:4] //从a[1]到a[4-1]的切片

用make创建切片

func make([]T,len,cap)[]T 通过传递类型,长度和容量来创建切片。容量是可选参数, 默认值为切片长度。make 函数创建一个数组,并返回引用该数组的切片。

sld := make([]int, 5, 5)
fmt.Println(sld)

追加切片元素

sld := append(sld, 123, 34) //向sld中追加两个元素

函数

语法

func name(ele1 int, ele2 string) (rtnval1 int, rtnval2 string){
	rtnval1 = 1
	rtnval2 = "asd"
	return
//
}
func main(){
	val1, val2 := name(2, "zxc")
}

可变参数函数

只有最后一个参数可以为可变参数。当传入一组数时,会转化为切片,切片名为该可变参数名。可以使用语法糖直接将切片传递给可变参数

func f(nums ...int){
	for _, v := range nums{
		fmt.Printf("%d ", v)
	}
}

func main()
{
	f(1, 2, 3, 4)
	a := []int {1, 2, 3, 4}
	f(a...)//直接传递切片,在函数中改变nums将同时改变a
}

map

语法

mp := make(map[string]int)
mp := map[string]int {
	"caution": 509,
	"joker": 717,
	"oyster": 213,
}

map[T1]T2是一个类型

var mp map[string]int
mp = make(map[string]int)//必须使用make初始化

添加、获取元素

同C++

删除元素

delete(mp, "joker")

map的长度

可用len(mp)获取

map是引用类型

当 map 被赋值为一个新变量的时候,它们指向同一个内部数据结构。因此,改变其中一个变量,就会影响到另一变量。

mp1 := map[string]int {
	"oyster": 1,
	"caution": 2,
}
mp2 := mp1
mp2["oyster"] = 3
fmt.Println(mp1) //mp1也会改变
fmt.Println(mp2) 

map作为参数进行传递的时候,也是如此。


rune

rune 是 Go 语言的内建类型,它也是 int32 的别称。在 Go 语言中,rune 表示一个代码点。代码点无论占用多少个字节,都可以用一个 rune 来表示。

func printString(s string){
	r := []rune(s)
	for i, v := range r {
		fmt.Printf("%c", v)
	}
}

结构体

语法

type E struct { //命名结构体
	s1, s2 string
	age int
}
func main(){
	e1 := E {
		s1 : "asd",
		age : 3,
		s2 : "zxc",
	}
	e2 := E{"asd", "zxc", 3}
}
func main(){
	e3 := struct{ //匿名结构体
		s1, s2 string
		age int
	}{
		s1 : "asd",
		age : 3,
		s2 : "zxc",
	}
}

指向结构体的指针

若p为指向结构体的指针,则可以用p.s1代替(*p).s1

匿名字段

省略变量名,用类型名代替变量名

type Person struct {  
    string
    int
}

func main() {  
    var p1 Person
    p1.string = "naveen"
    p1.int = 50
    fmt.Println(p1)
}

提升字段

若一个结构体中有匿名结构体,则可以像访问正常成员一样访问匿名结构体的成员

导出结构体、导出字段

首字母大写才能被其他包使用


方法

方法类似于类的成员函数

语法

func (e E) show(tms int) {
	for i := 0; i < tms; i++ {
		fmt.Println(e)
	}
}

func main(){
	e := E { "asd", "zxc", 3 }
	e.show(3)
}

为了在一个类型上定义一个方法,方法的接收器类型定义和方法的定义应该在同一个包中。

指针接收器

使用指针接收器可以改变e的内容。当使用指针接收器时,e.show()和(&e).show()都是合法的


接口

语法

package main

import "fmt"

type VolFinder interface { 
	FindVol() []rune
}

type MyString string

func (s MyString) FindVol() (ans []rune) {
	for _, v := range s {
		if(v == 'a' || v == 'e' || v == 'i' || v == 'o' || v == 'u') {
			ans = append(ans, v)
		}
	}
	return
}

func main(){
	s := MyString("OysterMaZhiHao")
	it := VolFinder(s)
	fmt.Println(MyString(it.FindVol()))

}

空接口 & 类型断言

由于空接口没有方法,因此所有类型都实现了空接口。

package main

import "fmt"

func show(it interface{}) {
	fmt.Printf("Type = %T, value = %v\n", it, it)
	v, ok := it.(int)
	fmt.Println(v, " ", ok)
}

func main(){
	a := interface{} ("asd")
	b := interface{} (509)
	show(a)
	show(b)
}

类型选择

package main

import "fmt"

func show(it interface{}) {
	switch it.(type){
	case string:
		fmt.Printf("I'm a string, %v\n", it)
	case int:
		fmt.Printf("i'm an int, %v\n", it)
	default:
		fmt.Printf("i'm an unknown type, %v\n", it)
	}
}
type MyString string

func main(){
	a := interface{} ("asd")
	b := interface{} (509)
	c := interface{} (MyString("zxc"))
	show(a)
	show(b)
	show(c)
}

实现了接口的类型可以与接口进行比较(类型相等)


并发

Go协程

使用go来创建Go协程实现并发

package main

import "fmt"
import "time"
func a () {
	for i := 0; i < 5; i++ {
		time.Sleep(250 * time.Millisecond)
		fmt.Printf("%d ", i)
	}
}

func b () {
	for i := 'a'; i <= 'e'; i++ {
		time.Sleep(400 * time.Millisecond)
		fmt.Printf("%c ", i)
	}
}

func main(){
	go a()
	go b()
	time.Sleep(3000 * time.Millisecond)
	fmt.Println("Main")
}

信道

a := make(chan int)
data := <- a // 读取信道 a  
a <- data // 写入信道 a

当把数据发送到信道时,程序控制会在发送数据的语句处发生阻塞,直到有其它 Go 协程从信道读取到数据,才会解除阻塞。与此类似,当读取信道的数据时,如果没有其它的协程把数据写入到这个信道,那么读取过程就会一直阻塞着。

信道的这种特性能够帮助 Go 协程之间进行高效的通信,不需要用到其他编程语言常见的显式锁或条件变量。

package main

import "fmt"

func a (done chan bool) {
	for i := 0; i < 5; i++ {
		fmt.Printf("%d ", i)
	}
	done <- true
}

func b (done chan bool) {
	for i := 'a'; i <= 'e'; i++ {
		fmt.Printf("%c ", i)
	}
	done <- true
}

func main(){
	done := make(chan bool)
	go a(done)
	<- done
	go b(done)
	<- done
	fmt.Println("Main")
}

使用for range遍历信道

package main

import "fmt"

func write(chnl chan <- int) {
	for i := 0; i < 10; i++ {
		chnl <- i
	}
	close(chnl)
}

func main() {
	chnl := make(chan int)
	go write(chnl)
	for v := range chnl {
		fmt.Printf("%v\n", v)
	}
}

缓冲信道

只有在缓冲满的时候,才会发生阻塞,容量参数为0时,即为之前所讨论的会发生阻塞的信道。当写入信道后超过信道容量,但又没有读取信道,就会发生死锁。

package main

import "fmt"
import "time"
func fun(chnl chan int){
	for i := 0; i < 5; i++ {
		chnl <- i
		fmt.Println("Write ", i)
	}
	close(chnl)
}

func main() {
	chnl := make(chan int, 2)
	go fun(chnl)
	time.Sleep(2 * time.Second)
	for v := range chnl {
		fmt.Println("Read ", v)
		time.Sleep(2 * time.Second)
	}
}

WaitGroup

当执行WaitGroup.Wait()时,只有当group中所有协程都执行完毕,才会停止阻塞。

package main

import "fmt"
import "time"
import "sync"

func fun (id int, wg *sync.WaitGroup) {
	fmt.Println("Start ", id)
	time.Sleep(1 * time.Second)
	fmt.Println("End ", id)
	wg.Done()
}

func main() {
	var wg sync.WaitGroup
	for i := 0; i < 5; i++ {
		wg.Add(1)
		go fun(i, &wg)
	}
	wg.Wait()
	fmt.Println("All done")
}

工作池

Example : Job Allocating

package main

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

type Job struct {
	id int
	tsk int
}

type Result struct {
	job Job
	ans int
}

func allocate(num int, chnl chan Job) {
	for i := 0; i < num; i++ {
		chnl <- Job{i, i + 10}
	}
	close(chnl)
}

func worker(in chan Job, out chan Result, wg *sync.WaitGroup) {
	for job := range in {
		out <- Result{job, job.tsk * 10}
		time.Sleep(1 * time.Second)
	}
	wg.Done()
}

func getResult(out chan Result, done chan bool) {
	for result := range out {
		fmt.Println("Task", result.job.id, "Finished, the ans is ", result.ans)
	}
	done <- true
}

func work(wrknum int, in chan Job, out chan Result, wg *sync.WaitGroup) {
	for i := 0; i < wrknum; i++ {
		wg.Add(1)
		go worker(in, out, wg)
	}
	wg.Wait()
	close(out)
}

func main(){
	const jobNum = 30
	const workerNum = 30
	fmt.Println("You have", jobNum, "jobs and ", workerNum, "workers\nNow jobs starts")
	startTime := time.Now()
	jobs := make(chan Job)
	results := make (chan Result)
	done := make (chan bool)
	var wg sync.WaitGroup
	go allocate(jobNum, jobs)
	go work(workerNum, jobs, results, &wg)
	go getResult(results, done)
	<- done
	endTime := time.Now()
	fmt.Println("All done, tot time : ", endTime.Sub(startTime).Seconds(), "seconds")
}

select

select用于选择一个chan读取数据,在执行case或default前会阻塞。当有多个case可以读取时,会随机读取一个case。

package main

import "time"
import "fmt"

func fun1 (chnl chan string) {
	time.Sleep(1 * time.Second)
	chnl <- "From func1"
}

func fun2 (chnl chan string) {
	time.Sleep(2 * time.Second)
	chnl <- "From func2"
}

func main() {
	chnl1 := make(chan string)
	chnl2 := make(chan string)
	go fun1(chnl1)
	go fun2(chnl2)
	for i := 0; i < 5; i++ {	
		select{
		case v1 := <- chnl1 :
			fmt.Println(v1)
		case v2 := <- chnl2 :
			fmt.Println(v2)
		default:
			fmt.Println("No data received")
		}
		time.Sleep(1 * time.Second)
	}
}

竞态条件

用Mutex处理竞态条件

在sync.Mutex.Lock()和sync.Mutex.Unlock()之间的代码只会被一个协程同时执行,其他协程在当前协程unlock之前都会阻塞

package main

import(
	"fmt"
	"sync"
)

var x int = 0

func incre(wg *sync.WaitGroup, mx *sync.Mutex) {
	mx.Lock()
	x++
	mx.Unlock()
	wg.Done()
}

func main() {
	var wg sync.WaitGroup
	var mx sync.Mutex
	for i := 0; i < 10000; i++ {
		wg.Add(1)
		go incre(&wg, &mx)
	}
	wg.Wait()
	fmt.Println("All done, x = ", x)
}

用信道处理竞态条件

package main

import(
	"fmt"
	"sync"
)

var x int = 0

func incre(wg *sync.WaitGroup, chnl chan bool) {
	chnl <- true
	x++
	<- chnl
	wg.Done()
}

func main() {
	var wg sync.WaitGroup
	chnl := make(chan bool, 1)
	for i := 0; i < 10000; i++ {
		wg.Add(1)
		go incre(&wg, chnl)
	}
	wg.Wait()
	fmt.Println("All done, x = ", x)
}

Go的面向对象

Go用struct代替class,用struct嵌套代替class的继承,用interface实现多态

defer

  • 在函数返回之前,调用defer的函数。
  • defer调用的函数的参数的值是defer时的值,而不是实际调用时的值。
  • 当有多次defer时,按defer栈顺序调用(先进后出)

错误处理

error

error是一个interface,nil为无错误,实现了Error() string方法的都是error类型。在打印error类型时,会自动调用error.Error()获取错误信息并打印

获取更多错误信息

  • 断言底层struct类型,用struct字段获取更多信息
  • 断言底层struct类型,调用方法获取更多信息

自定义错误

  • 使用errors.New(string)自定义错误

panic + recover

  • panic时,会先调用defer函数,然后结束当前协程,在上一层中在调用defer函数,再结束这一层,直到程序退出。
  • 在defer函数中执行recover可恢复到上一层,不结束程序。
  • 可以在recover后用debug.PrintStack()打印栈堆
package main
import (  
    "fmt"
    "runtime/debug"
)
func r() {  
    if r := recover(); r != nil {
        fmt.Println("Recovered", r)
        debug.PrintStack()
    }
}
func a() {  
    defer r()
    n := []int{5, 7, 4}
    fmt.Println(n[3])
    fmt.Println("normally returned from a")
}

func main() {  
    a()
    fmt.Println("normally returned from main")
}

头等函数

支持头等函数(First Class Function)的编程语言,可以把函数赋值给变量,也可以把函数作为其它函数的参数或者返回值。Go 语言支持头等函数的机制。

匿名函数

package main

import (  
    "fmt"
)

func main() {  
    a := func() {
        fmt.Println("hello world first class function")
    }
    a()
    fmt.Printf("%T", a)
}

函数类型

package main

import "fmt"

type add func(a, b int) int

func main() {
	var a add = func(a, b int) int {
		return a + b
	}
	s := a(2, 3)
	fmt.Println(s)
}

高阶函数

wiki 把高阶函数(Hiher-order Function)定义为:满足下列条件之一的函数:

  • 接收一个或多个函数作为参数
  • 返回值是一个函数

闭包

package main

import (  
    "fmt"
)

func appendStr() func(string) string {  
    t := "Hello"
    c := func(b string) string {
        t = t + " " + b
        return t
    }
    return c
}

func main() {  
    a := appendStr()
    b := appendStr()
    fmt.Println(a("World"))
    fmt.Println(b("Everyone"))

    fmt.Println(a("Gopher"))
    fmt.Println(b("!"))
}

reflect

  • import "reflect"
  • reflect.ValueOf()
  • reflect.TypeOf()
  • reflect.TypeOf().Kind()
  • reflect.ValueOf().NumField()
  • reflect.ValueOf().Fieldd()
  • reflect.ValueOf().Int()

文件操作

读取文件

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {
    data, err := ioutil.ReadFile("test.txt")
    if err != nil {
        fmt.Println("File reading error", err)
        return
    }
    fmt.Println("Contents of file:", string(data))
}

使用命令行标记来传递文件路径

package main
import (
    "flag"
    "fmt"
)

func main() {
    fptr := flag.String("fpath", "test.txt", "file path to read from")
    flag.Parse()
    fmt.Println("value of fpath is", *fptr)
}

创建一个字符串标记,名称是 fpath,默认值是 test.txt,描述为 file path to read from。这个函数返回存储 flag 值的字符串变量的地址。在程序访问 flag 之前,必须先调用 flag.Parse()。

用wrkspacepath/bin/filehandling -fpath=/path-of-file/test.txt运行程序

写入文件

package main

import (
    "fmt"
    "os"
)

func main() {
    f, err := os.Create("test.txt")
    if err != nil {
        fmt.Println(err)
        return
    }
    l, err := f.WriteString("Hello World")
    if err != nil {
        fmt.Println(err)
        f.Close()
        return
    }
    fmt.Println(l, "bytes written successfully")
    err = f.Close()
    if err != nil {
        fmt.Println(err)
        return
    }
}
package main

import (
    "fmt"
    "os"
)

func main() {
    f, err := os.Create("/home/naveen/bytes")
    if err != nil {
        fmt.Println(err)
        return
    }
    d2 := []byte{104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100}
    n2, err := f.Write(d2)
    if err != nil {
        fmt.Println(err)
        f.Close()
        return
    }
    fmt.Println(n2, "bytes written successfully")
    err = f.Close()
    if err != nil {
        fmt.Println(err)
        return
    }
}
package main

import (
    "fmt"
    "os"
)

func main() {
    f, err := os.Create("lines")
    if err != nil {
        fmt.Println(err)
        f.Close()
        return
    }
    d := []string{"Welcome to the world of Go1.", "Go is a compiled language.",
"It is easy to learn Go."}

    for _, v := range d {
        fmt.Fprintln(f, v)
        if err != nil {
            fmt.Println(err)
            return
        }
    }
    err = f.Close()
    if err != nil {
        fmt.Println(err)
        return
    }
    fmt.Println("file written successfully")
}


  
  最后修订:2022-8-11