Golang学习笔记(一)

比较杂,不包含基本语法,主要是①标准库的学习,参考自github和②一些进阶和坑


长期更新

  1. map内的值由于是值类型,所以对结构体以及数组等需要取地址才可以修改,否则只读取

  2. 初始化结构体时像引用类型初始化为nil,而像数组这样的初始化为每个都为0值,一直递归下去https://sanyuesha.com/2018/05/07/go-json/

  3. 使用json进行传递,会将指针类型变为对应变量;在Unmarshal的时候,就算是引用类型的map,也要加&?

  4. 读取文件ReadFile/OpenFile+bufio.NewScanner/OpenFile+ReadAll

bufio.NewScanner(io.Reader),所以OpenFile得到的是一个实现Reader的对象,而ReadFile需要使用相应类型的NewReader转换为Reader对象,即

bf, err := ioutil.ReadFile(path)
if err != nil {
	log.Fatal(err)
}
// 初始化reader
var r = bufio.NewScanner(bytes.NewReader(bf))


// 使用Scanner自定义读取模式
// 声明字符串
input := "foo bar   baz"
// 初始化扫描器模块
scanner := bufio.NewScanner(strings.NewReader(input))
// split调用一个split函数,函数内容可以根据格式自己定义。
// scanwords是一个自带的split函数,用于返回以空格,换行符分隔的字符串,并删除周围的空格,它绝不返回空字符串。
scanner.Split(bufio.ScanWords)

// 循环读取
for scanner.Scan() {
    //fmt.Printf("%s\n", scanner.Bytes())
    fmt.Println(scanner.Text())
}


// 自定义逗号分隔读取
const input = "1,2,3,4,"
scanner := bufio.NewScanner(strings.NewReader(input))
onComma := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
    for i := 0; i < len(data); i++ {
        if data[i] == ',' {
            return i + 1, data[:i], nil
        }
    }
    // 最后一个可能是空字符串,error传入ErrFinalToken告诉bufio是最后个,使其不报错。
    return 0, data, bufio.ErrFinalToken
}
scanner.Split(onComma)
for scanner.Scan() {
    fmt.Printf("%q ", scanner.Text())
}
if err := scanner.Err(); err != nil {
    log.Fatal("Invalid input:", err)
}

Text()默认以换行符分隔,并不包含在内

  1. 写文件OpenFile+Write/WriteFile(存在则先清空,不存在则按照所给权限创建),第一种方式不适合大规模写入,第二种写入[]byte,适合大规模写入,先创建一个Buffer,再最后转为[]byte
var buf bytes.Buffer

// 初始化writer,没有这个writer也可以向Buffer中写入,并在最后转为String
var w = bufio.NewWriter(&buf)

// 创建一个自定义长度的缓冲区,必须大于默认值16,小于默认值则以默认值大小生成。
//var w = bufio.NewWriterSize(&buf, 32)

// 写入字符串类型
w.WriteString("Hello, ")
// 写入单个rune类型
w.WriteRune('W')
// 写入单个byte类型
w.WriteByte('o')
// 写入byte类型
w.Write([]byte("rld!"))
// 重置当前缓冲区
w.Flush()

if err := ioutil.WriteFile(FilePath, buf.Bytes(), os.ModePerm); err != nil {
    log.Fatal(err)
}



var buf bytes.Buffer

// 写入字符串类型
buf.WriteString("Hello, ")
// 写入单个rune类型
buf.WriteRune('W')
// 写入单个byte类型
buf.WriteByte('o')
// 写入byte类型
buf.Write([]byte("rld!"))

if err := ioutil.WriteFile(FilePath, buf.Bytes(), os.ModePerm); err != nil {
    log.Fatal(err)
}


// string和[]byte互相转换
a:=[]byte{'a','b','c'}
b:=string(a)
c:=[]byte(b)
  1. OpenFile的权限为os.O_WRONLY|os.O_CREATE|os.O_TRUNC/os.O_WRONLY|os.O_CREATE|os.O_APPEND;最后为文件的权限(0644)

  2. make(切片)要加长度len,map不需要,但是创建之后就会有空位

  3. go产生的json文件要被python调用时需要先进行字符串化,否则就是[]Byte流

  4. bad interpreter: No such file or directory

​ vim→:set ff

​ dos2unix

  1. map中的值对象有两种情况,①如切片,map这样的引用类型或是指针类型。这种情况下是可以直接通过哈希键对象对值直接修改的;②如结构体或是普通的整型等。这种情况下是不能直接对值进行修改的,若非要修改,可以通过创建临时变量,通过赋值解决。

  2. walk一个路径,并可以选择跳过目录(return filepath.SkipDir),return nil跳过文件

func getShellScript(rootpath string) []string {

	list := make([]string, 0, 10)
	
	err := filepath.Walk(rootpath, func(path string, info os.FileInfo, err error) error {
	    if err != nil {
			fmt.Printf("prevent panic by handling failure accessing a path %q: %v\n", path, err)
			return err
		}
        if info.IsDir() {
	        return nil
	    }
	    if filepath.Ext(path) == ".sh" {
	        list = append(list, path)
	    }
	    return nil
	})
	if err != nil {
	    fmt.Printf("walk error [%v]\n", err)
	}
	return list
}
  1. 多个分隔符的Split
readlines := strings.FieldsFunc(temp, func(r rune) bool {
	 	return unicode.IsSpace(r) || unicode.IsPunct(r)
})
  1. string用index得到byte,切片得到string(注意如str[1:2]),for range中得到的是string和对应的byte索引,unicode比较用rune,'a’即可以是byte(uint8)也可以是rune(int32)

  2. 如果每行写入"\n",最后不会生成新行,但是会保留一个换行符在末尾,建议使用strings.TrimSpace(str)去除

  3. go与python不同,sdsds\\"sd\\"表示内部双引号

  4. 如何使用flags

var (
	h                      bool
	input_gold_dir_path    string
	input_predict_dir_path string
	input_text_dir_path    string
	output_dir_path        string
	perl_script_path       string
)

func init() {
	flag.BoolVar(&h, "h", false, "Get help info")
	flag.StringVar(&input_gold_dir_path, "gold", "", "Directory path contains the gold annotation.")
	flag.StringVar(&input_predict_dir_path, "predict", "", "Directory path contains the predict annotation.")
	flag.StringVar(&input_text_dir_path, "text", "", "Directory path contains the text file.")
	flag.StringVar(&output_dir_path, "output", "", "Directory path contains the output file.")
	flag.StringVar(&perl_script_path, "perl", "/home/fyh/PycharmProjects/GRAM-CNN/evaluation/conlleval.pl", "conlleval.pl path")
	flag.Usage = usage

}
func usage() {
	fmt.Fprintf(os.Stderr, "This binary file can evaluate NER results by conlleval.pl.\n\nUsage: run4conll [-h] [-gold path] [-predict path] [-text path] [-output path] [-perl path]\n\nOptions:\n")
	flag.PrintDefaults()
}
func main() {
    //当出现不存在的args会中止
	flag.Parse() 
	if h || input_gold_dir_path == "" || input_predict_dir_path == "" || input_text_dir_path == "" || output_dir_path == "" {
		flag.Usage()
		os.Exit(1)
	}
}
  1. 如何使用os/exec执行shell命令
func Exec_shell(s string) (string, error) {
	var out bytes.Buffer
	cmd := exec.Command("/bin/bash", "-c", s)
	cmd.Stdout = &out
	err := cmd.Run()
	return out.String(), err
}
  1. 字典嵌套结构
a := map[string]map[string]bool{}
if _, ok := a["fyh"]; !ok {
	a["fyh"] = map[string]bool{"ztd": true}
}

a := map[string]*HPO_class{}
if _, ok := a["fyh"]; !ok {
	a["fyh"] = &HPO_class{}
}
  1. 匿名函数非传参方式则传递的是地址;函数声明类型是func() int;函数也是引用类型
var funcArr [3]func() int
for i := range funcArr {
	funcArr[i] = func()int {
		return i * i
	}
}
for _,f := range funcArr {
	fmt.Println(f())
}
func squares() func() int {
    var x int
    return func() int {
        x++
        return x * x
    }
}
func main() {
    f := squares()
    fmt.Println(f()) // "1"
    fmt.Println(f()) // "4"
    fmt.Println(f()) // "9"
    fmt.Println(f()) // "16"
}
//通过这个例子,我们看到变量的生命周期不由它的作用域决定:squares返回后,变量x仍然隐式的存在于f中。

函数squares返回另一个类型为 func() int的函数。对squares的一次调用会生成一个局部变量x并返回一个匿名函数。每次调用时匿名函数时,该函数都会先使x的值加1,再返回x的平方。第二次调用squares时,会生成第二个x变量,并返回一个新的匿名函数。新匿名函数操作的是第二个x变量。

如果你使用go语句(第八章)或者defer语句(5.8节)会经常遇到此类问题。这不是go或defer本身导致的,而是因为它们都会等待循环结束后,再执行函数值。

  1. range循环中对于引用类型复制的是地址,所以也能进行原位修改
a := [5]map[string]int{}
a[1] = map[string]int{}
for i, f := range a {
	if i == 1 {
		f["fyh"] = 1
	}
}
fmt.Println(a)
  1. 循环变量地址不变,但是循环作用域内的变量地址每次赋值都会改变
for d := 0; d < 4; d++ {
	a := d
	fmt.Printf("%v\t%v\n", &d, &a)
}

//0xc000068080  0xc000068088
//0xc000068080  0xc0000680b0
//0xc000068080  0xc0000680b8
//0xc000068080  0xc0000680c0

a := []int{1, 2, 3}
for _, v := range a {
	fmt.Println(&v)
}

//0xc000068080
//0xc000068080
//0xc000068080

//给定循环变量,后进行值拷贝,函数值中记录的是循环变量的内存地址,而不是循环变量某一时刻的值,在匿名函数中需要考虑,我们会引入一个与循环变量同名的局部变量,作为循环变量的副本
  1. 对于正则表达式,我们注意要使用``,对于使用变量的字符串,使用+号连接
s := "toe"
pattern, _ := regexp.Compile(`(?i:\b` + s + `$)|(?i:\b` + s + `\b)|(?i:^` + s + `\b)`)
if pattern.MatchString("toes fuck") {
	fmt.Println("yes")
}
  1. 在导入包的时候不需要重复import包中已导入的其他包

  2. 我们可以给同一个包内的任意命名类型定义方法,只要这个命名类型的底层类型(译注:这个例子里,底层类型是指[]Point这个slice,Path就是命名类型)不是指针或者interface

且方法集是根据接收器Receiver来决定的

  1. 定义新类型时
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
}
perim := Path{
    {1, 1},
    {5, 1},
    {5, 4},
    {1, 1},
}
//初始化时相当于[]Point{{1,1}...},其中Point的初始化可省略Point
fmt.Println(perim.Distance())
  1. sort包的使用
//第一种方法
type Person struct {
	Name string
	Age  int
}
type ByAge []Person
//为切片类型定义好三种方法,即可以完成sort的接口要求
func (a ByAge) Len() int           { return len(a) }
func (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }

people := []Person{
	{"Bob", 31},
	{"John", 42},
	{"Michael", 17},
	{"Jenny", 26},
}
sort.Sort(ByAge(people))


//第二种方法,类似使用Lambda函数
sort.Slice(people, func(i, j int) bool {
	return people[i].Age > people[j].Age
})
  1. 调用基于指针对象的方法,下面三种情况里的任意一种情况都是可以的
  • 不论是接收器的实际参数和其接收器的形式参数相同,比如两者都是类型T或者都是类型*T

  • 或者接收器实参是类型T,但接收器形参是类型*T,这种情况下编译器会隐式地为我们取变量的地址

  • 或者接收器实参是类型*T,形参是类型T。编译器会隐式地为我们解引用,取到指针指向的实际变量

  1. 在类型中内嵌的匿名字段也可能是一个命名类型的指针,这种情况下字段和方法会被间接地引入到当前的类型中(译注:访问需要通过该指针指向的对象去取)。添加这一层间接关系让我们可以共享通用的结构并动态地改变对象之间的关系。下面这个ColoredPoint的声明内嵌了一个*Point的指针
type ColoredPoint struct {
    *Point
    Color color.RGBA
}

p := ColoredPoint{&Point{1, 1}, red}
q := ColoredPoint{&Point{5, 4}, blue}
fmt.Println(p.Distance(*q.Point)) // "5"
q.Point = p.Point                 // p and q now share the same Point
p.ScaleBy(2)
fmt.Println(*p.Point, *q.Point) // "{2 2} {2 2}"

这种类型的值便会拥有Point和RGBA类型的所有方法,以及直接定义在ColoredPoint中的方法。当编译器解析一个选择器到方法时,比如p.ScaleBy,它会首先去找直接定义在这个类型里的ScaleBy方法,然后找被ColoredPoint的内嵌字段们引入的方法,然后去找Point和RGBA的内嵌字段引入的方法,然后一直递归向下找。如果选择器有二义性的话编译器会报错,比如你在同一级里有两个同名的方法

  1. 方法表达式 Method expression

可以通过以struct.method/(*struct).method方法来分别调用struct中的非指针方法和指针方法,同时将self参数置于形参首位

p := Point{1, 2}
q := Point{4, 6}

distance := Point.Distance
fmt.Println(distance(p, q))

scale := (*Point).ScaleBy
scale(&p, 2)
  1. switch

switch x.(type)/switch a:=x.(type)

  1. 使用sync.WaitGroup来对未知循环数时进行并发控制
func main() {
    // ...determine roots...
    // Traverse each root of the file tree in parallel.
    fileSizes := make(chan int64)
    var n sync.WaitGroup
    for _, root := range roots {
        n.Add(1)
        go walkDir(root, &n, fileSizes)
    }
    go func() {
        n.Wait()
        close(fileSizes)
    }()
    // ...select loop...
}
func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
    defer n.Done()
    for _, entry := range dirents(dir) {
        if entry.IsDir() {
            n.Add(1)
            subdir := filepath.Join(dir, entry.Name())
            go walkDir(subdir, n, fileSizes)
        } else {
            fileSizes <- entry.Size()
        }
    }
}
  1. 使用有缓存通道来确定并行数
// sema is a counting semaphore for limiting concurrency in dirents.
var sema = make(chan struct{}, 20)

