字符串
-
基本介绍
Go语言字符串的底层数据也是对应的字节数组,但是字符串的只读属性禁止了在程序中对底层字节数组的元素进行修改。
字符串赋值只是复制了数据地址和对应的长度,而不会导致底层数据的复制。
-
底层结构
type StringHeader struct { Data uintptr //指向底层的字节数组 Len int //字符串的字节长度 }
指针
-
概念介绍
基本数据类型,变量存的是值,也叫值类型;
指针类型,变量存的是一个地址,这个地址指向的空间存的才是值;
-
操作方式
获取变量地址
&,比如:var num int,使用 &num 获取num的地址。表示方式为:var ptr *int = &num
- ptr 是一个指针变量;
- ptr 类型是 *int;
- ptr 本身的值是 &num
获取指针类型指向的值
*,比如:var ptr *int,使用 *ptr 获取ptr指向的值
-
注意事项
-
值类型都有对应的指针类型,形式为 * 数据类型,比如int的对应的指针就是 *int,float32 对应的指针类型就是 *float,依次类推;
-
值类型包括:基本数据类型 int 系列、float 系列、bool、string、数组和结构体 struct。
-
数组
-
基本介绍
数组是一个由固定长度的特定类型的元素组成的序列,一个数组可以由零个或者多个元素组成。
数组是值类型,虽然数组元素的值可以被修改,但是数组本身的赋值和函数传参,都是以整体复制的方式处理的。
数组的长度是数组类型的组成部分,因为数组的长度是数组类型的一个部分,不同长度的或者不同类型的数据组成的数组都是不同类型的数组,无法直接赋值。
-
定义方式
var a [3]int /* 如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度 */ var b = [...]int{1,2,3}
-
内存结构
Go语言中的数组是值语义,一个数组变量既表示整个数组,并不是隐式的指向第一个元素的指针。
当一个数组变量被赋值或者传递时,实际上会复制整个数组。
-
空数组
长度为0的数组在内存中不占用空间,空数组可以用于强调某种特有类型的操作时避免分配额外的内存。
c1 := make(chan [0]int) go func() { c1 <- [0]int{} }() /** chan<- 写 channel 或者叫 chan in <-chan 读 channel 或者叫 chan out**/
-
内置函数
len() //计算数组长度 cap() //计算数组容量
切片
-
基本介绍
切片的底层数据也是数组,但是每个切片还有独立的长度和容量信息,切片赋值和函数传参时也是将切片头信息部分按传值方式处理。
切片头含有底层数组的指针,所以它的赋值也不会导致底层数组的复制
-
底层结构
type SliceHeader struct { Data uintptr Len int Cap int }
-
切片定义
var ( a []int //nil切片,和nil相等,一般用来表示一个不存在的切片 b = []int{} //空切片,和nil不相等,一般用来表示一个空的集合 c = []int{1,2,3} //有三个元素的切片,len和cap都为3 d = c[:2] //有两个元素的切片 len=2 cap=3 e = c[:0] //0个元素的切片 len=0 cap=3 f = make([]int,3) //有三个元素的切片 len=3 cap=3 g = make([]int,3,6) //有三个元素的切片 len=3 cap=6 )
-
内置函数
append() // 增加切片元素,在容量不足的情况下,append操作会导致重新分配内存,可能导致巨大的内存分配和复制数据代价
-
内存分配
切片高效操作的要点是降低内存分配的次数,尽量保证append操作不会超出cap的容量,降低触发内存分配的次数和每次分配内存的大小
// 扩容切片 a = append(a,0) // 切片扩展一个空间 copy(a[i+1:], a[i:]) // a[i:] 向后移一位 a[i] = x //设置新添加的元素 // 删除切片元素 a = []int{1,2,3} a = a[:len(a)-1] a = a[1:] // 利用空切片的特性删除切片元素 func TrimSpace(s []byte) []byte { b := s[:0] // 初始化b为0个元素的切片 for _,x := range s { if x != ' ' { b = append(b, x) } } return b } //提高GC效率 func FindPhoneNum(filename string) []byte { b, _ := ioutil.ReadFile(filename) return regexp.MustCompile("[0-9]+").Find(b) } /* 这段代码返回的[]byte指向保存整个文件的数组,因为切片引用了整个原始数组,导致自动垃圾回收器不能及时释放底层数组的空间,一个小的需求可能导致需要长时间保存整个文件数据。通过下面的方式进行优化 */ func FindPhoneNum(filename string) []byte { b, _ := ioutil.ReadFile(filename) regexp.MustCompile("[0-9]+").Find(b) return append([]byte{}, b...)// b的元素被打散一个个append进新的切片 }
-
其他
切片可以和nil进行比较,只有当切片底层数据指针为空时,切片本身为nil,这时候切片的长度和容量信息将是无效的
在对切片本身赋值或参数传递时,和数组指针的操作类似,只是复制切片头信息,并不会复制底层的数据
‘…’ 其实是go的一种语法糖
用法一:主要是用于函数有多个不定参数的情况,可以接受多个不确定数量的参数。
用法二:使slice可以被打散进行传递。
函数
-
基本介绍
Go语言中,函数是第一类对象,我们可以将函数保存到变量中。
//具名函数 func Add(a, b int) int { return a+b } //匿名函数 var Add = func(a, b int) int { return a+b }
-
闭包
一般我们把匿名函数捕获了外部函数的局部变量的函数称为匿名函数。
样例代码
// 案例一 func main() { for i:=0; i < 3; i++ { // 匿名函数结构体后面为什么要定义一个() defer func(){fmt.Println(i)}() } } //3 //3 //3 //因为闭包,每个defer语句延迟执行的函数引用的都是同一个i迭代变量,在循环结束后,i都为3,可以通过以下方法解决该问题 func main() { for i:=0; i<3; i++ { defer func(i){fmt.Println(i)}(i) } } // 案例二 func main() { res :=sum(20,10) fmt.Println("res=",res) } func sum(n1 int,n2 int) int{ //当执行到defer时,暂时不执行,会将defer后面的语句压入到独立的(defer栈) //当函数执行完毕后,再从defer栈 按照先入后出的方式出栈执行 //defer 将语句放入到栈时,也会将相关的值拷贝同时入栈*** defer fmt.Println("ok1 n1=",n1)//defer3 ok3 n1 20 defer fmt.Println("ok2 n2=",n2) //defer2 ok2 n2 10 n1 ++ n2 ++ res := n1 + n2 fmt.Println("ok3 res=",res)//1.ok1 return res } // 输出结果: ok3 res= 32 ok2 n2= 10 ok1 n1= 20 res= 32
闭包形式
闭包形式 2:立即执行函数,声明完以后加括号,用以表示即刻调用。
func() { // to do something }()
-
切片作为入参
Go语言中,如果以切片作为参数调用函数时,有时候会给人一种参数采用了引用方式传递的假象:因为在被调用函数内部可以修改传入的切片元素
任何可以通过函数参数修改调用参数的情形,都是因为函数参数中显示或者隐式的传入了指针参数。
因为切片中的底层数组部分是通过隐式指针传递(指针本身依然是传值的,但是指针指向的却是同一份数据),所以被调用函数是可以通过指针修改调用参数切片中的数据。
为什么内置的append必须要返回一个切片的原因?
除了数据之外,切片结构还包含了切片长度和切片容量的信息,这两个信息也是传值的,如果被调用函数中修改了Len或Cap信息的话,就无法反映到调用参数的切片中,这时候我们一般会通过返回修改后的切片来更新之前的切片,这也是
func twice(x []int) { for i := range x { x[i] *= 2 } } type IntSliceHeader struct { Data []int Len int Cap int } func twice(x IntSliceHeader) { for i:=0; i < x.Len; i++ { x.Data[i] *= 2 } }
-
递归
Go语言函数的递归调用深度逻辑上没有限制,函数调用的栈是不会出现溢出错误的,因为Go语言运行时会根据需要动态的调整函数栈的大小。每个goroutine刚启动时只会分配很小的栈,根据需要动态调整栈的大小。
-
其他
关键字defer
在Go语言中,可以使用关键字defer向函数注册退出调用,即主函数退出时,defer后的函数才被调用。defer语句的作用是不管程序是否出现异常,均在函数退出时自动执行相关代码。
make
-
参数介绍
make函数,该函数第一个参数是类型,第二个参数是分配的空间,第三个参数是预留分配空间,例如a:=make([]int, 5, 10), len(a)输出结果是5,cap(a)输出结果是10。
-
注意事项
然后我对a[4]进行赋值发现是可以,但对a[5]进行赋值发现报错了,原来预留的空间需要重新切片才可以使用。
package main import "fmt" func main(){ a := make([]int, 10, 20) fmt.Printf("%d, %d\n", len(a), cap(a)) fmt.Println(a) b := a[:cap(a)] fmt.Println(b) }
方法
-
基本介绍
Go语言中方法是关联到类型的,这样可以在编译阶段完成方法的静态绑定。
-
组合
type Point struct{X, Y float64} type ColorPoint struct { Point Color color.RGBA }
接口
-
基本介绍
Go的接口类型是对其他类型行为的抽象和概括,Go语言的接口类型独特之处在于它是满足隐式实现的鸭子类型。
这种设计可以让你创建一个新的接口类型满足已经存在的的具体类型却不用去破坏这些类型原有的定义,当我们使用的类型来自于不受我们控制的包时,这种设计尤其灵活有用。
-
样例代码
// 定义方法 func Fprintf(w io.Writer, format string, args ...interface{}) (int, error) //用于输出的接口 type io.Writer interface { Write(p []byte) (n int,err error) } //内置的错误接口 type error interface { Error() string } //下面的代码我们可以通过定制自己的输出对象,将每个字符转为大写字符后输出 type UpperWriter struct { io.Writer } // 为什么func后面直接跟()? func (p *UpperWriter) Write(data []byte) (n int, err error) { return p.Writer.Write(bytes.ToUpper(data)) } func main() { fmt.Fprintln(&UpperWriter{os.Stdoout}, "hello, world") }
-
虚拟继承
type animal interface { func hello() func run() } type xxx struct { animal //加入了接口,但是没有实现具体的方法,实现了虚拟继承 }
-
注意事项
Go语言中,对于基础类型(非接口类型)不支持隐式转换,我们无法将一个int类型的值直接赋给int64类型的变量,也无法将int类型的值赋值给底层是int类型的新定义命名类型的变量。
参考链接
-
Go语言核心编程读书笔记
https://mp.weixin.qq.com/s/zrvXUHcKQ_4ne9o6PL2eVg
-
Golang中make的使用
https://www.cnblogs.com/terrycc/p/13986422.html
-
golang make()内置函数
https://blog.csdn.net/happinessaflower/category_2918813.html
-
Go语言中type的用法
https://www.cnblogs.com/-wenli/p/12304379.html
-
golang中的三个点 ‘…’ 的用法
https://blog.csdn.net/jeffrey11223/article/details/79166724
-
Golang标准库API文档
https://studygolang.com/pkgdoc
-
Go语言中的闭包
https://www.cnblogs.com/hzhuxin/p/9199332.html
-
go里面的函数, func后面直接的括号是啥意思
https://www.golangtc.com/t/5406ea37320b527a3b0000c4