GO学习笔记

228 阅读19分钟

0.学前准备


Go安装(mac):
使用brew安装比较方便。Homebrew外网的域名被污染。使用如下链接配置homebrew即可。
https://zhuanlan.zhihu.com/p/90508170

配置完成后,执行命令安装:
brew install go 


Vscode:
安装完后:打开终端设置如下
配置国内镜像(mac/linux),下载相关的包
go env -w GO111MODULE=on
go env -w GOPROXY=https://goproxy.io
go env -w GOSUMDB=off
go env 用来查看是否配置成功

shift+command+p 输入go install选择 GO:Install/Update tools即可。


1.基础部分

1.1 变量定义/内置变量类型

  • var为定义变量的关键字,函数内部定义可以省略,定义的变量时。变量名在前,类型在后。
  • 可以一次定义多个不同类型的变量,且变量的类型可以省略,编译器自动处理和识别。
  • 定义的变量必须有值,不赋予有默认值,数字类型为0,字符串为“”,定义的变量必须使用。
  • 定义在函数内部的变量,作用域在函数内部。
  • 定义在函数外部的变量,作用域在包内,也就是第一行的package main这个包里面。无全局变量概念。
  • 函数内部第一次定义变量可以省略var,通过:=来赋值。第二次赋值的时候必须使用=
  • 函数外部定义变量必须有var关键字,可以把多个变量放在括号内部定义。
  • go的变量类型
  • bool,string 布尔类型/字符串类型
  • (u)int,(u)int8/16/32/64,uintptr (无符号)不规定整数类型,系统是32位的就是32,64位的就是64,没有long这种规定,如果长一点直接定义int64即可/规定长度整型/,uintptr是整型指针(类型也跟操作系统位数有关)。
  • byte,rune byte8位字节类型,rune是32位的占用1字节,是go语言中的char类型。两个都可以跟整数混用,可以理解为整数的别名。rune是用int32来表示的。
  • float32,float64,complex64,complex128 浮点型,complex是复数,有实和虚部,complex64是32+32的实部和虚部,complex128是64+64的实部和虚部。
  • go是强制类型转换,没有隐式类型转换。
package main

import "fmt"

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

// 用括号定义多个变量
var (
	a = 3
	b = 4
	c = 5
)
func variable(){
	var a int  //值为0
	var s string //值为空
	fmt.Println(a,s)  
	fmt.Printf("%d,%q\n",a,s) //使用printf并%q可以打印出空串

//变量被定义了,必须要被使用。类型可以省略,编译器可以自动识别和处理。
	var c,d = 4 ,5
	fmt.Println(c,d)
    
 //省略var,第一次定义必须用:=(函数外定义不可以用必须有关键字),第二次的时候使用=即可。
	e,f,g := 6,true,"ok!"
        e = 7
	fmt.Println(e,f,g)
}

1.2 常量定义/枚举类型


定义1:普通定义
const a=1 