// dirents returns the entries of directory dir.
func dirents(dir string) []os.FileInfo {
    sema <- struct{}{}        // acquire token
    defer func() { <-sema }() // release token
    // ...
  1. 读写锁

我们可以将其总结为如下三条:

  • 同时只能有一个 goroutine 能够获得写锁定;
  • 同时可以有任意多个 goroutine 获得读锁定;
  • 同时只能存在写锁定或读锁定(读和写互斥)。
// golang读写锁,其特征在于

// 读锁:可以同时进行多个协程读操作,不允许写操作
// 写锁:只允许同时有一个协程进行写操作,不允许其他写操作和读操作
// 读写锁共有四个方法

// RLock:获取读锁
// RUnLock:释放读锁
// Lock:获取写锁
// UnLock:释放写锁


// 多个读操作同时读取一个变量时,虽然加了锁,但是读操作是不受影响的。(读和写是互斥的,读和读不互斥)
var m *sync.RWMutex
func main() {
    m = new(sync.RWMutex)
    // 多个同时读
    go read(1)
    go read(2)
    time.Sleep(2*time.Second)
}
func read(i int) {
    println(i,"read start")
    m.RLock()
    println(i,"reading")
    time.Sleep(1*time.Second)
    m.RUnlock()
    println(i,"read over")
}

// 下一个reading不需要等待到上一个read over完成

// 由于读写互斥,所以写操作开始的时候,读操作必须要等写操作进行完才能继续,不然读操作只能继续等待
var m *sync.RWMutex

func main() {
	m = new(sync.RWMutex)
	// 写的时候啥也不能干
	go write(1)
	go read(2)
	go write(3)
	time.Sleep(3 * time.Second)
}
func read(i int) {
	println(i, "read start")
	m.RLock()
	println(i, "reading")
	m.RUnlock()
	println(i, "read over")
}
func write(i int) {
	println(i, "write start")
	m.Lock()
	println(i, "writing")
	time.Sleep(1 * time.Second)
	m.Unlock()
	println(i, "write over")
}

// 下一个reading需要等待上一个write over完成
  1. bytes包
str := "Hello World!"
// 根据字符串创建buffer
buf := bytes.NewBufferString(str)
// 根据byte创建buffer
buf = bytes.NewBuffer([]byte(str))

fmt.Printf("%s\n", buf.Bytes())

// 比较a和b, 返回 0: a等于b, 1: a包含b, -1: a不包含b
bytes.Compare(a, b)

// 判断a与b是否相同,返回ture/false
bytes.Equal(a, b)

// 判断a与b是否相同,忽略大小写
bytes.EqualFold(a, b)

// 判断a是否以b开头,当b为空时true
bytes.HasPrefix(a, b)

// 判断a是否以b结尾,当b为空时true
bytes.HasSuffix(a, b)

// 如果a以b结尾,则返回a去掉b结尾部分的新byte。如果不是,返回a
bytes.TrimSuffix(a, b)

// 如果a以b开头,则返回a去掉b开头部分的新byte。如果不是,返回a
bytes.TrimPrefix(a, b)

// 去除开头结尾所有的 空格换行回车缩进
bytes.TrimSpace(a)

// 去除开头结尾所有的指定字符串中的任意字符
bytes.Trim(a, " ")

// 按自定义方法 去除开头结尾所有指定内容
bytes.TrimFunc(a, unicode.IsLetter)

// 去除开头所有的 指定字符串中的任意字符
bytes.TrimLeft(a, "0123456789")

// 按自定义方法 去除开头所有指定内容
bytes.TrimLeftFunc(a, unicode.IsLetter)

// 去除结尾所有的 指定字符串中的任意字符
bytes.TrimRight(a, "0123456789")

// 按自定义方法 去除结尾所有指定内容
bytes.TrimRightFunc(a, unicode.IsLetter)

// 以一个或者多个空格分割成切片
bytes.Fields([]byte("  foo bar  baz   "))

// 根据指定方法分割成切片
bytes.FieldsFunc([]byte("  foo1;bar2,baz3..."), func(c rune) bool {
    return !unicode.IsLetter(c) && !unicode.IsNumber(c) // 以 不是字符或者数字 进行分割
})

// 判断a是否包含b
bytes.Contains(a, b)

// 判断byte是否包含字符串中任意字符,只要包含字符串中一个及以上字符返回true,否则false
bytes.ContainsAny([]byte("I like seafood."), "fÄo!")

// 判断byte是否包含rune字符
bytes.ContainsRune([]byte("I like seafood."), 'f')

// 统计a中包含所有b的个数,如果b为空则返回a的长度
bytes.Count(a, b)

// 检索a中首个b的位置,未检索到返回-1
bytes.Index(a, b)

// 检索a中首个 byte类型字符 的位置,未检索到返回-1
bytes.IndexByte(a, byte('k'))

// 自定义方法检索首个字符的位置,未检索到返回-1
bytes.IndexFunc([]byte("Hello, 世界"), func(c rune) bool {
    return unicode.Is(unicode.Han, c) // 是否包含中文字符
})

// 检索a中首个 字符串中任意字符 的位置,未检索到返回-1
bytes.IndexAny(a, "abc")

// 检索a中首个 rune类型字符 的位置,未检索到返回-1
bytes.IndexRune([]byte("chicken"), 'k')

// 检索a中最后个b的位置,未检索到返回-1
bytes.LastIndex(a, b)

// 检索a中最后个 byte类型字符 的位置,未检索到返回-1
bytes.LastIndexByte(a, byte('k'))

// 自定义方法检索最后个字符的位置,未检索到返回-1
bytes.LastIndexFunc(a, unicode.IsLetter)

// 将byte数组以指定 byte字符 连接成一个新的byte
s := [][]byte{a, b}
bytes.Join(s, []byte(","))

// 返回一个重复n次a的新byte
// 例如:a = []byte("abc"),返回 []byte("abcabc")
bytes.Repeat(a, 2)

// 返回一个 将a中的b替换为c 的新byte,n为替换个数,-1替换所有
bytes.Replace(a, b, c, -1)

// 返回一个 将a中的b替换为c 的新byte
bytes.ReplaceAll(a, b, c)

// byte类型转rune类型
bytes.Runes(a)

// 将a以指定字符byte分割成byte数组
bytes.Split(a, []byte(","))

// 将a以指定字符byte分割成byte数组, n为分割个数,-1分割所有
bytes.SplitN(a, []byte(","), 2)

// 将a以指定字符byte分割成byte数组,保留b。
bytes.SplitAfter(a, []byte(","))

// 将a以指定字符byte分割成byte数组,保留b。n为分割个数,-1分割所有
bytes.SplitAfterN(a, []byte(","), 2)

// 返回一个 以空格为界限,所有首个字母大写 的标题格式
bytes.Title(a)

// 返回一个 所有字母大写 的标题格式
bytes.ToTitle(a)

// 使用指定的映射表将 a 中的所有字符修改为标题格式返回。
bytes.ToTitleSpecial(unicode.SpecialCase{}, a)

// 所有字母大写
bytes.ToUpper(a)

// 使用指定的映射表将 a 中的所有字符修改为大写格式返回。
bytes.ToUpperSpecial(unicode.SpecialCase{}, a)

// 所有字母小写
bytes.ToLower(a)

// 使用指定的映射表将 a 中的所有字符修改为大写格式返回。
bytes.ToLowerSpecial(unicode.SpecialCase{}, a)

// 遍历a按指定的rune方法处理每个字符
bytes.Map(func(r rune) rune {
    if r >= 'A' && r <= 'Z' {
        return r
    } else {
        return 'a'
    }
}, a)
  1. container包

双链表list

list 的初始化有两种方法:分别是使用 New() 函数和 var 关键字声明,两种方法的初始化效果都是一致的

变量名 := list.New()

var 变量名 list.List

列表与切片和 map 不同的是,列表并没有具体元素类型的限制,因此,列表的元素可以是任意类型,这既带来了便利,也引来一些问题,例如给列表中放入了一个 interface{} 类型的值,取出值后,如果要将 interface{} 转换为其他类型将会发生宕机

双链表支持从队列前方或后方插入元素,分别对应的方法是 PushFront 和 PushBack

这两个方法都会返回一个 *list.Element 结构,如果在以后的使用中需要删除插入的元素,则只能通过 *list.Element配合 Remove() 方法进行删除,这种方法可以让删除更加效率化,同时也是双链表特性之一

l := list.New()
l.PushBack("fist")
l.PushFront(67)
方 法 功 能
InsertAfter(v interface {}, mark * Element) * Element 在 mark 点之后插入元素,mark 点由其他插入函数提供
InsertBefore(v interface {}, mark * Element) *Element 在 mark 点之前插入元素,mark 点由其他插入函数提供
PushBackList(other *List) 添加 other 列表元素到尾部
PushFrontList(other *List) 添加 other 列表元素到头部
// 堆
// 定义结构体并实现sort接口的方法和heap接口的方法
// 声明结构体
type IntHeap []int

// 创建sort.Interface接口的Len方法
func (h IntHeap) Len() int { return len(h) }

// 创建sort.Interface接口的Less方法
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }

// 创建sort.Interface接口的Swap方法
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }

// 创建heap.Interface接口的添加方法
func (h *IntHeap) Push(x interface{}) { *h = append(*h, x.(int)) }

// 创建heap.Interface接口的移除方法
func (h *IntHeap) Pop() interface{} {
	old := *h
	n := len(old)
	x := old[n-1]
	*h = old[0 : n-1]
	return x
}


// 调用heap的方法
import "container/heap"

func main() {

	// 初始化结构体
	h := &IntHeap{1, 4, 2, 7, 8, 9, 3, 6}
	// 初始化堆
	heap.Init(h)
	// 添加元素并重新排序
	heap.Push(h, 5)
	// 移除最小元素返回一个新的interface,根据sort排序规则
	heap.Pop(h)
	// 移除指定位置的interface,返回一个新的interface
	heap.Remove(h, 5)
	// 移除指定位置的interface,并修复索引
	heap.Fix(h, 5)
}



// 双向链表
import "container/list"

// 初始化链表
l := list.New()
l2 := list.New()

// 向链表后面插入值为v的元素并返回该元素的指针
e4 := l.PushBack(4)
// 向链表后面插入另一个链表,l和l2可以相同,但是都不能为nil
l.PushBackList(l2)
// 向链表前面面插入值为v的元素并返回一个该元素指针
e1 := l.PushFront(1)
// 向链表前面插入另一个链表,两个链表可以相同,但是都不能为nil
l.PushFrontList(l2)
// 向元素mark(e4)前插入值为v的元素并返回一个新的元素,如果mark不是链表的元素,则不改变链表,mark不能为nil
l.InsertBefore(3, e4)
// 向元素mark后插入值为v的元素并返回一个新的元素,如果mark不是链表的元素,则不改变链表,mark不能为nil
l.InsertAfter(2, e1)

// 如果元素指针存在链表中,从链表中移除元素,并返回元素值,元素值不能为nil
l.Remove(e1)
// 如果元素存在链表中,将元素移动到链表最前面,元素不能为nil
l.MoveToFront(e4)
// 如果元素存在链表中,将元素移动到链表最后面,元素不能为nil
l.MoveToBack(e1)
// 如果元素e和mark都存在链表中,将e移动到mark前面,两个元素都不能为nil
l.MoveBefore(e1, e4)
// 如果元素e和mark都存在链表中,将e移动到mark后面,两个元素都不能为nil
l.MoveAfter(e1, e4)

// 返回链表长度
l.Len()

// type Element struct {Value interface{}   //在元素中存储的值}
// func (l *List) Init() *List  //list l初始化或者清除list l
// func (l *List) Front() *Element //获取list l的第一个元素的指针
// func (e *Element) Next() *Element  //返回该元素的下一个元素,如果没有下一个元素则返回nil


// 遍历链表从前面开始打印内容
fmt.Println("front: ")
for e := l.Front(); e != nil; e = e.Next() {
    fmt.Println(e.Value)
}
// 遍历链表从后面开始打印内容
fmt.Println("back: ")
for e := l.Back(); e != nil; e = e.Prev() {
    fmt.Println(e.Value)
}



// 环形链表
import "container/ring"

// 初始化n个元素的环形链表
r := ring.New(5)
s := ring.New(5)
// 返回链表长度
n := r.Len()

for i := 0; i < n; i++ {
    r.Value = i  // 给元素赋值
    r = r.Next() // 获取下一个元素

    s.Value = i
    s = s.Next()
}

for j := 0; j < n; j++ {
    r = r.Prev()         // 获取上一个元素
    fmt.Println(r.Value) //
}

// 循环访问环形链表所有元素
r.Do(func(p interface{}) {
    fmt.Println(p.(int))
})

// 将前面n个元素移到后面,例:0,1,2,3,4,5 => 3,4,5,0,1,2
r.Move(3)

// 链表r与链表s是不同链表,则在r链表的后面链接s链表,否则删除相同部分
r.Link(s)
// 从下一个元素开始,移除链表连续n个元素
r.Unlink(3)
  1. 加密解密

crypto

  1. 连接sql数据库(待填坑)

database/sql

  1. init函数

golang程序初始化先于main函数执行,由runtime进行初始化,初始化顺序如下:

  1. 初始化导入的包(包的初始化顺序并不是按导入顺序(“从上到下”)执行的,runtime需要解析包依赖关系,没有依赖的包最先初始化,与变量初始化依赖关系类似,参见golang变量的初始化);
  2. 初始化包作用域的变量(该作用域的变量的初始化也并非按照“从上到下、从左到右”的顺序,runtime解析变量依赖关系,没有依赖的变量最先初始化,参见golang变量的初始化);
  3. 执行包的init函数;

init函数的主要特点:

  • init函数先于main函数自动执行,不能被其他函数调用;
  • init函数没有输入参数、返回值;
  • 每个包可以有多个init函数;
  • 包的每个源文件也可以有多个init函数,这点比较特殊;
  • 同一个包的init执行顺序,golang没有明确定义,编程时要注意程序不要依赖这个执行顺序。
  • 不同包的init函数按照包导入的依赖关系决定执行顺序。

参考来源

  1. flag包
import "flag"

// global variables
var (
	h                      bool
	input_gold_dir_path    string
	input_predict_dir_path string
	input_text_dir_path    string
	output_dir_path        string
	perl_script_path       string
)

// init函数定义flag变量
func init() {
	flag.BoolVar(&h, "h", false, "Get help info")
	flag.StringVar(&input_gold_dir_path, "gold", "", "Directory path contains the gold annotation.")
	flag.StringVar(&input_predict_dir_path, "predict", "", "Directory path contains the predict annotation.")
	flag.StringVar(&input_text_dir_path, "text", "", "Directory path contains the text file.")
	flag.StringVar(&output_dir_path, "output", "", "Directory path contains the output file.")
	flag.StringVar(&perl_script_path, "perl", "/home/fyh/PycharmProjects/GRAM-CNN/evaluation/conlleval.pl", "conlleval.pl path")
	flag.Usage = usage

}

// 定义usage函数
func usage() {
	fmt.Fprintf(os.Stderr, "This binary file can evaluate NER results by conlleval.pl.\n\nUsage: run4conll [-h] [-gold path] [-predict path] [-text path] [-output path] [-perl path]\n\nOptions:\n")
	flag.PrintDefaults()
}

// main函数中Parse
func main() {
	flag.Parse()
	if h || input_gold_dir_path == "" || input_predict_dir_path == "" || input_text_dir_path == "" || output_dir_path == "" {
		flag.Usage()
		os.Exit(1)
	}
}
  1. fmt包
var buf bytes.Buffer
var str string

