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)
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-添加如下内容保存即可。就不会再出现黄色波浪线。
{
"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
}