定义2:在函数内定义
func consts(){
	const filename = "123.txt"
	const a, b int = 3, 4
	const c, d = 5, 6
	var e,f int
	e = int(math.Sqrt(float64(a*a + b*b)))   //e定义了int类型 所以必须用float转换
	f = int(math.Sqrt(c*c + d*d))           //没有定义类型,不需要转换,按照文本处理,可以作为各种类型使用

定义3:在括号内定义
	const (
	filename = "123.txt"
	a, b int = 3, 4
	c, d = 5, 6
	)

特殊的常量类型:枚举类型
func enums() {
	const (
		cpp    = 0
		java   = 1
		python = 2
		golang = 30
	)
	fmt.Println(cpp, java, python, golang)
 或者使用iota自增更方便定义,效果一样。
 	const (
		cpp = iota
		_    //跳过值
		python
		golang
                vue。 //值是4
	)

iota也可以参与运算,只要有表达式就可以得到想要的结果,输出各种b的大小
	const(
		b = 1 << (10 * iota)
		kb
		mb
		gb
		tb
		pb
	)
	fmt.Println(b,kb,mb,gb,tb,gb)
}

1.3 if/switch/for

  • if 条件判断,条件中的变量可以赋值,变量的作用于在if语句中,其他不可引用。
  • if语句可以写成类似于其他语言的for格式。
  • switch无需写break。默认每个case后自动break,如果不需要break需要使用fallthrough。
  • switch可以没有表达式。在内部作定义。
  • for的条件不能有括号,且条件都可以省略掉,条件都省略后就是死循环。
  • 无while。for就可以实现。

if

func main() {
	const filename = "123.txt"

//写法1
/*读取文件内容,返回两个值就需要两个参数来接受值,[]byte-就是文件内容/error-可能读不到文件
	contents,err := ioutil.ReadFile(filename)
	if err != nil{
		//打印错误
		fmt.Println(err)
	} else {
		//[]byte 是数组,所以使用printf %s来打印内容
		fmt.Printf("%s\n", contents)
	}
*/

//写法2,类似于写成 其他语言的for格式
	//if条件里可以赋值
	//contents和err的作用域只在if语句中。其他不能引用
	if contents, err := ioutil.ReadFile("123.txt"); err != nil {
		fmt.Println(err)
	} else {
		fmt.Printf("%s\n", contents)
	}
}

switch


import (
	"fmt"
)

func main() {
	fmt.Println(
		grade(10),
		grade(70),
		grade(90),
		grade(0),
	)

}

func grade(score int) string {
	g := ""
	switch {
	case score <= 0 || score > 100:
		//panic中断程序运行,并返回错误
		panic(fmt.Sprintf("wrong score %d ", score))
	case score < 60:
		g = "D"
	case score < 80:
		g = "C"
	case score < 90:
		g = "B"
	case score <= 100:
		g = "A"
	}
	return g
}

for

package main

import (
	"fmt"
	"strconv"
)

func main() {
	fmt.Println(
		TenchangeBin(0),
		TenchangeBin(200),
	)

}

//10进制转2进制,首先对2取模,找出最低位,在除以2,得到的数字在对2取模,找出第二位,以此类推,除到0的时候结束,经过顺序反转过来就是2进制的值
func TenchangeBin(n int) string {
	//无起始条件,结束条件是n>0,递增是n=n/2
	result := ""
	if n == 0 {
		panic("zero can't change to bin")
	}
	for ; n > 0; n /= 2 {
		//最低位
		dw := n % 2
		// 把每次的结果都加在前边 1 01 101这样。
		//result是字符串,dw是数字,所以将数字专为字符串使用strconv.itoa
		result = strconv.Itoa(dw) + result
	}

	return result
}


1.4 函数/指针

函数-function

  • 函数是一等公民。
  • 函数定义与变量定义一直,函数名在前类型在后,参数名也是在前类型在后,同类型可以写在一起。
  • 函数可以有多个返回值,如果其中一个不想返回,可以把它变成_ 即可。比如 a,_ := div(a,b),不要乱用,大部分是用在返回一个值和一个错误。
  • 多个返回值定义的时候,可以用int,error来定义参数,同是使用fmt.Errorf来打印错误。这不会中断程序的运行。
  • 函数的函数体和参数都可以包含函数。函数也可以使用匿名函数来实现功能,不需要特别定义一个函数。
  • 可变参数列表,func sum(numbers ...int) int{}, ...int就是可变参数列表,可以传递一个或者多个参数进去。
  • go语言是值传递。调用函数,参数就要copy一份。
  • go语言通过指针来实现参数传递。
package main

import (
	"fmt"
	"math"
	"reflect"
	"runtime"
)

func main() {
	fmt.Println(apply(func (a,b int) int  {
		return int(math.Pow(float64(a),float64(b)))	
	},3,4))
}

//


/*math.pow用于计算几次方,返回值/返回值都是float所以需要强制类型转换一下。
func pow(a, b int) int {
	return int(math.Pow(float64(a), float64(b)))
}

也可以直接写在匿名函数里面。不需要定义在包里面。
如果使用单独的函数的话,调用的时候就是main.pow名称
如果不使用的话。就是main.main.func1名称
*/

//函数式编程,第一个参数是op ,op又是一个函数。
//op func 有两个参数返回是一个int,接着还有a,b两个int参数
//把收到的a,b两个参数给allpy to op函数来操作。返回int
func apply(op func(int, int) int, a, b int) int { //获取函数的名字
	//获得函数名称,展示会调用那个函数
	p := reflect.ValueOf(op).Pointer() //获得函数的指针
	opName := runtime.FuncForPC(p).Name()
	fmt.Printf("calling funcation-name is %s with args %d,%d,", opName, a, b)
	return op(a, b)
}

指针-ptr

  • 指针不能运算。
  • 值传递不能给函数做操作不影响之前的值。
  • 参数传递的是指针地址,修改后影响之前的值。
package main

import "fmt"

func main() {
	//值传递
        a, b := 3, 4
	swap(a, b)
	fmt.Println(a, b)

        //参数传递,使用指针
	swap1(&a,&b)
	fmt.Println(a,b)
        
        //最简单的调换位置
        a, b = swap2(a, b)
	fmt.Println(a, b)
}

func swap(a, b int) {
	a,b = b,a
}

func swap1(a,b *int){
	*a,*b = *b,*a
}

func swap2(a, b int) (int, int) {
	return b, a
}

1.5 数组/切片/Map

数组-array

  • 数组是值类型。不是参数传递。[10]int和[20]int是不同类型。函数调用不会更改值。但是使用指针就会更改值。但是指针很麻烦,使用slice即可更改。
  • 定义使用var关键字,数组名在前,类型在后,且必须指定大小。 var arr1 [5]int,不赋值默认是0
  • 可以使用:=来初定义数组,但是必须赋初值。对于大小[5]可以用[...]来替代。让编译器自己判断大小
  • grade [4][5]int 4行5列。4个长度为5的int数组
  • 通过rang来遍历数组,获得数组的值和下标。任何地方都可以使用_省略变量。
  • arr [5]int ,arr2 [3] int。这是不同的类型。
  • go语言一般不使用数组,使用最多的是切片。
package main

import "fmt"

func printarr(arr [5]int) {
	arr[0] = 100 //
	for i, v := range arr {
		fmt.Println(i, v)
	}

}

//通过指针应用,可以修改数组的值
func printarr2(arr *[5]int) {
	arr[0] = 200 //
	for i, v := range arr {
		fmt.Println(i, v)
	}

}

func main() {
	//使用var定义,变量名写在前,类型写在后,且必须指定大小。
	var arr1 [5]int
	//可以使用:=但是必须赋予初值
	arr2 := [3]int{1, 2, 3}
	//可以使用...。由编译器来判断大小。但也必须赋初值。
	arr3 := [...]int{4, 5, 6, 7, 8}
	fmt.Println(arr1, arr2, arr3)

	var grid [4][5]int

	fmt.Println(grid)

	printarr(arr1) //在函数中第一行arr[0]=100 调用函数的时候下标为0的值是100
	printarr(arr3)

	fmt.Println("ok!!!!")
	fmt.Println("arr1 values is ", arr1) //虽然在函数中赋值了。但是数字的值并没有变化
	fmt.Println("arr3 values is ", arr3)

	printarr2(&arr1) //在函数中第一行arr[0]=100 调用函数的时候下标为0的值是100
	printarr2(&arr3)

	fmt.Println("ok!!!!")
	fmt.Println("arr1 values is ", arr1) //通过指针,变成了参数传递,第一个值已经变了。
	fmt.Println("arr3 values is ", arr3)

	/*cannot use arr2 (variable of type [3]int) as type [5]int in argument to printarr
		printarr(arr2)
	    打印arr2就会报错。[3]int和[5]int不是一个类型。无法传递。
	*/
}

切片-slice

  • 数组,一般是半闭半开区间,包含左边值,不包含右边值。
  • slice不是值类型。slice是一个视图。修改了之后原本的底层数组也会被修改。
  • slice本身没有数据,是对底层array的一个view。
  • slice可以一再reslice。每一次的slice都是针对自己的slice。
  • slice可以向后扩展,不可以向前扩展。slice是对arrry的view。虽然限定了[2:6],取7的值时候就报错。但是reslice后还可以扩展。因为array没有结束。
  • slice,s[i]不可以超过len(s),向后扩展不可以超过底层数组的cap(s)
  • 添加元素如果超过了cap,则会重新配分更大的底层数组。原数组会考呗过去,原数组如果没人用会被垃圾回收掉。
  • 值传递的关系,必须接受append的返回值,因为lens和caps都会变化。
  • 追加新值:s = append(s,val)

image.png

image.png

package main

import "fmt"

//[]int 代表slice
func updateslice(s []int){
	s[0]=100
}



func main() {
	arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
	s := arr[2:6]  //半开半闭,包含2,但是不包含6
	fmt.Println("print slice s", s)
    //开始和结尾都可以省略
	fmt.Println("arr[2:6]=",arr[2:6])
	fmt.Println("arr[:6]=",arr[:6])
	fmt.Println("arr[2:]=",arr[2:])
	fmt.Println("arr[:]=",arr[:])

	s1 := arr[2:]
	s2 := arr[:]
	fmt.Println("s1 = ",s1)
	fmt.Println("s2 = ",s2)

	updateslice(s1)
	fmt.Println("update s1 = ",s1)  //s1现在是100,3,4,5,6,7
	fmt.Println("update arr now = ",arr)//arr现在是0 1 100 3 4 5 6 7
	fmt.Println("s2 = ",s2) //s2现在是0 1 100 3 4 5 6 7

    fmt.Println()
	updateslice(s2)
	fmt.Println("update arr now = ",arr)//arr现在是100  1 100 3 4 5 6 7
	fmt.Println("s2 = ",s2) //s2现在是100 1 100 3 4 5 6 7

    //reslice
	fmt.Println("s2 now =",s2)
	s2 = s2[:5]
	fmt.Println("frist reslice =",s2)
	s2 = s2[2:]
	fmt.Println("second reslice =",s2)

    //slice的扩展
	arr[0],arr[2] = 0,2
    s1 = arr[2:6]
	s2 = s1[3:5]
	
	fmt.Println()
	fmt.Println("arr now =",arr)
	fmt.Printf("s1=%v,len(s1)=%d,cap(s1)=%d\n",s1,len(s1),cap(s1)) //还可以看到6 7
	fmt.Println()
	fmt.Printf("s2%v,len(s2)=%d,cap(s2)=%d\n",s2,len(s2),cap(s2)) //还可以看到7

	s3 := s2[3:7]
	fmt.Println(s3)   //这就会报错。因为超过了cap的范围。
        
        //追加值
        fmt.Println("s2 now = ",s2)
	s3 := append(s2,10)   //s2的view新增了10,本来s2点cap是7,追加10之后,7变为10.结果是5,6,10
	//s4和s5不是对arr的view,是系统新开的一个slice。且我们看不见的。
	s4 := append(s3,11)    //结果是 5,6,10,11
	s5 := append(s4,12)    //结果是 5,6,10,11,12
	fmt.Println("s3 ,s4 ,s5 = ",s3,s4,s5)

	fmt.Println("now arr = ",arr) //结果是0 1 2 3 4 5 6 10. 11和12已经不是对arr的view了。
        
        

//slice 新建/删除/拷贝

package main

import "fmt"

//查看len和cap的大小和分配。当cap放不下的时候乘以2来扩展,依次8 16 32 64这样扩展
func printslice(s []int){
	fmt.Printf("%v,len=%d, cap=%d \n",s,len(s),cap(s))
}



func main() {
	
	//创建slice
	var s []int //定义一个空slice,go语言默认使用zerovalue,所以空slice的值是nil
	//使用for循环append奇数
	for i:=0 ; i< 100 ;i++{
		printslice(s)
		s= append(s, 2 *i+1)
	}
	fmt.Println(s)

	//创建slice,创建一个数组并赋值,然后在slice去view数组。
	s1 := []int{2,4,6,8}
	fmt.Println(s1)

	//知道创建的slice大小,但是暂时没有赋值。使用make来创建即可。
	s2 := make([]int,16)   //创建一个16大小的slice
	s3 := make([]int,10,32)  //创建一个长度为10的slice,但是cap是32的。预估增长比较大所以开始就多分配空间。
	printslice(s2)
	printslice(s3)


	//复制slice
	copy(s2,s1)    //先copy后source,所以s2的值就是2,4,6,8,然后12个0
	printslice(s2)

	/*删除slice的元素,并将后的元素左移。
    假如删除8这个下标为3的元素,一般可以是s2[:3]+s2[4:],但是go没有+法
	所以使用append来实现,对于后边11个0.可以使用...来替代就可以了。不用一个一个的打。
    长做的是删除头尾的元素,中间元素不太多。
	*/
	s2 = append(s2[:3],s2[4:]...)
	printslice(s2)

	//删除头
	front := s2[0]
	s2 = s2[1:]
	printslice(s2)

	//删除尾
	tail := s2[len(s2)-1]
	s2 = s2[:len(s2)-1]

	fmt.Println(front,tail)
	fmt.Println(s2)

}
}

Map

  • map[key]values,map[k1]map[k2]vales(复合map,k1是key,值是一个[k2]values)
  • 使用make可以创建map,还有var和:= 但是make最简单。
  • 获取元素使用m[key]
  • key不存在不会报错,获得初始值nil
  • 使用value,ok:=m[key] 判断key是否存在,不存在返回false,存在返回true
  • 使用delete(m,"name") 来删除一个key
  • 使用range来遍历map,结果是无序的,如果需要顺序需要key排序。放入到slice里面,slice排序,在遍历
  • 使用len来获得元素个数
  • go-map使用哈希表,必须可以比较想等,除了slice,map,function的内建类型都可以作为key
  • struct类型不包含上述内建字段也可以做为key
package main

import (
	"fmt"
)

func main() {
	//新建map
	m := map[string]string{
		"name": "vip",
		"site": "tsrj",
	}
	//使用make新建空map,默认值是emptry map
	m2 := make(map[string]int)
	//使用var来定义,默认值是nil。nil和empty map可以在运算的使用混用。
	var m3 map[string]int

	fmt.Println(m, m2, m3)

	//遍历map,k的顺序随机。如果只需要k,那么v可以用_标识,这样就不会打印v的数据。
	for k, v := range m {
		fmt.Println(k, v)
	}

	//获得其中一个k的values,通过ok来判断,存在返回true,不存在返回flase
	title, ok := m["name"] //通过访问k得出values值
	fmt.Println(title, ok)

	//一般使用这样的写法来判断k是否存在,存在返回值,不存在返回值k不存在。
	if title1, ok := m["name1"]; ok {
		fmt.Println(title1)
	} else {
		fmt.Println("key dones not exist")
	} //对于k不存在,则输出一个空值。并不会报错

	//删除元素,使用delete
	delete(m, "name")
	name1, ok := m["name"]
	fmt.Println(name1, ok)
}



/*
 for map
 寻找最长不含有重复字符的子串,来自leetcode
1. 一个字符串,对于每一个字母x。
2.有一个start位置,表示当前找到的最大的子串的开始位置,start->x-1这一段是没有重复字符的。
3.lastocc[x]用于记录x在start->x-1之间最后出现的位置在哪里。
4.x未出现或者出现在start之前,无需操作
5.x出现在start->x-1中间,就需要更新start位置,更新到lastocc[x]+1这个位置
6.最后更新lastocc[x]更新maxlen
7.中文暂时没有处理好。用rune来处理

*/

package main

import (
	"fmt"
)

func lesofnorepsubstr(s string) int {

	lastocc := make(map[byte]int)
	start := 0
	maxlen := 0

	//遍历字符串,trun没讲到先用byte转换
   

	for i, ch := range []byte(s) {
		//条件反过来不是小于什么都不做,而是大于更新start
		//lastocc[ch]可能不存在,下标是0.参与运算可能会出错,所以用ok来判断
		if lastI, ok := lastocc[ch]; ok && lastI >= start {
			start = lastI + 1
		}

		//更新长度
		if i - start + 1 > maxlen {
			maxlen = i - start + 1
		}
		lastocc[ch] = i

	}
	return maxlen

}

func main() {
	fmt.Println(lesofnorepsubstr("vvnlkwerjnnnyw"))
	fmt.Println(lesofnorepsubstr("wwwve222"))
	fmt.Println(lesofnorepsubstr("bbbbbbb"))
	fmt.Println(lesofnorepsubstr(""))

}

1.6 字符串处理

  • rune 相当于go中的char
  • 使用range来遍历,pos和rune对,英文连续,中文加3
  • 获得字符数量使用 utf8.runecountinstring,通过解码来计数
  • 获得字节长度使用 len
  • 获得字节使用 []byte
  • string包里面有很多的操作可以对字符串进行操作。
  • fieds(可以识别空格,连续的空格也会认为是1个分割),split,join
  • contains,index
  • tolower,toupper
  • trim,trimright,trimleft
package main

import (
	"fmt"
	"unicode/utf8"
)

func main() {
	s := "我爱上海财经大学"
	fmt.Printf("%s\n", []byte(s)) //打印字符串
	for _, b := range []byte(s) { //打印出utf-8编码。每一个汉字占用3字节,加头尾4.
		fmt.Printf("%X ", b)
	}
	fmt.Println()

	for i, ch := range s { //对字符进行解码,把utf8转成unicode 然后放在rune里面。然后返回。
		fmt.Printf("(%d %X )", i, ch)
	}
	fmt.Println()

	//utf8库,可以实现很多功能。
	fmt.Println("rune count:", utf8.RuneCountInString(s))

	bytes := []byte(s) //拿到字节
	for len(bytes) > 0 {
		ch, size := utf8.DecodeRune(bytes) //拿到字符
		bytes = bytes[size:]
		fmt.Printf("%c ", ch)
	}
	fmt.Println()

	//转成rune数组或者slice,直接获取到下标和字符,很简单的。很上层。前边的就很下层。
	//使用这个。开了一个rune数组,存储了字符。然后在打印出来。
	for i, ch := range []rune(s) {
		fmt.Printf("(%d %c)", i, ch)
	}
	fmt.Println()
}

对于不支持中文的。使用rune来处理即可。
package main

import (
	"fmt"
)

func lesofnorepsubstr(s string) int {

	lastocc := make(map[rune]int)
	start := 0
	maxlen := 0

	//遍历字符串,使用rune。
	//直接range s不可以,中文会算3个字符,使用rune线转换把中文变成1个字符,这样位数就对了。
	//使用rune就不用管unicode。
	for i, ch := range []rune(s) {
		if lastI, ok := lastocc[ch]; ok && lastI >= start {
			start = lastI + 1
		}

		//更新长度
		if i-start+1 > maxlen {
			maxlen = i - start + 1
		}
		lastocc[ch] = i

	}
	return maxlen

}

func main() {
	fmt.Println(lesofnorepsubstr("vvnlkwerjnnnyw"))
	fmt.Println(lesofnorepsubstr("wwwve222"))
	fmt.Println(lesofnorepsubstr("bbbbbbb"))
	fmt.Println(lesofnorepsubstr(""))
	fmt.Println(lesofnorepsubstr("我爱上海财经大学"))
	fmt.Println(lesofnorepsubstr("一二二一三三三四"))
        

}

1.7 结构体

  • 仅支持封装,不支持继承和多态
  • 没有class,只有struct(结构体)
  • 不用考虑是分配在堆上还是在栈上
  • 使用工厂函数可以控制结构体的构造,返回的是局部变量的地址。
  • 结构体是值传递,如果想要修改使用指针即可。理解一下值传递和指针传递。只有指针才可以改变结构内容。
  • nil指针也可以调用方法。
  • 改变内容使用指针接受者,结构过大也考虑使用。如果有指针接受者,最好都是指针接受者。
  • 值/指针接受者 都可以接受值/指针
package main

import "fmt"

//定义一个treenode的结构体,分别定义值和左右指针
type treeNode struct {
	value       int
	left, right *treeNode
}

//如果要控制结构体的构造,使用工厂函数,就是普通的函数
func createTreenode(value int) *treeNode {
	return &treeNode{value: value} //局部变量给予其他使用也不会向c一样报错
}

//为结构定义方法。显示定义和命名方法接受者
//node treenode 叫接收者,类似于其他语言的this指针
//print是函数名称。
func (node treeNode) print() {
	fmt.Print(node.value)
}

//遍历,可以接受空指针。无需再判断是否为空。
func (node *treeNode) traverse(){
	//只用中序遍历,判断树是否为空树,再然后先左再中再右
	if node == nil{
		return
	}
	node.left.traverse()
	node.print()
	node.right.traverse()
}


//结构体也是值传递,如果treenNode不带指针,是无法修改值的。
func (node *treeNode) setValue(value int) {
	node.value = value
}

func main() {
	//创建结构体,无构造函数。
	//var root treeNode  //root = 0,nil,nil
	root := treeNode{value: 3}
	root.left = &treeNode{}
	root.right = &treeNode{}

	//无论是地址还是结构本身,一律使用.来访问成员
	root.right.left = new(treeNode)

	//使用工厂函数,一般返回的是局部变量的地址。
	root.left.right = createTreenode(2)

	//在slice里面定义
	nodes := []treeNode{
		{value: 3},      //3,nil,nil
		{},              //0,nil,nil
		{6, nil, &root}, //6,nil,地址
	}

	fmt.Println(nodes)
	//实际上调用的是print函数,参数有接收者是 node treenode,是值传递。
	root.print()

	fmt.Println()

	//修改值为4.不使用指针则修改不了值,因为go是值传递。
	root.right.left.setValue(4)
	root.right.left.print()


	root.traverse()

}

1.8 封装

  • 通过函数命名CameCase,首字母大写代表公共public,首字母小写代表私有private
  • 公共和私有只是对包来说的。main只是一个入口。真正的代码写在不同的包里面。
  • 每一个目录一个包。包名可以跟目录名称不一样。但是只能有一个包名
  • main包含执行入口。
  • 结构定义的方法必须放在同一包内,但是可以是不同的文件。
  • 无继承这种东西,对于已有的类型。通过定义别名/使用组合来扩展已有类型
  • 还可以使用内嵌的方式来扩展已有类型。属于语法糖咯。很简洁。
  • 定义别名最简单,但是后续使用组合,会有编译错误要修改。
  • 使用组合的方式最常用。
  • 继承,go里面没有,虽然看起来像是继承,但不是。内部是使用接口来实现继承这种东西的。

封装

目录结构:
tree
 - node.go
 - traverse.go
 - entry 
   - entry
   
   

node.go

package tree

import "fmt"

/*type Node struct 
可以省略tree。因为包名是无法省略的。所以treenode可以变为node
引用的时候就是 tree.node

*/
type Node struct {
	Value       int
	Left, Right *Node
}

func (node Node) Print() {
	fmt.Print(node.Value, " ")
}

func (node *Node) SetValue(value int) {
	if node == nil {
		fmt.Println("Setting value to nil node.Ignored")
		return
	}
	node.Value = value
}

func CreateNode(value int) *Node {
	return &Node{Value: value}
}


traverse.go
//可以将不同功能的函数独立成一个文件。只要包是一个。不会冲突。引用的时候也不会报错。
package tree

func (node *Node) Traverse() {
	if node == nil {
		return
	}

	node.Left.Traverse()
	node.Print()
	node.Right.Traverse()
}


entry.go
//一个目录只能有一个包,所以独立建立entry,存放entry.go。
//添加main函数入口
package main

import "learngo/learngo/tree"

func main() {
	root := tree.Node{Value: 3}
	root.Left = &tree.Node{}
	root.Right = &tree.Node{5, nil, nil}        
	root.Right.Left = new(tree.Node)
	root.Left.Right = tree.CreateNode(2)
	root.Right.Left.SetValue(4)

	root.Traverse()

}

  • root.Right = &tree.Node{5, nil, nil}
  • 这里在vscode会用黄色的下波浪线来标注。是一个warning。错误是composite literal uses unkeyed fields
  • 首选项-设置-打开设置json-添加如下内容保存即可。就不会再出现黄色波浪线。

image.png

{
    "gopls": {
        "analyses": { "composites": false }
    },
}

扩展

组合方式
tree
- node.go
- traverse.go
- entry 
  - entry.go

package main

import (
	"fmt"
	"learngo/learngo/tree"
)

//通过组合的方式,然后把中序遍历变为后序遍历
//m小写属于private。
type myTreeNode struct {
	//放指针,无需引用时候copy,也可以不用指针。用什么类型定义什么类型。
	node *tree.Node
}

//后序遍历
func (myNode *myTreeNode) postOrder() {
	//判断是否为空,左右子树都可能是空树
	if myNode == nil || myNode.node == nil {
		return
	}

	/*如下定义执行的时候会报错,can't mytreenode literal ,can't take the address
	.postOrder()之前的就是literal
	因为mytreenode是指针,需要一个变量来获得地址
	myTreeNode{myNode.node.Left}.postOrder()
	myTreeNode{myNode.node.Right}.postOrder()
	*/

	//访问左右树,并遍历。
	left := myTreeNode{myNode.node.Left}
	right := myTreeNode{myNode.node.Right}
	left.postOrder()
	right.postOrder()
	//访问节点本身
	myNode.node.Print()

}

func main() {
	root := tree.Node{Value: 3}
	root.Left = &tree.Node{}
	root.Right = &tree.Node{5, nil, nil}
	root.Right.Left = new(tree.Node)
	root.Left.Right = tree.CreateNode(2)
	root.Right.Left.SetValue(4)

	root.Traverse()
	fmt.Println()
	myRoot := myTreeNode{&root}
	myRoot.postOrder()
	fmt.Println()

}





定义别名方式
queue
- queue.go
- entry
  - entry.go
  
queue.go 
package queue

//通过别名的方式实现现有类型的扩展
type Queue []int

//通过*queue指针接受着。才能体现q的值变化,q所指向的slice被改变了。
func (q *Queue) Push(V int){
	*q = append(*q, V)
}

func (q *Queue) Pop() int{
	head := (*q)[0]
	*q = (*q)[1:]
	return head
}

func (q *Queue) IsEmpty() bool{
	return len(*q) ==0
}  

entry.go
package main

import (
	"fmt"
	"learngo/learngo/queue"
)

func main() {
	//创建一个原始队列,里面有1
	q := queue.Queue{1}
	
	
	//push 2和3进去。属于先进先出的队列
	//如下的pop和push的q与一开始定义的q不是一个slice。因为指针改变了值。
	q.Push(2)
	q.Push(3)
	
	fmt.Println(q.Pop())
	fmt.Println(q.Pop())
	fmt.Println(q.IsEmpty())
	fmt.Println(q.Pop())
	fmt.Println(q.IsEmpty())
}


内嵌类型方式
tree
node.go
traverse.go
- treeentry_embedded
  entry.go


/*
通过内嵌的方式来扩展已有的类型
把node省略掉,就是内嵌的做法,好处就是省略代码量看起来简洁。
把所有标红的node都删除即可。
内嵌的只是没有名字。但是myNode.后还是Node

*/

package main

import (
	"fmt"
	"learngo/learngo/tree"
)

type myTreeNode struct {
	//node *tree.Node 内嵌做法,一个语法糖
	//虽然没有名字,其实还是Node不管它之前是什么主要看.后边是什么
	*tree.Node
}

//后序遍历
func (myNode *myTreeNode) postOrder() {
	if myNode == nil || myNode.Node == nil {
		return
	}

	//myNode.之后可以选择right/left/value也可以选择各种函数方法traverse/postorder等等

	left := myTreeNode{myNode.Left}
	right := myTreeNode{myNode.Right}
	left.postOrder()
	right.postOrder()
	//访问节点本身
	myNode.Print()

}

func main() {
	//root的类型变为mytreenode,如下的代码无需修改,因为可以.出各种方法
	root := myTreeNode{&tree.Node{Value: 3}}
	root.Left = &tree.Node{}
	root.Right = &tree.Node{5, nil, nil}
	root.Right.Left = new(tree.Node)
	root.Left.Right = tree.CreateNode(2)
	root.Right.Left.SetValue(4)

	root.Traverse()
	fmt.Println()

	root.postOrder()
	fmt.Println()

}

1.9 依赖管理

gopath

  • 默认在user/go/之下,不管理依赖,默认都拉取到gopath之下。会导致gopath越来越大。
  • 默认必须建立一个src的目录。还分全局gopath和项目gopath
  • 使用go get来获取库,不会使用镜像来拉取,直接会去源地址拉取。默认拉取的是最新的版本。
  • 全局使用go env -w更改
  • 当前窗口使用export 来更改,不会再全局生效。

govendor

  • 为了解决gopath版本不一样的情况。可以每个项目有自己的vendor目录,用于存放第三方库。
  • 代码运行在0.9的版本是可以的,但是go get拉取的时候拉取的是最新的版本,存放在gopath/src下
  • 当你的项目1,使用gopath/src/1.2版本的时候会出现一些莫名其妙的错误。
  • 在项目1中,新建一个vendor这个目录,通过第三方glide和dep来拉取第三方库。
  • 在执行过程中,先寻找vendor,如果没有在去寻找src下的库。这样就解决了版本的问题。

go mod

  • 可以在镜像拉取特定的库,不写版本就是最新版本(也可以直接用于升级操作),指定版本就更新为指定版本。
  • go.mod/go.sum 存放拉取的所有库的信息和版本信息。拉取后,这两个文件的信息就会改变。
  • go mod init / go build ./... 将之前gopath/govendor 迁移到gomod上
  • go mod init 可以初始化go mod
  • go get /go mod tidy 获取更新依赖/清除依赖

2.高级部分

2.1 接口interface

  • 强类型语言有接口比如java,c++,go,弱类型语言无接口比如python,php。
  • go 语言的多态和继承由接口来完成。
  • duck typing,描述外部行为而非内部结构。go属于结构化类型系统。类似duck typing
  • go语言中,接口由使用者来定义规定必须有什么方法。实现者不需要说明实现了那个接口,只要实现接口里面的方法即可。
  • go是值传递类型,接口的值可以是真实的值也可以是指针。接口有类型且有值。一般不用指针。指针只能使用指针接受者,值传递都可以。
  • intetface{} 可以表示任何类型。
  • 接口可以是组合接口。
  • 比较常用的组合接口,stringer 类似于java中的to string。 Reader/Writer 读取和写入
  • 以后建立组合接口可以直接用Reader/Writer做一个组合接口来直接使用。
接口的定义,定义了get方法就可以使用
type Retriever interface {
    Get(url string ) string
}

func download(r Retriever) string{
    return r.Get("http://www.baidu.com")
}


组合接口
type vip interface {
    Get
    Post
}