// 采用默认格式将其参数格式化并写入标准输出
// 总是会在相邻参数的输出之间添加空格并在输出结束后添加换行符
// 返回写入的字节数和遇到的任何错误
fmt.Println("打印内容", "Hello World!")

// 采用默认格式将其参数格式化并写入标准输出
// 如果两个相邻的参数都不是字符串,会在它们的输出之间添加空格;如果都是字符串,并不会添加空格
// 返回写入的字节数和遇到的任何错误
fmt.Print("打印内容", "Hello World!", "\n")

// 根据format参数生成格式化的字符串并写入标准输出
// 返回写入的字节数和遇到的任何错误
/*
		%b  二进制
		%o  八进制
		%d  十进制
		%x	十六进制
		%f	浮点数 3.141593
		%g  浮点数 3.141592653589793
		%e	浮点数 3.141593e+00
		%t 	布尔值
		%c  字符(rune)
		%s  字符串
		%q  带双引号的字符串"abc"或带单引号的'c'
		%v  变量的自然形式
		%T  变量的类型
		%%  字面上的%号标志
	 */
fmt.Printf("打印内容%s", "Hello World!\n")

// 采用默认格式将其参数格式化并写入w
// 总是会在相邻参数的输出之间添加空格并在输出结束后添加换行符
// 返回写入的字节数和遇到的任何错误
fmt.Fprintln(&buf, "Hello", "World!")

// 采用默认格式将其参数格式化并写入w
// 如果两个相邻的参数都不是字符串,会在它们的输出之间添加空格
// 返回写入的字节数和遇到的任何错误
fmt.Fprint(&buf, "Hello", "World!")

// 根据format参数生成格式化的字符串并写入w
// 返回写入的字节数和遇到的任何错误
fmt.Fprintf(&buf, "Hello %s", "World!")

// 采用默认格式将其参数格式化,串联所有输出生成并返回一个字符串
// 总是会在相邻参数的输出之间添加空格并在输出结束后添加换行符
fmt.Sprintln("Hello", "World!")

// 采用默认格式将其参数格式化,串联所有输出生成并返回一个字符串
// 如果两个相邻的参数都不是字符串,会在它们的输出之间添加空格
fmt.Sprint("Hello", "World!")

// 根据format参数生成格式化的字符串并返回该字符串
fmt.Sprintf("Hello %s", "World!")

// 根据format参数生成格式化字符串并返回一个包含该字符串的错误
fmt.Errorf("%s", "Error")

// 从标准输入扫描文本,将成功读取的空白分隔的值保存进成功传递给本函数的参数
// 换行视为空白。返回成功扫描的条目个数和遇到的任何错误
// 如果读取的条目比提供的参数少,会返回一个错误报告原因
fmt.Scan("Hello", "World!")

// 类似Scan,但会在换行时才停止扫描。最后一个条目后必须有换行或者到达结束位置
fmt.Scanln("Hello", "World!")

// 从r扫描文本,将成功读取的空白分隔的值保存进成功传递给本函数的参数
// 换行视为空白。返回成功扫描的条目个数和遇到的任何错误
// 如果读取的条目比提供的参数少,会返回一个错误报告原因
fmt.Fscan(&buf, "Hello", "World!")

// example
var a bytes.Buffer
a.WriteString("fyh ztd")
b := ""
c := ""
fmt.Fscan(&a, &b, &c)
fmt.Println(b, c)

// result:fyh ztd

// 类似Fscan,但会在换行时才停止扫描。最后一个条目后必须有换行或者到达结束位置
fmt.Fscanln(&buf, "Hello", "World!")

// 从r扫描文本,根据format 参数指定的格式将成功读取的空白分隔的值保存进成功传递给本函数的参数
// 返回成功扫描的条目个数和遇到的任何错误
fmt.Fscanf(&buf, "%s", "Hello")

// 从字符串str扫描文本,将成功读取的空白分隔的值保存进成功传递给本函数的参数
// 换行视为空白。返回成功扫描的条目个数和遇到的任何错误
// 如果读取的条目比提供的参数少,会返回一个错误报告原因
fmt.Sscan(str, "Hello", "World!")

// 类似Sscan,但会在换行时才停止扫描。最后一个条目后必须有换行或者到达结束位置
fmt.Sscanln(str, "Hello", "World!")

// 从字符串str扫描文本,根据format 参数指定的格式将成功读取的空白分隔的值保存进成功传递给本函数的参数
// 返回成功扫描的条目个数和遇到的任何错误
fmt.Sscanf(str, "%s", "Hello")
  1. 交叉编译

mac下编译为linux

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build

mac下编译为windows

CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build

linux下编译为mac

CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build

linux下编译为windows

CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build

windows下编译为mac

CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build main.go

windows下编译为linux

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build main.go

  1. image包
// image实现了基本的2D图片库
// 基本接口叫作Image。图片的色彩定义在image/color包。
// Image接口可以通过调用如NewRGBA和NewPaletted函数等获得;也可以通过调用Decode函数解码包含GIF、JPEG或PNG格式图像数据的输入流获得
// 解码任何具体图像类型之前都必须注册对应类型的解码函数
// 注册过程一般是作为包初始化的副作用,放在包的init函数里。因此,要解码PNG图像,只需在程序的main包里嵌入  import _ "image/png"

import (
    "image"
    "image/color"
    "image/png"
)

type Circle struct {
    X,Y,R float64
}
func (c Circle) Brightness(x, y float64) uint8 {
	var dx, dy = c.X-x, c.Y-y
	d := math.Sqrt(dx*dx+dy*dy) / c.R
	if d > 1 {
		return 0
	} else {
		return 255
	}
}
func createRGBAImage() *image.RGBA {

	// 指定长宽
	var w, h = 280, 240

	var hw, hh = float64(w/2), float64(h/2)
	r := 40.0

	// 三分之二的圆周率
	p := 2 * math.Pi / 3
	// 红色圆
	cr := &Circle{hw - r*math.Sin(0), hh - r*math.Cos(0), 60}
	// 绿色圆
	cg := &Circle{hw - r*math.Sin(p), hh - r*math.Cos(p), 60}
	// 蓝色圆
	cb := &Circle{hw - r*math.Sin(-p), hh - r*math.Cos(-p), 60}


	// 指定两个坐标点生成矩形
	// Rect是Rectangle {Pt(x0,y0),Pt(x1,y1)}的简写
	// 返回的矩形具有必要时交换的最小和最大坐标,以便格式良好
	rect := image.Rect(0, 0, w, h)

	// 初始化一张NRGBA图片
	m := image.NewRGBA(rect)

	// 为图片上每个点设置颜色
    // color.RGBA有四个属性
	for x := 0; x < w; x++ {
		for y := 0; y < h; y++ {
			c := color.RGBA{
				R: cr.Brightness(float64(x), float64(y)),
				G: cg.Brightness(float64(x), float64(y)),
				B: cb.Brightness(float64(x), float64(y)),
				A: 255,
			}
			m.Set(x, y, c)
		}
	}

	return m
}

func examplePngEncode(PngFilePath string) {

	// 生成RGBA图片内容
	m := createRGBAImage()

	// 创建并打开一个只能写入的png文件
	f, err := os.OpenFile(PngFilePath, os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer f.Close()

	// 将RGBA内容编码成png写入文件
	if err := png.Encode(f, m); err != nil {
		log.Fatal(err)
	}
}

func examplePngDecode() {

	// 打开png文件
	f, err := os.Open(PngFilePath)
	if err != nil {
		log.Fatal(err)
	}

	// png解码
	img, err := png.Decode(f)
	if err != nil {
		log.Fatal(err)
	}

	levels := []string{" ", "░", "▒", "▓", "█"}

	// Bounds返回图片域,边界不一定包含点(0,0)
	for y := img.Bounds().Min.Y; y < img.Bounds().Max.Y; y++ {
		for x := img.Bounds().Min.X; x < img.Bounds().Max.X; x++ {

			// At返回像素在(x,y)处的颜色
			// At(bounds().min.x,bounds().min.y)返回网格的左上角像素
			// At(bounds().max.x-1,bounds().max.y-1)返回右下角的像素
			pointColor := img.At(x, y)

			// 使用灰度色覆盖
			c := color.GrayModel.Convert(pointColor).(color.Gray)
			level := c.Y / 51 // 51 * 5 = 255
			if level == 5 {
				level--
			}
			fmt.Print(levels[level])
		}
		fmt.Print("\n")
	}
}
func exampleJpegEncode() {

	// 生成RGBA图片内容
	m := createRGBAImage()

	// 创建并打开一个只能写入的jpeg文件
	f, err := os.OpenFile(JpegFilePath, os.O_WRONLY|os.O_CREATE, 0600)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer f.Close()

	// 将RGBA内容编码成jpeg写入文件
	// Options图像质量值为100,是最好的图像显示
	if err := jpeg.Encode(f, m, &jpeg.Options{100}); err != nil {
		log.Fatal(err)
	}
}
  1. 后缀树实现对数级子字符串搜索
import (
	"bytes"
	"fmt"
	"index/suffixarray"
	"io/ioutil"
	"os"
)

// 写入文件持久化
var buf bytes.Buffer
index := suffixarray.New([]byte("banana"))
index.Write(&buf)
err := ioutil.WriteFile("index_file", buf.Bytes(), 0600)
if err != nil {
	fmt.Fprintf(os.Stderr, "%v", err)
}

// 读出索引
data, err := ioutil.ReadFile("index_file")
if err != nil {
    fmt.Fprintf(os.Stderr, "%v", err)
}
index := suffixarray.New(nil)
index.Read(bytes.NewReader(data))

// 查找子字符串
offsets := index.Lookup([]byte("ana"), -1)
for index, value := range offsets {
    fmt.Println(index, value)
}
  1. io包
// 读取reader内容向指定大小buffer填充
func exampleReadFull() {

	// 根据字符串创建一个reader
	r := strings.NewReader("some io.Reader stream to be read\n")

	// 声明4个字节的buffer
	buf := make([]byte, 4)

	// 从reader精确地读取len(buf)字节数据填充进buffer
	// 函数返回写入的字节数和错误(如果没有读取足够的字节)
	// 只有没有读取到字节时才可能返回EOF;如果读取了有但不够的字节时遇到了EOF,函数会返回ErrUnexpectedEOF
	// 只有返回值err为nil时,返回值n才会等于len(buf)
	if _, err := io.ReadFull(r, buf); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("%s\n", buf)
}

// writer拷贝reader内容
func exampleCopy() {
    
	// 声明buffer
	var buf bytes.Buffer
	// 根据字符串创建reader
	r := strings.NewReader("some io.Reader stream to be read")

	// 将src的数据拷贝到dst,直到在src上到达EOF或发生错误
	// 返回拷贝的字节数和遇到的第一个错误
	if _, err := io.Copy(&buf, r); err != nil {
		log.Fatal(err)
	}
    // writer拷贝reader指定字节数的内容
	if _, err := io.CopyN(&buf, r, 10); err != nil {
		log.Fatal(err)
	}
    // writer拷贝reader内容,提供指定大小的缓冲区
    b := make([]byte, 8)
    // 与 Copy 相同,只是它通过提供的缓冲区(如果需要的话)进行分级,而不是分配临时的缓冲区
    if _, err := io.CopyBuffer(&buf, r, b); err != nil {
		log.Fatal(err)
	}
	fmt.Println(buf.String())
}
  1. ioutil包
// 声明内容
var data = []byte("Go is a general-purpose language designed with systems programming in mind.")

// 创建一个reader
var r = bytes.NewReader(data)

// 从r读取数据直到EOF或遇到error,返回读取的数据和遇到的错误
// 成功的调用返回的err为nil而非EOF
// 因为本函数定义为读取r直到EOF,它不会将读取返回的EOF视为应报告的错误
b, err := ioutil.ReadAll(r)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(b))

// 向filename指定的文件中写入数据。如果文件不存在将按给出的权限创建文件,否则在写入数据之前清空文件
_ = ioutil.WriteFile(File, data, os.ModePerm)

// 从filename指定的文件中读取数据并返回文件的内容
// 成功的调用返回的err为nil而非EOF
// 因为本函数定义为读取整个文件,它不会将读取返回的EOF视为应报告的错误
bf, err := ioutil.ReadFile(File)
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(bf))

// 返回dirname指定的目录的目录信息的有序列表
files, err := ioutil.ReadDir(Dir)
if err != nil {
    log.Fatal(err)
}
for _, file := range files {

    // 文件名称(不含扩展名)
    fmt.Println(file.Name())
    // 修改时间
    fmt.Println(file.ModTime())
    // 文件模式
    fmt.Println(file.Mode())
    // 判断是否是一个目录,等价于Mode().IsDir()
    fmt.Println(file.IsDir())
    // 普通文件返回值表示其大小;其他文件的返回值含义各系统不同
    fmt.Println(file.Size())
    // 基础数据源(可以返回nil)
    fmt.Println(file.Sys())
}

// 创建temp文件
func exampleTempFile() {

	data := []byte("temporary file's content")

	// 在dir目录下创建一个新的、使用prefix为前缀的临时文件,以读写模式打开该文件并返回os.File指针
	// 如果dir是空字符串,TempFile使用默认用于临时文件的目录(参见os.TempDir函数)
	// 不同程序同时调用该函数会创建不同的临时文件,调用本函数的程序有责任在不需要临时文件时摧毁它
	tmpFile, err := ioutil.TempFile("", "example")
	if err != nil {
		log.Fatal(err)
	}
	// 移除文件
	defer os.Remove(tmpFile.Name())
	// 写入
	if _, err := tmpFile.Write(data); err != nil {
		log.Fatal(err)
	}
	// 关闭
	if err := tmpFile.Close(); err != nil {
		log.Fatal(err)
	}
}

// 创建temp目录
func exampleTempDir() {

	content := []byte("temporary file's content")
	// 在dir目录里创建一个新的、使用prfix作为前缀的临时文件夹,并返回文件夹的路径
	// 如果dir是空字符串,TempDir使用默认用于临时文件的目录(参见os.TempDir函数)
	// 不同程序同时调用该函数会创建不同的临时目录,调用本函数的程序有责任在不需要临时文件夹时摧毁它
	dir, err := ioutil.TempDir("", "example")
	if err != nil {
		log.Fatal(err)
	}
	// 移除文件夹
	defer os.RemoveAll(dir)

	// 将新的文件夹路径放入一个单一路径里
	tmpfn := filepath.Join(dir, "tmpfile")
	if err := ioutil.WriteFile(tmpfn, content, 0666); err != nil {
		log.Fatal(err)
	}
}
  1. log包
const LogFile = "testdata/testLog"

// 创建日志文件,返回一个os.File指针,os.OpenFile也返回这个指针
file, err := os.Create(LogFile)
if err != nil {
    log.Fatal(err)
}

// 创建一个Logger
// 参数out设置日志信息写入的目的地
// 参数prefix会添加到生成的每一条日志前面
// 参数flag定义日志的属性(时间、文件等等)
l := log.New(file, "[INFO]", log.Ldate|log.Ltime|log.Lshortfile)

// 或使用以下语句定义日志的属性
// 设置logger的输出选项(日志属性)
l.SetFlags(log.Ldate|log.Ltime|log.Lshortfile)
// 设置logger的输出前缀
l.SetPrefix("")
// 设置标准logger的输出目的地,默认是标准错误输出
l.SetOutput(file)

// 写入输出一次日志事件
// 参数s包含在Logger根据选项生成的前缀之后要打印的文本,如果s末尾没有换行会添加换行符
// calldepth用于恢复PC,出于一般性而提供,但目前在所有预定义的路径上它的值都为2
l.Output(2, "Hello World!")
// 返回标准logger的输出前缀
l.Prefix()
// 返回标准logger的输出选项
l.Flags()
// 其它用法大致与fmt包类似
l.Println("ok")



// syslog包
// 通过连接到指定网络上的地址来建立到日志守护程序的连接
// 每次写入返回的时候都会发送一条日志消息,其中包含设施和严重性(来自优先级)和标记
// 如果标签为空,则使用 os.Args0。如果网络为空,拨号将连接到本地系统日志服务器。否则,请参阅 net.Dial 的文档以获取网络和 raddr 的有效值
sl, err := syslog.Dial("tcp", "localhost:1234",
                       syslog.LOG_WARNING|syslog.LOG_DAEMON|syslog.LOG_INFO, "testTag")
if err != nil {
    log.Fatal(err)
}
// 写入
if _, err := sl.Write([]byte("Hello World!")); err != nil {
    log.Fatal(err)
}
// 关闭连接
if err := sl.Close(); err != nil {
    log.Fatal(err)
}

// 建立到系统日志守护进程的新连接
// 每次写入返回的写入程序都会发送一条具有给定优先级(syslog 设施和严重性的组合)和前缀标记的日志消息
// 如果标签为空,则使用 os.Args0
news, err := syslog.New(syslog.LOG_DEBUG, "testNew")
defer news.Close()
if err != nil {
    log.Fatal(err)
}

// 创建一个 log.Logger,它的输出以指定的优先级写入系统日志服务
// 这是 syslog 设施和严重性的组合。logFlag 参数是通过 log.New 创建记录器的标志集
l, err := syslog.NewLogger(syslog.LOG_DEBUG, log.Ldate|log.Ltime|log.Lshortfile)
if err != nil {
    log.Fatal(err)
}
l.Fatal("退出")
  1. math包
import "math"

// 返回一个NaN, IEEE 754“这不是一个数字”值
math.NaN()

// 判断f是否表示一个NaN(Not A Number)值
math.IsNaN(12.34)

// 如果sign>=0函数返回正无穷大,否则返回负无穷大
f := math.Inf(0)

// 如果sign > 0,f是正无穷大时返回真;如果sign<0,f是负无穷大时返回真;sign==0则f是两种无穷大时都返回真
math.IsInf(12.34, 1)
math.IsInf(f, 0)
// 返回不小于x的最小整数(的浮点值)
// 计算数字只入不舍
math.Ceil(12.34)
// 返回不大于x的最小整数(的浮点值)
// 计算数字只舍不入
math.Floor(12.34)

// 四舍五入
math.Round(12.34)

// 返回x的整数部分(的浮点值)
math.Trunc(12.34)

// 返回f的整数部分和小数部分,结果的正负号都相同
math.Modf(12.34)
// 返回x的绝对值
math.Abs(-12.34) // 12.34

// 返回x和y中最大值
math.Max(12.3, 22)

// 返回x和y中最小值
math.Min(12.3, 22)

// 返回x-y和0中的最大值,不可以反过来
math.Dim(12.3, 12)

// 取余运算,可以理解为 x-Trunc(x/y)*y,结果的正负号和x相同
math.Mod(22, 3)

// IEEE 754差数求值,即x减去最接近x/y的整数值(如果有两个整数与x/y距离相同,则取其中的偶数)与y的乘积
math.Remainder(12, 34)

// 返回x的二次方根
math.Sqrt(144) // 12

// 返回x的三次方根
math.Cbrt(27)

// 返回Sqrt(p*p + q*q),注意要避免不必要的溢出或下溢
math.Hypot(2, 3)

// 求正弦
math.Sin(3)

// 求余弦
math.Cos(4)

// 求正切
math.Tan(5)

// 求反正弦(返回弧度)
// Asin(±0) = ±0
// Asin(x) = NaN if x < -1 or x > 1
math.Asin(0.5)

// 求反余弦(x是弧度)
// Acos(x) = NaN if x < -1 or x > 1
math.Acos(0.5)

// 求反正切(x是弧度)
// Atan(±0) = ±0
// Atan(±Inf) = ±Pi/2
math.Atan(0.5)

// 类似Atan(y/x),但会根据x,y的正负号确定象限
math.Atan2(0.5, 0.2)

// 求双曲正弦
math.Sinh(2)

// 求双曲余弦
math.Cosh(2)

// 求双曲正切
math.Tanh(2)

// 求反双曲正弦
math.Asinh(2)

// 求反双曲余弦
math.Acosh(2)

// 求反双曲正切
math.Atanh(2)

// 求自然对数
math.Log(2)

// 等价于Log(1+x)。但是在x接近0时,本函数更加精确
math.Log1p(0.0001)

// 求2为底的对数;特例和Log相同
math.Log2(2)

// 求10为底的对数;特例和Log相同
math.Log10(10)

// 返回x的二进制指数值,可以理解为Trunc(Log2(x))
math.Logb(10)

// 返回E**x;x绝对值很大时可能会溢出为0或者+Inf,x绝对值很小时可能会下溢为1
math.Exp(12)

// 等价于Exp(x)-1,但是在x接近零时更精确;x绝对值很大时可能会溢出为-1或+Inf
math.Expm1(12)

// 返回2**x
math.Exp2(12)

// 返回x**y
math.Pow(3, 4)

// 返回10**e
math.Pow10(2)

// 伽玛函数(当x为正整数时,值为(x-1)!)
math.Gamma(10)

// 返回Gamma(x)的自然对数和正负号
math.Lgamma(10)

// 误差函数
math.Erf(1)

// 余补误差函数
math.Erfc(10.01)

// 第一类贝塞尔函数,0阶
math.J0(2)

// 第一类贝塞尔函数,1阶
math.J1(2)

// 第一类贝塞尔函数,n阶
math.Jn(2, 2)

// 第二类贝塞尔函数,0阶
math.Y0(2)

// 第二类贝塞尔函数,1阶
math.Y1(2)

// 第二类贝塞尔函数,n阶
math.Yn(2, 2)
  1. rand包

实现了伪随机数的生成

import (
	"math/rand"
)

// 使用给定的seed来初始化生成器到一个确定的状态
// 如未调用,默认资源的行为就好像调用了Seed(1)
// 如果需要实现随机数,seed值建议为 时间戳
// 如果seed给定固定值,结果永远相同
rand.Seed(time.Now().UnixNano())

// 返回一个非负的伪随机int值
fmt.Println(rand.Int())

// 返回一个取值范围在[0,n]的伪随机int值,如果n<=0会panic
fmt.Println(rand.Intn(100))

// 返回一个取值范围在[0, 1]的伪随机float32值
fmt.Println(rand.Float32())

// 返回一个取值范围在[0, 1]的伪随机float64值
fmt.Println(rand.Float64())

// 返回一个服从标准正态分布(标准差=1,期望=0)、取值范围在[-math.MaxFloat64, +math.MaxFloat64]的float64值
fmt.Println(rand.NormFloat64())

// 返回一个服从标准指数分布(率参数=1,率参数是期望的倒数)、取值范围在(0, +math.MaxFloat64]的float64值
fmt.Println(rand.ExpFloat64())

// 返回一个有n个元素的,[0,n)范围内整数的伪随机排列的切片
fmt.Println(rand.Perm(15))


// 初始化一个Source,代表一个生成均匀分布在范围[0, 1<<63)的int64值的(伪随机的)资源,类似种子
// 需要随机,建议使用时间戳,详情查看example()
source := rand.NewSource(99)

// 初始化*rand.Rand
r := rand.New(source)

fmt.Println(r.Intn(10))


// shuffle的用法
numbers := []byte("12345")
letters := []byte("ABCDE")

// 随机交换数切片的位置, n应为切片的长度,n < 0 会panic
rand.Shuffle(len(numbers), func(i, j int) {
    numbers[i], numbers[j] = numbers[j], numbers[i]
    letters[i], letters[j] = letters[j], letters[i]
})
for i := range numbers {
    fmt.Printf("%c: %c\n", letters[i], numbers[i])
}
  1. big包
import "math/big"

// 创建一个值为x的*big.Int
i := big.NewInt(10)

// 返回x的int64表示,如果不能用int64表示,结果为undefined
i.Int64()
  1. net包

  2. os包

// 返回内核提供的主机名
fmt.Println(os.Hostname())

// 返回调用者所在进程的进程ID
fmt.Println("pid:", os.Getpid())

// 返回表示环境变量的格式为"key=value"的字符串的切片拷贝
fmt.Println(os.Environ())

// 返回对应当前工作目录的根路径。如果当前目录可以经过多条路径抵达(因为硬链接),Getwd会返回其中一个
fmt.Println(os.Getwd())

// 返回用于保管临时文件的默认目录
fmt.Println(os.TempDir())

// 返回用于用户的缓存数据的默认根目录。用户应该在这个目录中创建自己的特定于应用程序的子目录,并使用它
fmt.Println(os.UserCacheDir())

// 用户文件夹路径
fmt.Println(os.UserHomeDir())

// 使用指定的权限和名称创建一个目录。如果出错,会返回*PathError底层类型的错误,不会创建上级目录
if err := os.Mkdir(DirPath, os.ModePerm); err != nil {
    log.Fatal(err)
}

// 使用指定的权限和名称创建一个目录,包括任何必要的上级目录,并返回nil,否则返回错误
// 权限位perm会应用在每一个被本函数创建的目录上
// 如果path指定了一个已经存在的目录,MkdirAll不做任何操作并返回nil
if err := os.MkdirAll(DirPath, os.ModePerm); err != nil {
    log.Fatal(err)
}

// 以读取的方式打开文件,文件不存在会报错
os.Open(FilePath)

// 以读写方式打开文件,并且清空原始内容,如果文件不存在以0666操作模式创建文件
os.Create(FilePath)

// 改变文件的大小,它不会改变I/O的当前位置。 如果截断文件,多出的部分就会被丢弃。如果出错,错误底层类型是*PathError
os.Truncate(FilePath, 1024)

// 删除指定的文件或目录。如果出错,会返回*PathError底层类型的错误
os.Remove(FilePath)

// 删除path指定的文件,或目录及它包含的任何下级对象。它会尝试删除所有东西,除非遇到错误并返回。如果path指定的对象不存在,会返回nil而不返回错误
os.RemoveAll(FilePath)

// 硬链接相当于对文件创建了一个指针,移动原文件不会造成影响
// 创建硬链接。如果出错,会返回* LinkError底层类型的错误
os.Link(FilePath, NewFilePath)

// 创建软连接。如果出错,会返回* LinkError底层类型的错误,对文件路径创建文件,如果文件移动变成死链接
os.Symlink(FilePath, NewFilePath)

// 获取指定的软链接文件指向的文件的路径。如果出错,会返回*PathError底层类型的错误
os.Readlink(NewFilePath)

// 以读写方式打开文件,并且内容写入方式为添加,如果文件不存在以0755操作模式创建文件
f, err := os.OpenFile(FilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0755)
if err != nil {
    log.Fatal(err)
}

// 写入内容(向文件内添加内容)
if _, err := f.Write([]byte("appended some data\n")); err != nil {
    log.Fatal(err)
}

// 关闭文件
defer f.Close()

// 判断文件是否存在
if _, err := os.Stat(FilePath); err != nil {
    if os.IsNotExist(err) {
        fmt.Println("file does not exist")
    } else {
        log.Fatal(err)
    }
}

// 修改指定文件的mode操作模式
// 如果name指定的文件是一个符号链接,它会修改该链接的目的地文件的mode。如果出错,会返回*PathError底层类型的错误
if err := os.Chmod(FilePath, 0644); err != nil {
    log.Fatal(err)
}

// 修改指定文件的用户id和组id
// 如果name指定的文件是一个符号链接,它会修改该链接的目的地文件的用户id和组id。如果出错,会返回*PathError底层类型的错误
if err := os.Chown(FilePath, 501, 20); err != nil {
    log.Fatal(err)
}

// 修改指定文件的用户id和组id
// 如果name指定的文件是一个符号链接,它会修改该符号链接自身的用户id和组id。如果出错,会返回*PathError底层类型的错误
if err := os.Lchown(FilePath, 501, 20); err != nil {
    log.Fatal(err)
}


mtime := time.Date(2019, time.February, 1, 3, 4, 5, 0, time.UTC)
atime := time.Date(2019, time.March, 2, 4, 5, 6, 0, time.UTC)
// 修改指定文件的访问时间和修改时间,类似Unix的utime()或utimes()函数
// 底层的文件系统可能会截断/舍入时间单位到更低的精确度。如果出错,会返回*PathError底层类型的错误
if err := os.Chtimes(FilePath, atime, mtime); err != nil {
    log.Fatal(err)
}

// 返回一个描述指定文件的FileInfo
// 如果指定的文件对象是一个符号链接,返回的FileInfo描述该符号链接的信息,本函数不会试图跳转该链接。如果出错,返回的错误值为*PathError类型
fi, err := os.Lstat(FilePath)
fi, err := os.Stat(FilePath)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("permissions: %#o\n", fi.Mode().Perm()) // 0400, 0777, etc.
switch mode := fi.Mode(); {
    case mode.IsRegular():
    fmt.Println("regular file")
    case mode.IsDir():
    fmt.Println("directory")
    case mode&os.ModeSymlink != 0:
    fmt.Println("symbolic link")
    case mode&os.ModeNamedPipe != 0:
    fmt.Println("named pipe")
}

// 设置名为key的环境变量(临时)。如果出错会返回该错误
if err := os.Setenv("NAME", "Gopher"); err != nil {
    log.Fatal(err)
}

// 检索并返回名为key的环境变量的值。如果不存在该环境变量会返回空字符串
fmt.Println(os.Getenv("NAME"))

// 检索并返回名为key的环境变量的值 和 是否存在的bool值。如果不存在布尔值为false
fmt.Println(os.LookupEnv("NAME"))

// 删除当前程序所有环境变量,对系统无影响
os.Clearenv()

// 使用指定函数替换s中的${var}或$var。例如,os.ExpandEnv(s)等价于os.Expand(s, os.Getenv)
fmt.Println(os.Expand("Hello $NAME!", func(s string) string { return "Gopher" }))

// 替换s中的${var}或$var为名为var 的环境变量的值。引用未定义环境变量会被替换为空字符串
fmt.Println(os.ExpandEnv("Hello ${NAME}!"))

// 取消设置单个环境变量
if err := os.Unsetenv("NAME"); err != nil {
    log.Fatal(err)
}

// 根据进程id查找一个运行中的进程
p, err = os.FindProcess(p.Pid)
if err != nil {
    log.Fatal(err)
}

// 立刻退出进程
if err := p.Kill(); err != nil {
    log.Fatal(err)
}

// 获取命令行参数
// 第一个参数是命令本身,所以从第二个开始截取
args := os.Args[1:]
fmt.Println(args)
  1. exec包

exec包执行外部命令
它包装了os.StartProcess函数以便更容易的修正输入和输出,使用管道连接I/O,以及作其它的一些调整

import "os/exec"

// 查询环境变量
// 在环境变量PATH指定的目录中搜索可执行文件,如file中有斜杠,则只在当前目录搜索
// 返回完整路径或者相对于当前目录的一个相对路径
path, err := exec.LookPath("fortune")
if err != nil {
    fmt.Println("error: ", err)
} else {
    fmt.Printf("fortune is available at %s\n", path)
}


// 执行命令
cmd := exec.Command("go", "doc", "exec")

// 设置输入内容
cmd.Stdin = strings.NewReader("")

// 声明buffer
var out bytes.Buffer

// 设置输出内容填充地址
cmd.Stdout = &out

// 执行c包含的命令,并阻塞直到完成
err := cmd.Run()
if err != nil {
    log.Fatal(err)
}
fmt.Println(out.String())

// Example

func Exec_shell(s string) (string, error) {
	var out bytes.Buffer
	cmd := exec.Command("/bin/bash", "-c", s)
	cmd.Stdout = &out
    // 执行命令,并阻塞直到完成
	err := cmd.Run()
	return out.String(), err
}

shell_command := "shell cmd"
out, exex_err := Exec_shell(shell_command)
if exex_err != nil {
    fmt.Fprintf(os.Stderr, "%v", exex_err)
} else {
    fmt.Printf("%v", out)
    fmt.Println("Completed")
}
  1. signal包
import "os/signal"

// 初始化一个信号channel
c := make(chan os.Signal, 1)

// 让signal包将输入信号转发到c。如果没有列出要传递的信号,会将所有输入信号传递到c;否则只传递列出的输入信号
// signal包不会为了向c发送信息而阻塞(就是说如果发送时c阻塞了,signal包会直接放弃)
// 调用者应该保证c有足够的缓存空间可以跟上期望的信号频率。对使用单一信号用于通知的通道,缓存为1就足够了
signal.Notify(c, syscall.SIGINT, syscall.SIGKILL)

s := <-c
fmt.Println("Got signal:", s)

// 让signal包停止向c转发信号
// 它会取消之前使用c调用的所有Notify的效果。当Stop返回后,会保证c不再接收到任何信号
signal.Stop(c)
  1. user包
import "os/user"

// 根据用户名查询用户
u, err := user.Lookup("root")
if err != nil {
    log.Fatal(err)
}
fmt.Println("Lookup Name:", u.Name)

// 根据用户ID查询用户
ui, err :=  user.LookupId("501")
if err != nil {
    log.Fatal(err)
}
fmt.Println("Lookup Uid:", ui.Uid)

// 根据组ID查询组
gi, err := user.LookupGroupId("20")
if err != nil {
    log.Fatal(err)
}
fmt.Println( "Lookup Gid:", gi.Gid)

// 根据组名称查询组
g, err := user.LookupGroup("staff")
if err != nil {
    log.Fatal(err)
}
fmt.Println("Lookup GroupName:", g.Name)


// 返回当前的用户帐户
u, err := user.Current()
if err != nil {
    log.Fatal(err)
}

// 用户名
fmt.Println("Name:", u.Name)
// 登陆名
fmt.Println("Username:", u.Username)
// 用户文件夹路径
fmt.Println("HomeDir:", u.HomeDir)
// 用户ID
fmt.Println("Uid:", u.Uid)
// 用户组ID
fmt.Println("Gid:", u.Gid)
// 用户所属组ID列表
fmt.Println(u.GroupIds())
  1. path包
import "path"

// path实现了对斜杠分隔的路径的实用操作函数
var pathStr = "/testdata/test/"
// 返回路径的最后一个元素,在提取元素前会删掉末尾的斜杠
// 如果路径是"",会返回".";如果路径是只有一个斜杆构成,会返回"/"
path.Base(pathStr) // test

// 通过单纯的词法操作返回和path代表同一地址的最短路径
// 它会不断的依次应用如下的规则,直到不能再进行任何处理:
// 1. 将连续的多个斜杠替换为单个斜杠
// 2. 剔除每一个.路径名元素(代表当前目录)
// 3. 剔除每一个路径内的..路径名元素(代表父目录)和它前面的非..路径名元素
// 4. 剔除开始一个根路径的..路径名元素,即将路径开始处的"/.."替换为"/"
path.Clean("a//c") // a/c

// 返回路径除去最后一个路径元素的部分,即该路径最后一个元素所在的目录
// 在使用Split去掉最后一个元素后,会简化路径并去掉末尾的斜杠
// 如果路径是空字符串,会返回".";如果路径由1到多个斜杠后跟0到多个非斜杠字符组成,会返回"/";其他任何情况下都不会返回以斜杠结尾的路径
path.Dir(pathStr) // /testdata/test

// 返回path文件扩展名
path.Ext("/a/test.html") // html

// 返回路径是否是一个绝对路径
path.IsAbs("/dev/null")

// 将路径从最后一个斜杠后面位置分隔为两个部分(dir和file)并返回
// 如果路径中没有斜杠,函数返回值dir会设为空字符串,file会设为path
// 两个返回值满足path == dir+file
dir, file := path.Split(pathStr)
fmt.Println(dir, file)

// 可以将任意数量的路径元素放入一个单一路径里,会根据需要添加斜杠
// 结果是经过简化的,所有的空字符串元素会被忽略
path.Join("a", "b/c") // /a/b/c

// 如果name匹配shell文件名模式匹配字符串,Match函数返回真
// Match要求匹配整个name字符串,而不是它的一部分。只有pattern语法错误时,会返回ErrBadPattern
/*
	匹配字符串语法:
		pattern:
			{ term }
		term:
			'*'                                  匹配0或多个非/的字符
			'?'                                  匹配1个非/的字符
			'[' [ '^' ] { character-range } ']'  字符组(必须非空)
			c                                    匹配字符c(c != '*', '?', '\\', '[')
			'\\' c                               匹配字符c
		character-range:
			c           匹配字符c(c != '\\', '-', ']')
			'\\' c      匹配字符c
			lo '-' hi   匹配区间[lo, hi]内的字符
	 */
path.Match("*", "abc")

// filepath包实现了兼容各操作系统的文件路径的实用操作函数
// 操作方法如path包
// 如win系统,就变成了\testdata\test
  1. plugin包

  2. reflect包 reference

import "reflect"

// reflect包实现了运行时反射,允许程序操作任意类型的对象
// 典型用法是用静态类型interface{}保存一个值,通过调用TypeOf获取其动态类型信息,该函数返回一个Type类型值
// 调用ValueOf函数返回一个Value类型值,该值代表运行时的数据
// Zero接受一个Type类型参数并返回一个代表该类型零值的Value类型值

type Hello struct {
	Name string   `json:"name"`
	Age  int      `json:"age"`
	Arr  []string `json:""`
}

func (h *Hello) Say() {
	fmt.Println("Hello World!")
}

func (h Hello) Says() {
	fmt.Println("Hello World!")
}

hello := Hello{}

// 返回接口中保存的值的类型,TypeOf(nil)会返回nil
t := reflect.TypeOf(hello)

// 返回该类型的元素类型,如果该类型的Kind不是Array、Chan、Map、Ptr或Slice,会panic
reflect.TypeOf(&hello)    // *main.Hello
reflect.TypeOf(&hello).Elem()    // main.Hello

p := []int{}
fmt.Println(reflect.TypeOf(p).Elem())    // int

// 返回该接口的具体类型
t := reflect.TypeOf(&hello)
fmt.Println(t.Kind())    // ptr
fmt.Println("reflect.Ptr:", t.Kind() == reflect.Ptr)

// 返回该类型在自身包内的类型名,如果是未命名类型会返回""
fmt.Println("Name:", t.Name())

// 返回类型的包路径,即明确指定包的import路径,如"encoding/base64"
// 如果类型为内建类型(string, error)或未命名类型(*T, struct{}, []int),会返回""
fmt.Println("PkgPath:", t.PkgPath())

// 返回类型的字符串表示。该字符串可能会使用短包名(如用base64代替"encoding/base64")
// 也不保证每个类型的字符串表示不同。如果要比较两个类型是否相等,请直接用Type类型比较
fmt.Println("String:", t.String())

// 返回要保存一个该类型的值需要多少字节;类似unsafe.Sizeof
fmt.Println("Size:", t.Size())

// 返回当从内存中申请一个该类型值时,会对齐的字节数
fmt.Println("Align:", t.Align())

// 返回当该类型作为结构体的字段时,会对齐的字节数
fmt.Println("FieldAlign:", t.FieldAlign())

// 返回该类型的方法集中方法的数目
// 匿名字段的方法会被计算;主体类型的方法会屏蔽匿名字段的同名方法;
// 匿名字段导致的歧义方法会滤除
fmt.Println("NumMethod:", t.NumMethod())

// 返回该类型方法集中的第i个方法,i不在[0, NumMethod())范围内时,将导致panic
// 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
// 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
fmt.Println("Method:", t.Method(0))

// 根据方法名返回该类型方法集中的方法,使用一个布尔值说明是否发现该方法
// 对非接口类型T或*T,返回值的Type字段和Func字段描述方法的未绑定函数状态
// 对接口类型,返回值的Type字段描述方法的签名,Func字段为nil
fmt.Println(t.MethodByName("Says"))

// 返回struct类型的字段数(匿名字段算作一个字段),如非结构体类型将panic
fmt.Println("NumField:", t.NumField())

// 返回struct类型的第i个字段的类型,如非结构体或者i不在[0, NumField())内将会panic
fmt.Println("Field:", t.Field(0))

// 获取字段下的tag标签
// 使用Tag.Get时不存在的tag会返回空字符串
fmt.Println("Field Tag(json):", t.Field(0).Tag.Get("jsons"))

// 查找指定名称的tag,返回 tag内容和布尔值(用于判断是否存在tag)
// 当布尔值为false时,这个tag不存在
// 当布尔值为true时,tag内容为空字符串时,说明tag存在但是设为空值
fmt.Println(t.Field(2).Tag.Lookup("json"))

// 返回索引序列指定的嵌套字段的类型,
// 等价于用索引中每个值链式调用本方法,如非结构体将会panic
fmt.Println("FieldByIndex:", t.FieldByIndex([]int{1}))

// 返回该类型名为name的字段(会查找匿名字段及其子字段),
// 布尔值说明是否找到,如非结构体将panic
fmt.Println(t.FieldByName("Name"))

// 返回该类型第一个字段名满足函数match的字段,布尔值说明是否找到,如非结构体将会panic
fmt.Println(t.FieldByNameFunc(func(s string) bool { return s == "Name" }))

// 返回array类型的长度,如非数组类型将panic
// 数组array类型创建后不可变[3]int{1,2,3}或者[...]int{1,2,3},不全将初始化为0
// 数组类型为[3]int,切片类型为[]int
t.Len()

// 返回map类型的键的类型。如非映射类型将panic
t.Key()

// 返回一个channel类型的方向,如非通道类型将会panic
t.ChanDir()

// 返回func类型的参数个数,如果不是函数,将会panic
t.NumIn()

// 返回func类型的第i个参数的类型,如非函数或者i不在[0, NumIn())内将会panic
t.In(0)

// 返回func类型的返回值个数,如果不是函数,将会panic
t.NumOut()

// 返回func类型的第i个返回值的类型,如非函数或者i不在[0, NumOut())内将会panic
t.Out(0)

// 获取*io.Writer的Type
writerType := reflect.TypeOf((*io.Writer)(nil)).Elem()
// 获取*os.File的Type
fileType := reflect.TypeOf((*os.File)(nil))

// 判断*os.File是否实现了*io.Writer接口
fmt.Println(fileType.Implements(writerType))

// 如果该类型的值可以直接赋值给u代表的类型,返回true
fmt.Println(fileType.AssignableTo(writerType))

// 如该类型的值可以转换为u代表的类型,返回true
fmt.Println(fileType.ConvertibleTo(writerType))



hello := Hello{}

// 返回一个初始化为i接口保管的具体值的Value,ValueOf(nil)返回Value零值
v := reflect.ValueOf(hello)

// 返回v持有的接口保管的值的Value封装,或者v持有的指针指向的值的Value封装
// 如果v的Kind不是Interface或Ptr会panic;如果v持有的值为nil,会返回Value零值
reflect.ValueOf(&hello).Elem()

// 返回v持有的值的类型的Type表示
// 详细查看exampleType()
v.Type()

// 返回v持有的值的类型,如果v是Value零值,返回值为Invalid
v.Kind()
fmt.Println("reflect.String:", v.Kind() == reflect.String)

// 返回v持有的布尔值,如果v的Kind不是Bool会panic
v.Bool()
// 返回v持有的有符号整数(表示为int64),如果v的Kind不是Int、Int8、Int16、Int32、Int64会panic
v.Int()
// ...

// 返回v[i:j](v持有的切片的子切片的Value封装);如果v的Kind不是Array、Slice或String会panic。如果v是一个不可寻址的数组,或者索引出界,也会panic
v.Slice(0, 1)

// 返回v持有值的长度,如果v的Kind不是Array、Chan、Slice、Map、String会panic
v.Len()




// Ex1
func main() {
	var num float64 = 1.2345

	fmt.Println("type: ", reflect.TypeOf(num))
	fmt.Println("value: ", reflect.ValueOf(num))
}

// 运行结果:
// type:  float64
// value:  1.2345


// Ex2 得到原有真实值
var num float64 = 1.2345
// 得到reflect.Value
pointer := reflect.ValueOf(&num)
value := reflect.ValueOf(num)

// 当执行reflect.ValueOf(interface)之后,就得到了一个类型为”relfect.Value”变量,可以通过它本身的Interface()方法获得接口变量的真实内容,然后可以通过类型判断进行转换,转换为原有真实类型
// 可以理解为“强制转换”,但是需要注意的时候,转换的时候,如果转换的类型不完全符合,则直接panic
// Golang 对类型要求非常严格,类型一定要完全符合
// 如下两个,一个是*float64,一个是float64,如果弄混,则会panic
convertPointer := pointer.Interface().(*float64)
convertValue := value.Interface().(float64)

fmt.Println(convertPointer)
fmt.Println(convertValue)

// 运行结果:
// 0xc42000e238
// 1.2345

// Ex3 未知原有类型
func DoFiledAndMethod(input interface{}) {

	getType := reflect.TypeOf(input)
	fmt.Println("get Type is :", getType.Name())

	getValue := reflect.ValueOf(input)
	fmt.Println("get all Fields is:", getValue)

	// 获取方法字段
	// 1. 先获取interface的reflect.Type,然后通过NumField进行遍历
	// 2. 再通过reflect.Type的Field获取其Field
	// 3. 最后通过Field的Interface()得到对应的value
	for i := 0; i < getType.NumField(); i++ {
		field := getType.Field(i)
		value := getValue.Field(i).Interface()
		fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
	}

	// 获取方法
	// 1. 先获取interface的reflect.Type,然后通过.NumMethod进行遍历
	for i := 0; i < getType.NumMethod(); i++ {
		m := getType.Method(i)
		fmt.Printf("%s: %v\n", m.Name, m.Type)
	}
}


// Ex4 设置实际变量的值
// 只有当X是指针的时候,才可以通过reflec.Value修改实际变量X的值

var num float64 = 1.2345
fmt.Println("old value of pointer:", num)

// 通过reflect.ValueOf获取num中的reflect.Value,注意,参数必须是指针才能修改其值
pointer := reflect.ValueOf(&num)
// newValue也是reflect.Value
newValue := pointer.Elem()

fmt.Println("type of pointer:", newValue.Type())
fmt.Println("settability of pointer:", newValue.CanSet())

// 重新赋值
newValue.SetFloat(77)
fmt.Println("new value of pointer:", num)

////////////////////
// 如果reflect.ValueOf的参数不是指针,会如何?
pointer = reflect.ValueOf(num)
//newValue = pointer.Elem() // 如果非指针,这里直接panic,“panic: reflect: call of reflect.Value.Elem on float64 Value”

// 运行结果:
// old value of pointer: 1.2345
// type of pointer: float64
// settability of pointer: true
// new value of pointer: 77


// 调用函数

type person struct {
	name string `json:"fyh"`
	age  int
}

func (p *person) Say(i int, j int) {
	fmt.Println("Hello World!", i, j)
}
func (p person) Says() {
	fmt.Println("Hello World!")
}


p := person{}
// 对于指针方法
v := reflect.ValueOf(&p)
// 对于非指针方法
v := reflect.ValueOf(p)

// 选择方法然后调用
v.Method(0).Call([]reflect.Value{reflect.ValueOf(4), reflect.ValueOf(7)})

// 调用可变参数
v.CallSlice([]reflect.Value{reflect.ValueOf(9), reflect.ValueOf(10), eflect.ValueOf(11)})

// 返回v持有值类型的第i个方法的已绑定(到v的持有值的)状态的函数形式的Value封装
// 返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)
// 如果i出界,或者v的持有值是接口类型的零值(nil),会panic
v.Method(0)

// 返回v的名为name的方法的已绑定(到v的持有值的)状态的函数形式的Value封装
// 返回值调用Call方法时不应包含接收者;返回值持有的函数总是使用v的持有者作为接收者(即第一个参数)
// 如果未找到该方法,会返回一个Value零值
v.MethodByName("Says")

// 判断是否是指针,返回是否可以获取v持有值的指针
// 可以获取指针的值被称为可寻址的。如果一个值是切片或可寻址数组的元素、可寻址结构体的字段、或从指针解引用得到的,该值即为可寻址的
fmt.Println(v.CanAddr())

// 判断是否可以设置值
// 如果v持有的值可以被修改,会返回true。只有一个Value持有值可以被寻址同时又不是来自非导出字段时,它才可以被修改
// 如果返回false,调用Set或任何限定类型的设置函数(如SetBool、SetInt64)都会panic
fmt.Println(v.CanSet())

// 设置bool值。如果v的Kind不是Bool或者v.CanSet()返回false,会panic
v.SetBool(true)

// 设置int值。如果v的Kind不是Int、Int8、Int16、Int32、Int64之一或者v.CanSet()返回false,会panic
v.SetInt(0)

// 设置uinvt值。如果v的Kind不是Uint、Uintptr、Uint8、Uint16、Uint32、Uint64或者v.CanSet()返回false,会panic
v.SetUint(0)

// 设置float值。如果v的Kind不是Float32、Float64或者v.CanSet()返回false,会panic
v.SetFloat(0)

// 设置complex值。如果v的Kind不是Complex64、Complex128或者v.CanSet()返回false,会panic
v.SetComplex(0)

// 设置[]byte值。如果v持有值不是[]byte类型或者v.CanSet()返回false,会panic
v.SetBytes([]byte("Hello World!"))

// 设置string值。如果v的Kind不是String或者v.CanSet()返回false,会panic
v.SetString("Hello World!")

// 设置指针值。如果v的Kind不是UnsafePointer或者v.CanSet()返回false,会panic
v.SetPointer(&Hello{"Go", 12, []string{"Hello", "World"}})

// 设定值的容量。如果v的Kind不是Slice或者n出界(小于长度或超出容量),将导致panic
v.SetCap(10)

// 设定值的长度。如果v的Kind不是Slice或者n出界(小于零或超出容量),将导致panic
v.SetLen(10)

// 用来给v的映射类型持有值添加/修改键值对,如果val是Value零值,则是删除键值对。如果v的Kind不是Map,或者v的持有值是nil,将会panic
// key的持有值必须可以直接赋值给v持有值类型的键类型。val的持有值必须可以直接赋值给v持有值类型的值类型
v.SetMapIndex(reflect.ValueOf("test"), reflect.ValueOf("Hello World!"))

// 将v的持有值修改为x的持有值。如果v.CanSet()返回false,会panic。x的持有值必须能直接赋给v持有值的类型
v.Set(reflect.ValueOf("Hello"))


var c chan int
var fn func(int) int
var m map[string]string
var sl = []int{1, 2, 3}

// 创建一个元素类型为typ、有buffer个缓存的channel类型的Value值
reflect.MakeChan(reflect.TypeOf(c), 1)

// 创建一个具有给定类型、包装函数fn的函数的Value封装。
// 当被调用时,该函数会将提供给它的参数转化为Value切片
// 执行results := fn(args)
// 将results中每一个result依次排列作为返回值
reflect.MakeFunc(reflect.TypeOf(fn), func(args []reflect.Value) (results []reflect.Value) { return []reflect.Value{args[0]} })

// 创建一个特定map类型的Value值
reflect.MakeMap(reflect.TypeOf(m))

// 创建一个具有n个初始空间的map类型的Value值
reflect.MakeMapWithSize(reflect.TypeOf(m), 10)

// 创建一个新申请的元素类型为typ,长度len容量cap的slice类型的Value值
reflect.MakeSlice(reflect.ValueOf(sl).Type(), reflect.ValueOf(sl).Len(), reflect.ValueOf(sl).Cap())

// 返回元素类型为t、方向为dir的channel类型的Type
// 运行时GC强制将通道的元素类型的大小限定为64kb
// 如果t的尺寸大于或等于该限制,本函数将会panic
reflect.ChanOf(reflect.BothDir, reflect.TypeOf(c))

// 返回给定参数和结果类型的func类型的Type
// 例如,如果 k 表示 int 并且 e 表示字符串,则 FuncOf([]Type{k}, []Type{e}, false) 表示 func(int) string
// 可变参数variadic 控制函数是否可变。如果len(in)-1 不代表切片并且可变参数为true,则 FuncOf 会panic
reflect.FuncOf([]reflect.Type{reflect.TypeOf(15)}, []reflect.Type{reflect.TypeOf("Hello")}, false)

// 使用给定的键和元素类型返回map类型的Type
// 例如,如果 k 表示 int 并且 e 表示字符串,则 MapOf(k, e) 表示 map[int]string
// 如果键类型不是有效的映射键类型(也就是说,如果它不执行 Go 的==运算符),则 MapOf会panic
reflect.MapOf(reflect.TypeOf(15), reflect.TypeOf("Hello"))

// 使用给定的计数和元素类型返回数组类型的Type
// 例如,如果 t 表示 int ,则 ArrayOf(5, t) 表示 [5]int
// 如果生成的类型会比可用的地址空间大,ArrayOf 会panic
reflect.ArrayOf(5, reflect.TypeOf(15))

// 返回元素类型为 t 的slice类型的Type
// 例如,如果 t 表示 int , SliceOf(t) 表示 []int
reflect.SliceOf(reflect.ValueOf(sl).Type())

// Ex
var m map[string]string
v := reflect.MakeMap(reflect.TypeOf(m))
// v := reflect.MakeMap(reflect.MapOf(reflect.TypeOf(""), reflect.TypeOf("")))
v.SetMapIndex(reflect.ValueOf("S"), reflect.ValueOf("s"))
fmt.Println(v.Interface().(map[string]string))

// 返回一个Value类型值,该值持有一个指向类型为typ的新申请的零值的指针,返回值的Type为PtrTo(typ)
reflect.New(typ)

// 返回类型t的指针的类型
reflect.PtrTo(typ)

// 返回一个持有类型typ的零值的Value
// 注意持有零值的Value和Value零值是两回事
// Value零值表示不持有任何值,例如Zero(TypeOf(42))返回一个Kind为Int、值为0的Value
// Zero的返回值不能设置也不会寻址
reflect.Zero(typ)

// 将src中的值拷贝到dst,直到src被耗尽或者dst被装满,要求这二者都是slice或array,且元素类型相同
dst :=[]byte("yes")
src := []byte("Hello World!")
reflect.Copy(reflect.ValueOf(dst), reflect.ValueOf(src))
fmt.Println(dst)


oldSlice := []string{"abc", "defg"}
newSlice := []string{"efg", "hij", "klm"}

// will overwrite "efg" and "hij"

var n = reflect.Copy(reflect.ValueOf(newSlice), reflect.ValueOf(oldSlice))

fmt.Printf("Copied %d elements.\n", n)
fmt.Printf("newSlice elements are now : %v \n", newSlice)

// 用来判断两个值是否深度一致:除了类型相同;在可以时(主要是基本类型)会使用==;但还会比较array、slice的成员,map的键值对,结构体字段进行深入比对
// map的键值对,对键只使用==,但值会继续往深层比对,可以正确处理循环的类型
// 函数类型只有都会nil时才相等;空切片不等于nil切片;还会考虑array、slice的长度、map键值对数
reflect.DeepEqual("1", 1)

// Ex
fmt.Println(reflect.DeepEqual([]int{1, 2, 3}, []int{1, 2, 3}))    // true
fmt.Println(reflect.DeepEqual([]int{1, 2, 3}, []int{2, 3}))    // false
fmt.Println([]int{1, 2, 3} == []int{2, 3})    // slice can only be compared to nil
  1. 多线程技巧
// chan的输入和输出循环次数都给定
for _, filename := range filename_list {
    go single(c, filename.Name())
}
for i := 0; i < len(filename_list); i++ {
    <-c
}
func single(c chan string, filename string) {
  c <- ""
}


// 使用WaitGroup进行精细控制
var n sync.WaitGroup
for _, file_name := range file_list {
    n.Add(1)
    go single(&n, c, file_name.Name())
}
go func() {
    n.Wait()
    close(c)
}()
for sub_string := range c {
    // do sth with sub_string
}
func single(n *sync.WaitGroup, c chan string, filename string) {
    defer n.Done()
    c <- ""
}

// 使用互斥锁,相当于缓存量为1的chan
var n sync.Mutex
  1. regexp包
语法 表达式示例 说明 匹配结果
一般字符 abc 匹配自身 abc
. a.c 匹配任意除换行符"\n"外的字符, 在 DOTALL 模式中也能匹配换行符 abc
\ a\.c a\\c 转义字符,使后一个字符改变原来的意思; 如果字符串中有字符 * 需要匹配,可以使用 * 或者字符集[*]。 a.c a\c
[…] a[bcd]e 字符集(字符类),对应的位置可以是字符集中任意字符。 字符集中的字符可以逐个列出,也可以给出范围,如 [abc] 或 [a-c], 第一个字符如果是 ^ 则表示取反,如 [^abc] 表示除了abc之外的其他字符。 abe 或 ace 或 ade
\d a\dc 数字:[0-9] a1c
\D a\Dc 非数字:[^\d] abc
\s a\sc 空白字符:[<空格>\t\r\n\f\v] a c
\S a\Sc 非空白字符:[^\s] abc
\w a\wc 单词字符:[A-Za-z0-9] abc
\W a\Wc 非单词字符:[^\w] a c

语法 说明 表达式示例 匹配结果
* 匹配前一个字符 0 或无限次 abc* ab 或 abccc
+ 匹配前一个字符 1 次或无限次 abc+ abc 或 abccc
? 匹配前一个字符 0 次或 1 次 abc? ab 或 abc
{m} 匹配前一个字符 m 次 ab{2}c abbc
{m,n} 匹配前一个字符 m 至 n 次,m 和 n 可以省略,若省略 m,则匹配 0 至 n 次; 若省略 n,则匹配 m 至无限次 ab{1,2}c abc 或 abbc

语法 说明 表达式示例 匹配结果
^ 匹配字符串开头,在多行模式中匹配每一行的开头 ^abc abc
$ 匹配字符串末尾,在多行模式中匹配每一行的末尾 abc$ abc
\A 仅匹配字符串开头 \Aabc abc
\Z 仅匹配字符串末尾 abc\Z abc
\b 匹配 \w 和 \W 之间 a\b!bc a!bc
\B [^\b] a\Bbc abc

语法 说明 表达式示例 匹配结果
| | 代表左右表达式任意匹配一个,优先匹配左边的表达式 abc|def abc 或 def
(…) 括起来的表达式将作为分组,分组将作为一个整体,可以后接数量词 (abc){2} abcabc
(?P…) 分组,功能与 (…) 相同,但会指定一个额外的别名 (?Pabc){2} abcabc
引用编号为 的分组匹配到的字符串 (\d)abc\1 1abe1 或 5abc5
(?P=name) 引用别名为 的分组匹配到的字符串 (?P\d)abc(?P=id) 1abe1 或 5abc5

// 检查文本正则表达式是否匹配

data := "Hello World![test]"
pattern := `\d*`

ok, err := regexp.Match(pattern, []byte(data))    // 字节片
ok, err = regexp.MatchReader(pattern, strings.NewReader(data))    // RuneReader读取
ok, err = regexp.MatchString(pattern, data)    // 字符串

if err != nil {
    log.Fatal(err)
}
fmt.Println(ok)


// 返回一个字符串,它引用参数文本中的所有正则表达式元字符; 返回的字符串是一个匹配文本文本的正则表达式。例如,QuoteMeta([foo])返回\[foo\]
// 特殊字符有:\.+*?()|[]{}^$
// 这些字符用于实现正则语法,所以当作普通字符使用时需要转换
// 用于生成转义序列
str := regexp.QuoteMeta(`Escaping symbols like: .+*?()|[]{}^$`)
fmt.Println(str)

// 使用Compile,为复杂形式
data := "Hello foo!"

// 编译解析一个正则表达式,并且如果成功返回一个可以用来匹配文本的 Regexp 对象
r, err := regexp.Compile(`foo.?`)
if err != nil {
    log.Fatal(err)
}
// 等同于Compile。但如果表达式不能被解析就会panic
r = regexp.MustCompile(`foo(.?)`)

fmt.Println(r.MatchString(data))    // 匹配字符串
fmt.Println(r.Match([]byte(data)))    // 匹配字符切片
fmt.Println(r.MatchReader(strings.NewReader(data)))    // 匹配RuneReader

fmt.Println("正则表达式:", r.String())    // 返回正则表达式
fmt.Printf("%s\n", r.Find([]byte(data)))    // 查找字节切片,返回第一个匹配内容
fmt.Println(r.FindIndex([]byte(data)))    // 查找字节切片,返回[起始位置, 结束位置]
fmt.Println(r.FindString(data))    // 查找字符串,返回第一个匹配内容
fmt.Println(r.FindStringIndex(data))    // 查找字符串,返回[起始位置, 结束位置]
fmt.Printf("%q\n", r.FindAll([]byte(data), -1))    // 查找字节切片,返回前n个匹配项,n<0表示所有匹配项
fmt.Println(r.FindAllIndex([]byte(data), -1))    // 查找字节切片,返回[[起始位置, 结束位置], [起始位置, 结束位置]...]
fmt.Println(r.FindAllString(data, -1))    // 查找字符串,返回所有匹配的内容
fmt.Println(r.FindAllStringIndex(data, -1))
fmt.Println(r.FindReaderIndex(strings.NewReader(data)))
// 查找字符串,以匹配项为分割符 分割成多个子串
// 最多分割出 n 个子串,第 n 个子串不再进行分割
// 如果 n < 0,则分割所有子串
// 返回分割后的子串列表
fmt.Println(r.Split(data, -1))
// 在 src 中搜索匹配项,并替换为 repl 指定的内容
// 全部替换,并返回替换后的结果
fmt.Printf("%s\n", r.ReplaceAll([]byte(data), []byte("World!")))
fmt.Printf("%s\n", r.ReplaceAllString(data, "World!"))
// 在 src 中搜索匹配项,然后将匹配的内容经过 repl方法 处理后,替换 src 中的匹配项
// 如果 repl 的返回值中有“分组引用符”($1、$name),则将“分组引用符”当普通字符处理
// 全部替换,并返回替换后的结果
fmt.Printf("%s\n", r.ReplaceAllFunc([]byte(data), func(b []byte) []byte { return []byte("World!") }))
fmt.Printf("%s\n", r.ReplaceAllStringFunc(data, func(s string) string { return "World!" }))

// 切换到贪婪模式
// 正则标记“非贪婪模式”(?U),如`(?U)H[\w\s]+o`
r.Longest()

// CompilePOSIX采用最左最长方式搜索,而 Compile 采用最左最短方式搜索,POSIX 语法不支持 Perl 的语法格式:\d、\D、\s、\S、\w、\W

data := "seafood fool"
r := regexp.MustCompile(`foo(.?)`)
// 统计正则表达式中的分组个数(不包括“非捕获的分组”)
fmt.Println(r.NumSubexp())

// 查找字节切片,返回第一个匹配的内容,同时返回子表达式匹配的内容
// [[完整匹配项], [子匹配项], [子匹配项], ...]
fmt.Printf("%s\n", r.FindSubmatch([]byte(data)))
fmt.Println(r.FindSubmatchIndex([]byte(data)))
fmt.Println(r.FindStringSubmatch(data))

// 查找字节切片,返回所有匹配的内容
// 同时返回子表达式匹配的内容
// [ [[完整匹配项], [子匹配项], [子匹配项], ...], [[完整匹配项], [子匹配项], [子匹配项], ...] ]
fmt.Printf("%q\n", r.FindAllSubmatch([]byte(data), -1))
fmt.Println(r.FindAllStringSubmatch(data, -1))

// 实现(?<=...)\w+(?=...)的效果
data := "seafood!efool"
r := regexp.MustCompile(`sea(\w+)\b`)
result := r.FindAllStringSubmatch(data, -1)
for _, r := range result {
    fmt.Println(r[1])
}

// dotAll模式,比如需要匹配文本中的换行符
reg := regexp.MustCompile(`
(?s:(.*?))
`
) result := reg.FindAllStringSubmatch(buf, -1) for _, r := range result { fmt.Println(r[1]) }
  1. runtime包
// 返回Go的根目录。如果存在GOROOT环境变量,返回该变量的值;否则,返回创建Go时的根目录
fmt.Println(runtime.GOROOT())
// 返回Go的版本字符串。它要么是递交的hash和创建时的日期;要么是发行标签如"go1.3"
fmt.Println(runtime.Version())
// 返回本地机器的逻辑CPU个数
fmt.Println(runtime.NumCPU())
// 返回当前进程执行的cgo调用次数
fmt.Println(runtime.NumCgoCall())
// 返回当前存在的Go程数
fmt.Println(runtime.NumGoroutine())
// 执行一次垃圾回收
runtime.GC()
// 设置可同时执行的最大CPU数,并返回先前的设置
// 若 n < 1,它就不会更改当前设置
// 本地机器的逻辑CPU数可通过 NumCPU 查询。本函数在调度程序优化后会去掉
runtime.GOMAXPROCS(2)

go func() {

    fmt.Println("Start Goroutine1")

    // 终止调用它的go程。其它go程不会受影响。Goexit会在终止该go程前执行所有defer的函数。
    // 在程序的main go程调用本函数,会终结该go程,而不会让main返回
    // 因为main函数没有返回,程序会继续执行其它的go程
    // 如果所有其它go程都退出了,程序会panic
    runtime.Goexit()

    fmt.Println("End Goroutine1")
}()

go func() {

    // 使当前go程放弃处理器,以让其它go程运行。它不会挂起当前go程,因此当前go程未来会恢复执行
    runtime.Gosched()
    fmt.Println("Start Goroutine2")
    fmt.Println("End Goroutine2")
}()

go func() {

    // 将调用的go程绑定到它当前所在的操作系统线程
    // 除非调用的go程退出或调用UnlockOSThread,否则它将总是在该线程中执行,而其它go程则不能进入该线程
    runtime.LockOSThread()
    fmt.Println("Start Goroutine3")
    fmt.Println("End Goroutine3")

    // 将调用的go程解除和它绑定的操作系统线程
    // 若调用的go程未调用LockOSThread,UnlockOSThread不做操作
    runtime.UnlockOSThread()
}()
  1. sort包
type People struct {
	Name string
	Age  int
}

type Peoples []People

func (p Peoples) Len() int           { return len(p) }
func (p Peoples) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }
func (p Peoples) Less(i, j int) bool { return p[i].Age < p[j].Age } // 按age递增排序

peoples := Peoples{
    {"Gopher", 7},
    {"Alice", 55},
    {"Vera", 24},
    {"Bob", 75},
}

// 任意切片排序,但是切片必须包含Len(),Swap(),Less()方法
sort.Sort(peoples)
// 任意切片排序,但为稳定排序
sort.Stable(peoples)

// 包装一个Interface接口并返回一个新的Interface接口,对该接口排序可生成递减序列
p := sort.Reverse(peoples)
// 排序
sort.Sort(p)


fmt.Println(peoples)
fmt.Println(sort.IsSorted(peoples))

s := []string{"Go", "Bravo", "Gopher", "Alpha", "Grin", "Delta"}

// 排序为递增顺序
sort.Strings(s)
fmt.Println(s)

// 检查是否已排序为递增顺序
fmt.Println(sort.StringsAreSorted(s))
// 返回x在s中应该存在的位置,无论s中是否存在
// 在递增顺序的s中搜索x,返回x的索引。如果查找不到,返回值是x应该插入s的位置(以保证s的递增顺序),返回值可以是len(s)
index := sort.SearchStrings(s, "Hello")
fmt.Println(index)

s := []int{5, 2, 6, 3, 1, 4}

// 排序为递增顺序
sort.Ints(s)
fmt.Println(s)

index := sort.SearchInts(s, 5)
sort.Float64s(s)
index := sort.SearchFloat64s(s, 2)


people := []struct {
    Name string
    Age  int
}{
    {"Gopher", 7},
    {"Alice", 55},
    {"Vera", 24},
    {"Bob", 75},
}


// 对切片自定义进行排序
// 该接口不能保证是稳定的。对于稳定类型,使用SliceStable
// 如果提供的接口不是切片,则函数将终止
sort.Slice(people, func(i, j int) bool { return people[i].Name < people[j].Name })
sort.SliceStable(people, func(i, j int) bool { return people[i].Name < people[j].Name })
fmt.Println("By name:", people)

sort.Slice(people, func(i, j int) bool { return people[i].Age < people[j].Age })
sort.SliceStable(people, func(i, j int) bool { return people[i].Age < people[j].Age })
fmt.Println("By age:", people)
  1. 捕获外部变量

无论对于循环还是闭包,只要不是以:=初始化的,都将视为外部变量

c := []int{}
for i := 2; i < 10; i++ {
    c = append(c, func() int {
        fmt.Println(&i)
        return i
    }())
}
fmt.Println(c)
// 0xc000068080
// 0xc000068080
// 0xc000068080
// [2 3 4 5 6 7 8 9]

c := []func() int{}
for i := 2; i < 10; i++ {
    c = append(c, func(a int) func() int { return func() int { return i } }(i))
}
for _, f := range c {
    fmt.Println(f())
}
// [10 10 10 ...]
c := []func() int{}
for i := 2; i < 10; i++ {
    c = append(c, func(a int) func() int { return func() int { return a } }(i))
}
for _, f := range c {
    fmt.Println(f())
}
// [2 3 4 ...]
  1. strconv包
// bool转string
s := strconv.FormatBool(true)
// float32转string
// fmt表示格式:'f'(-ddd.dddd)、'b'(-ddddp±ddd,指数为二进制)、'e'(-d.dddde±dd,十进制指数)、'E'(-d.ddddE±dd,十进制指数)、'g'(指数很大时用'e'格式,否则'f'格式)、'G'(指数很大时用'E'格式,否则'f'格式)
// prec控制精度(排除指数部分):对'f'、'e'、'E',它表示小数点后的数字个数;对'g'、'G',它控制总的数字个数。如果prec 为-1,则代表使用最少数量的、但又必需的数字来表示f
s32 := strconv.FormatFloat(float64(v), 'f', -1, 32)
// float64转string
s64 := strconv.FormatFloat(v, 'f', -1, 64)
// int转string
s := strconv.Itoa(v)
// int64转十进制string
// base 必须在2到36之间
s = strconv.FormatInt(int64(v), 10)

// string转bool
bl, err := strconv.ParseBool(s)
// string转float32
s, err := strconv.ParseFloat(s32, 32)
// string转float64
s, err := strconv.ParseFloat(s64, 64)
// string转int
i, err := strconv.Atoi(s)
// 十进制string转int64
i64, err := strconv.ParseInt(s, 10, 64)
  1. strings包
// 字符串倒序
func reverse(s string) string {
	r := []rune(s)
	for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
		r[i], r[j] = r[j], r[i]
	}
	return string(r)
}
s := reverse("Hello, World!")

// 根据字符串创建reader
strings.NewReader("Hello World!")

// 使用提供的多组old、new字符串对创建并返回一个*strings.Replacer,oldnew交替
// 替换是依次进行的,匹配时不会重叠
r := strings.NewReplacer("<", "<", ">", ">")
fmt.Println(r.Replace("This is HTML!"))
// 比较a和b, 返回 0: a等于b, 1: a大于b, -1: a小于b
strings.Compare(a, b)
// 判断a与b是否相同(将unicode大写、小写、标题三种格式字符视为相同)
strings.EqualFold(a, b)
// 判断a是否以b开头,当b为空时true
strings.HasPrefix(a, b)

// 判断a是否以b结尾,当b为空时true
strings.HasSuffix(a, b)

// 如果a以b结尾,则返回a去掉b结尾部分的新string。如果不是,返回a
strings.TrimSuffix(a, b)

// 如果a以b开头,则返回a去掉b开头部分的新string。如果不是,返回a
strings.TrimPrefix(a, b)

// 去除开头结尾所有的 空格换行回车缩进
strings.TrimSpace(a)

// 去除开头结尾所有的 指定字符串中的任意字符
strings.Trim(a, " ")

// 按自定义方法 去除开头结尾所有指定内容
strings.TrimFunc(a, unicode.IsLetter)

// 去除开头所有的 指定字符串中的任意字符
strings.TrimLeft(a, "0123456789")

// 按自定义方法 去除开头所有指定内容
strings.TrimLeftFunc(a, unicode.IsLetter)

// 去除结尾所有的 指定字符串中的任意字符
strings.TrimRight(a, "0123456789")

// 按自定义方法 去除结尾所有指定内容
strings.TrimRightFunc(a, unicode.IsLetter)

// 以一个或者多个空格分割成切片
strings.Fields("  foo bar  baz   ")

// 根据指定方法分割成切片,将满足true的字符连起来后分隔
strings.FieldsFunc("  foo1;bar2,,,,,baz3...", func(c rune) bool {
    return !unicode.IsLetter(c) && !unicode.IsNumber(c) // 以 不是字符或者数字 进行分割
})    // [foo1 bar2 baz3]

// 判断a是否包含b
strings.Contains(a, b)

// 判断是否包含字符串中任意字符,只要包含字符串中一个及以上字符返回true,否则false
strings.ContainsAny("I like seafood.", "fÄo!")

// 判断是否包含rune字符
strings.ContainsRune("I like seafood.", 'f')

// 统计a中包含所有b的个数,如果b为空则返回a的长度
strings.Count(a, b)

// 检索b在a中第一次出现的位置,未检索到返回-1
strings.Index(a, b)

// 检索字符c在s中第一次出现的位置,不存在则返回-1
strings.IndexByte(a, byte('k'))

// 自定义方法检索首个字符的位置,未检索到返回-1
strings.IndexFunc("Hello, 世界", func(c rune) bool {
    return unicode.Is(unicode.Han, c) // 是否包含中文字符
})

// 检索a中首个 字符串中任意字符 的位置,未检索到返回-1
strings.IndexAny(a, "abc")

// 检索a中最后个b的位置,未检索到返回-1
strings.LastIndex(a, b)

// 自定义方法检索最后个字符的位置,未检索到返回-1
strings.LastIndexFunc(a, unicode.IsLetter)

// 将string数组以指定字符连接成一个新的string
s := []string{a, b}
strings.Join(s, ",")

// 返回count个s串联的字符串
// 例如:a = "abc",返回 "abcabc"
strings.Repeat(a, 2)

// 返回一个 将a中的b替换为c 的新string,n为替换个数,-1替换所有
strings.Replace(a, b, c, -1)

// 返回一个 将a中的b替换为c 的新string
strings.ReplaceAll(a, b, c)

// 将a以指定字符分割成string数组
strings.Split(a, b)

// 将a以指定字符分割成string数组, n为分割个数,-1分割所有
strings.SplitN(a, b, 2)

// 将a以指定字符分割成string数组,保留b;b不被消耗,放入前一个组
strings.SplitAfter(a, b)

// 将a以指定字符分割成string数组,保留b。n为分割个数,-1分割所有
strings.SplitAfterN(a, b, 2)

// 返回一个 以空格为界限,所有首个字母大写 的标题格式,返回Title形式的字符串
strings.Title(a)

// 所有字母大写
strings.ToUpper(a)

// 所有字母小写
strings.ToLower(a)

// 遍历a按指定的rune方法处理每个字符
strings.Map(func(r rune) rune {
    if r >= 'A' && r <= 'Z' {
        return r
    } else {
        return 'a'
    }
}, a)
  1. sync包

sync包提供了基本的同步基元,如互斥锁;除了Once和WaitGroup类型,大部分都是适用于低水平程序线程,高水平的同步使用channel通信更好一些

// 协程同步
// 声明sync.WaitGroup用于等待一组线程的结束
var wg sync.WaitGroup

for i := 0; i < 10; i++ {

    // 添加delta个等待线程
    // 向内部计数加上delta,delta可以是负数;如果内部计数器变为0,Wait方法阻塞等待的所有线程都会释放,如果计数器小于0,方法panic
    // 注意Add加上正数的调用应在Wait之前,否则Wait可能只会等待很少的线程。一般来说本方法应在创建新的线程或者其他应等待的事件之前调用
    wg.Add(1)

    go func(i int) {

        fmt.Println(i)
        // 减少WaitGroup计数器的值,应在线程的最后执行
        wg.Done()
    }(i)
}

// 阻塞直到WaitGroup计数器减为0
wg.Wait()
fmt.Println("finish")


// 协程中只调用单次方法
// 声明sync.Once只执行一次动作的对象
var once sync.Once

// 声明一个bool值的channel
done := make(chan bool)

for i := 0; i < 10; i++ {
    go func() {
        // 当且仅当第一次被调用时才执行函数f
        // 如果once.Do(f)被多次调用,只有第一次调用会执行f,即使f每次调用Do 提供的f值不同。需要给每个要执行仅一次的函数都建立一个Once类型的实例
        // Do用于必须刚好运行一次的初始化。因为f是没有参数的,因此可能需要使用闭包来提供给Do方法调用
        once.Do(func() {
            fmt.Println("Only once")
        })
        done <- true
    }()
}

for i := 0; i < 10; i++ {
    <-done
}


// 利用互斥锁进行线程操作
// 初始化一个互斥锁
// 可以创建为其他结构体的字段,零值为解锁状态
// Mutex类型的锁和线程无关,可以由不同的线程加锁和解锁
var locker = new(sync.Mutex)

// 使用锁locker创建一个*sync.Cond
// Cond实现了一个条件变量,一个线程集合地,供线程等待或者宣布某事件的发生
c := sync.NewCond(locker)

// 锁定
c.L.Lock()

// 唤醒所有等待c的线程。调用者在调用本方法时,建议(但并非必须)保持c.L的锁定
c.Broadcast()

// 唤醒等待c的一个线程(如果存在)。调用者在调用本方法时,建议(但并非必须)保持c.L的锁定
c.Signal()

// 解锁
c.L.Unlock()

// 自行解锁c.L并阻塞当前线程,在之后线程恢复执行时,Wait方法会在返回前锁定c.L
// 和其他系统不同,Wait除非被Broadcast或者Signal唤醒,不会主动返回
//c.Wait()


// 初始化一个读写互斥锁
// 该锁可以被同时多个读取者持有或唯一个写入者持有
// RWMutex可以创建为其他结构体的字段;零值为解锁状态
// RWMutex类型的锁也和线程无关,可以由不同的线程加读取锁/写入和解读取锁/写入锁
var rw = new(sync.RWMutex)

// 将rw锁定为写入状态,禁止其他线程读取或者写入
rw.Lock()

// 解除rw的写入锁状态,如果未加写入锁会导致运行时错误
rw.Unlock()

// 将rw锁定为读取状态,禁止其他线程写入,但不禁止读取
rw.RLock()

// 解除rw的读取锁状态,如果m未加读取锁会导致运行时错误
rw.RUnlock()

// 返回一个互斥锁,通过调用rw.Rlock和rw.Runlock实现了Locker接口
rw.RLocker()



// 声明并发Map
var m sync.Map

// 将键值对保存到sync.Map
m.Store("a", "Hello World!")
m.Store("b", "Hello Gopher!")
m.Store("c", "Hello zc!")

// 从sync.Map中根据键取值
fmt.Println(m.Load("c"))

// 根据键删除对应的键值对
m.Delete("c")

// 遍历所有sync.Map中的键值对
m.Range(func(k, v interface{}) bool {
    fmt.Println("iterate:", k, v)
    return true
})
  1. tabwriter

创建格式化文本

// 创建一个过滤器
w := new(tabwriter.Writer)

// 初始化,第一个参数指定输出目标
// minwidth 最小单元长度
// tabwidth tab字符的宽度
// padding  计算单元宽度时会额外加上它
// padchar  用于填充的ASCII字符,如果是'\t',则Writer会假设tabwidth作为输出中tab的宽度,且单元必然左对齐
// flags    格式化控制
// 等同于 tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0)
w.Init(os.Stdout, 0, 8, 0, '\t', 0)

w.Write([]byte("a\tb\tc\td\t."))
w.Write([]byte("123\t12345\t1234567\t123456789\t.\n"))

// 在最后一次调用Write后,必须调用Flush方法以清空缓存,并将格式化对齐后的文本写入生成时提供的output中
w.Flush()
  1. time包
// 返回当前本地时间
time.Now()

// 返回指定时间
t := time.Date(2019, time.February, 7, 0, 0, 0, 0, time.UTC)

// 返回unix时间,即从时间点January 1, 1970 UTC到时间点t所经过的时间(单位秒)
t.Unix()

// 判断两个时间是否相同,会考虑时区的影响,因此不同时区标准的时间也可以正确比较。本方法和用t==u不同,这种方法还会比较地点和时区信息
t.Equal(time.Now())

// 判断t的时间点是否在u之前
t.Before(time.Now())

// 判断t的时间点是否在u之后
t.After(time.Now())

// 返回时间点t对应的年、月、日
t.Date()

// 返回t对应的那一天的时、分、秒
t.Clock()

// 返回时间点t对应的年份
t.Year()

// 返回时间点t对应那一年的第几月
t.Month()

// 返回时间点t对应那一月的第几日
t.Day()

// 返回时间点t对应的那一周的周几
t.Weekday()

// 返回t对应的那一天的第几小时,范围[0, 23]
t.Hour()

// 返回t对应的那一小时的第几分种,范围[0, 59]
t.Minute()

// 返回t对应的那一分钟的第几秒,范围[0, 59]
t.Second()

// 增加2小时, 减少使用负号
t.Add(time.Hour * 2)

// 获取当前时间减去指定时间的时间
// 如果结果超出了Duration可以表示的最大值/最小值,将返回最大值/最小值。要获取时间点t-d(d为Duration),可以使用t.Add(-d)
t.Sub(time.Now())

// 返回距离t最近的时间点,该时间点应该满足从Time零值到该时间点的时间段能整除d;如果有两个满足要求的时间点,距离t相同,会向上舍入;如果d <= 0,会返回t的拷贝
t.Round(time.Hour)

// 根据layout指定的格式返回t代表的时间点的格式化文本表示
t.Format("2006-01-02 15:04:05")

// 返回采用如下格式字符串的格式化时间
// "2006-01-02 15:04:05.999999999 -0700 MST"
t.String()

// 返回从t到现在经过的时间,等价于time.Now().Sub(t)
time.Since(t)

// 阻塞当前go程至少d代表的时间段。d<=0时,Sleep会立刻返回
time.Sleep(time.Second * 2)

// 创建一个Timer,它会在最少过去时间段d后到期,向其自身的C字段发送当时的时间
time.NewTimer(time.Minute * 2)

// 会在另一线程经过时间段d后向返回值发送当时的时间。等价于NewTimer(d).C
time.After(time.Minute * 2)

// 返回一个新的Ticker,该Ticker包含一个通道字段,并会每隔时间段d就向该通道发送当时的时间
// 它会调整时间间隔或者丢弃tick信息以适应反应慢的接收者
// 如果d<=0会panic。关闭该Ticker可以释放相关资源
time.NewTicker(time.Second * 10)

// 是NewTicker的封装,只提供对Ticker的通道的访问。如果不需要关闭Ticker,本函数就很方便
time.Tick(time.Second * 10)
  1. unicode包
// 判断一个字符是否是控制字符,主要是策略C的字符和一些其他的字符如代理字符
unicode.IsControl(c)

// 判断一个r字符是否是十进制数字字符
unicode.IsDigit(c)

// 判断一个字符是否是unicode图形。包括字母、标记、数字、符号、标点、空白,参见L、M、N、P、S、Zs
unicode.IsGraphic(c)

// 判断一个字符是否是字母
unicode.IsLetter(c)

// 判断字符是否是小写字母
unicode.IsLower(c)

// 判断字符是否是大写字母
unicode.IsUpper(c)

// 判断一个字符是否是标记字符
unicode.IsMark(c)

// 判断一个字符是否是数字字符
unicode.IsNumber(c)

// 判断一个字符是否是unicode标点字符
unicode.IsPunct(c)

// 判断一个字符是否是空白字符
// 在Latin-1字符空间中,空白字符为:'\t', '\n', '\v', '\f', '\r', ' ', U+0085 (NEL), U+00A0 (NBSP).其它的空白字符请参见策略Z和属性Pattern_White_Space
unicode.IsSpace(c)

// 判断一个字符是否是unicode符号字符
unicode.IsSymbol(c)

// 得到rune, int32
a := 'g'

// 得到byte, uint8
var a byte = 'g'

// 得到string
a := "g"

// 转大写
unicode.ToUpper(lcG)
// 转小写
unicode.ToLower(lcG)
// 转标题
unicode.ToTitle(lcG)


// utf8包实现了对utf-8文本的常用函数和常数的支持,包括rune和utf-8编码byte序列之间互相翻译的函数
var b = []byte("Hello World!")

// 判断切片p是否包含完整且合法的utf-8编码序列
utf8.Valid(b)

// 判断r是否可以编码为合法的utf-8序列
utf8.ValidRune('H')

// 判断s是否包含完整且合法的utf-8编码序列
utf8.ValidString(string(b))

// 将r的utf-8编码序列写入p(p必须有足够的长度),并返回写入的字节数
utf8.EncodeRune(b, 'H')

// 解码p开始位置的第一个utf-8编码的码值,返回该码值和编码的字节数
// 如果编码不合法,会返回(RuneError, 1)。该返回值在正确的utf-8编码情况下是不可能返回的
utf8.DecodeRune(b)

// 类似DecodeRune但输入参数是字符串
utf8.DecodeRuneInString(string(b))
  1. unsafe包

unsafe包提供了一些跳过go语言类型安全限制的操作

// 返回类型v本身数据所占用的字节数
// 返回值是“顶层”的数据占有的字节数
// 例如,若v是一个切片,它会返回该切片描述符的大小,而非该切片底层引用的内存的大小
s := unsafe.Sizeof(hello)
fmt.Println(s)

// 返回类型v所代表的结构体字段在结构体中的偏移量,它必须为结构体类型的字段的形式
// 换句话说,它返回该结构起始处与该字段起始处之间的字节数
f := unsafe.Offsetof(hello.b)
fmt.Println(f)

// 返回类型v的对齐方式(即类型v在内存中占用的字节数)
// 若是结构体类型的字段的形式,它会返回字段f在该结构体中的对齐方式
a := unsafe.Alignof(hello)
fmt.Println(a)
  1. 字符串trick
  • ASCII 字符串长度使用 len() 函数
  • Unicode 字符串长度使用 utf8.RuneCountInString() 函数
  1. 遍历字符串
// 遍历ASCII字符
theme := "狙击 start"
for i := 0; i < len(theme); i++ {
    fmt.Printf("ascii: %c  %d\n", theme[i], theme[i])
}

// 遍历Unicode字符
theme := "狙击 start"
for _, s := range theme {
    fmt.Printf("Unicode: %c  %d\n", s, s)
}

UTF-8和Unicode的区别

Unicode 与 ASCII 类似,都是一种字符集。

字符集为每个字符分配一个唯一的 ID,我们使用到的所有字符在 Unicode 字符集中都有一个唯一的 ID,例如上面例子中的 a 在 Unicode 与 ASCII 中的编码都是 97。汉字“你”在 Unicode 中的编码为 20320,在不同国家的字符集中,字符所对应的 ID 也会不同。而无论任何情况下,Unicode 中的字符的 ID 都是不会变化的。

UTF-8 是编码规则,将 Unicode 中字符的 ID 以某种方式进行编码,UTF-8 的是一种变长编码规则,从 1 到 4 个字节不等。编码规则如下:

  • 0xxxxxx 表示文字符号 0~127,兼容 ASCII 字符集。
  • 从 128 到 0x10ffff 表示其他字符。
  1. 类型转换

在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。由于Go语言不存在隐式类型转换,因此所有的类型转换都必须显式的声明

类型转换只能在定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型(将 int16 转换为 int32)。当从一个取值范围较大的类型转换到取值范围较小的类型时(将 int32 转换为 int16 或将 float32 转换为 int),会发生精度丢失(截断)的情况。

只有相同底层类型的变量之间可以进行相互转换(如将 int16 类型转换成 int32 类型),不同底层类型的变量相互转换时会引发编译错误(如将 bool 类型转换为 int 类型)

  1. 指针解析

指针(pointer)在Go语言中可以被拆分为两个核心概念:

  • 类型指针,允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据,类型指针不能进行偏移和运算。
  • 切片,由指向起始元素的原始指针、元素数量和容量组成。

其实,指针是 C/C++ 语言拥有极高性能的根本所在,在操作大块数据和做偏移时即方便又便捷。因此,操作系统依然使用C语言及指针的特性进行编写

C/C++ 中指针饱受诟病的根本原因是指针的运算和内存释放,C/C++ 语言中的裸指针可以自由偏移,甚至可以在某些情况下偏移进入操作系统的核心区域,我们的计算机操作系统经常需要更新、修复漏洞的本质,就是为解决指针越界访问所导致的“缓冲区溢出”的问题。

指针地址和指针类型

一个指针变量可以指向任何一个值的内存地址,它所指向的值的内存地址在 32 和 64 位机器上分别占用 4 或 8 个字节,占用字节的大小与所指向的值的大小无关。当一个指针被定义后没有分配到任何变量时,它的默认值为 nil。指针变量通常缩写为 ptr

每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用在变量名前面添加&操作符(前缀)来获取变量的内存地址(取地址操作),格式如下:

ptr := &v    // v 的类型为 T
  1. 打印x的类型
fmt.Printf("%T",x)
fmt.Println(reflect.TypeOf(x))
  1. 创建指针的另一种方法

new() 函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向默认值。

str := new(string)
*str = "Go语言教程"
// str = &"Go语言教程"    // 报错,无法对右值取地址
fmt.Println(*str)

你可能感兴趣的:(golang)