golang高级学习笔记

1. 在以字符串作为参数传递给fmt.Println函数时,字符串的内容并没有被复制

——传递的仅仅是字符串的地址和⻓度(字符串的结构在 reflect.StringHeader  中定义)。在Go语⾔

中,函数参数都是以复制的⽅式(不⽀持以引⽤的⽅式)传递(⽐较特殊的是,Go语⾔闭包函数对外部

变量是以引⽤的⽅式使⽤)

2.字符

字符串的元素不可修改,是⼀个只读的字节数组,

example:

func main() {
//查询Unicode字符串长度用utf8.RuneCountInString()函数,ASCII字符用len()
str1:="君のことが大好きです、"
str2:="这是中文"
n1:=utf8.RuneCountInString(str1)
n2:=utf8.RuneCountInString(str2)
fmt.Println(n1,n2)
//bianli_ASCII()//乱码了,
bianli_Unicode()
  
  
  for i, c := range []byte("世界abc") {
   fmt.Println(i, c)//仅能成功遍历ascii码
  }
}
func bianli_ASCII(){
theme:="狙击 start"
for i:=0;i<len(theme);i++{
fmt.Printf("ASCII: %c %d\n",theme[i],theme[i])
}
}
func bianli_Unicode(){
theme:="狙击 start"
for _,s:=range theme{
fmt.Printf("Unicode: %c %d\n",s,s)
}
}
3.切片

⽤ copy 和 append 组合可以避免创建中间的临时切⽚,同样是完成添加元素的操作:
a = append(a, 0) // 切⽚扩展1个空间
copy(a[i+1:], a[i:]) // a[i:]向后移动1个位置
a[i] = x // 设置新添加的元素
第⼀句 append ⽤于扩展切⽚的⻓度,为要插⼊的元素留出空间。第⼆句 copy 操作将要插⼊位置开始
之后的元素向后挪动⼀个位置。第三句真实地将新添加的元素赋值到对应的位置。操作语句虽然冗⻓
了⼀点,但是相⽐前⾯的⽅法,可以减少中间创建的临时切⽚。

3.1

删除切⽚元素
根据要删除元素的位置有三种情况:从开头位置删除,从中间位置删除,从尾部删除。其中删除切⽚
尾部的元素最快:
a = []int{1, 2, 3}
a = a[:len(a)-1] // 删除尾部1个元素
a = a[:len(a)-N] // 删除尾部N个元素

3.1.1

删除开头的元素也可以不移动数据指针,但是将后⾯的数据向开头移动。可以⽤ append 原地完成(所
谓原地完成是指在原有的切⽚数据对应的内存区间内完成,不会导致内存空间结构的变化):
a = []int{1, 2, 3}
a = append(a[:0], a[1:]...) // 删除开头1个元素
a = append(a[:0], a[N:]...) // 删除开头N个元素
也可以⽤ copy 完成删除开头的元素:
a = []int{1, 2, 3}
a = a[:copy(a, a[1:])] // 删除开头1个元素
a = a[:copy(a, a[N:])] // 删除开头N个元素

3.2

在判断⼀个切⽚是否为空时,⼀般
通过 len 获取切⽚的⻓度来判断,⼀般很少将切⽚和 nil 值做直接的⽐较。

TrimSpace 函数⽤于删除 []byte 中的空格。函数实现利⽤了0⻓切⽚的特性,实现⾼效⽽
且简洁。
func TrimSpace(s []byte) []byte {
b := s[:0]
for _, x := range s {
if x != ' ' {
b = append(b, x)
}
}
return b
}


类似的问题,在删除切⽚元素时可能会遇到。假设切⽚⾥存放的是指针对象,那么下⾯删除末尾的元
素后,被删除的元素依然被切⽚底层数组引⽤,从⽽导致不能及时被⾃动垃圾回收器回收(这要依赖
回收器的实现⽅式):
var a []*int{ ... }
a = a[:len(a)-1] // 被删除的最后⼀个元素依然被引⽤, 可能导致GC操作被阻碍

将需要⾃动内存回收的元素设置为 nil ,保证⾃动回收器可以发现需要回收的对象,
然后再进⾏切⽚的删除操作:
var a []*int{ ... }
a[len(a)-1] = nil // GC回收最后⼀个元素内存
a = a[:len(a)-1] // 从切⽚删除最后⼀个元素
当然,如果切⽚存在的周期很短的话,可以不⽤刻意处理这个问题。因为如果切⽚本身已经可以被GC
回收的话,切⽚对应的每个元素⾃然也就是可以被回收的了。

通过两种⽅法将 []float64  类型的切⽚转换为 []int  类型的切⽚:

三个索引的切片

  1. 第三个索引可以限定容量。对于slice[i:j:k],长度=j-i,容量=k-i

  2. 在创建切片时设置切片的容量和长度一样,可以强制让新切片的第一个append操作创建新的底层数组,与原有的底层数组分类。保持数组的简洁,更加的安全。

    • a. 若不限定分片的容量,直接append的话可能会覆盖底层数组,从而影响到其他切片,出现奇怪的bug
    func main(){
     b := []int{1, 2, 3, 4, 5, 6}
     c := b[: 2]
     c = append(c, 7)
     fmt.Println(b)
     fmt.Println(c)
    }
    
    ----------------------
    //b切片被c影响
    [1 2 7 4 5 6]
    [1 2 7]
    
    • b. 在使用切片时限定容量可以避免上述情况
    func main(){
     b := []int{1, 2, 3, 4, 5, 6}
     c := b[: 2:2]
     c = append(c, 7)
     fmt.Println(b)
     fmt.Println(c)
    }
    
    ------------------
    //在使用切片时限定容量,c切片append时开辟了新的数组,不影响原数组上的切片
    [1 2 3 4 5 6]
    [1 2 7]
    


4.函数、方法、接口

当可变参数是⼀个空接⼝类型时,调⽤者是否解包可变参数会导致不同的结果:
func main() {
var a = []interface{}{123, "abc"}
Print(a...) // 123 abc
Print(a) // [123 abc]
}
func Print(a ...interface{}) {
fmt.Println(a...)
}
第⼀个 Print 调⽤时传⼊的参数是 a... ,等价于直接调⽤ Print(123, "abc") 。第⼆个 Print 调⽤
传⼊的是未解包的 a ,等价于直接调⽤ Print([]interface{}{123, "abc"}) 。

如果返回值命名了,可以通过名字来修改返回值,也可以通过 defer 语句在 return 语句之后修改返
回值:
func Inc() (v int) {
defer func(){ v++ } ()
return 42
}
其中 defer 语句延迟执⾏了⼀个匿名函数,因为这个匿名函数捕获了外部函数的局部变量 v ,这种函
数我们⼀般叫闭包。闭包对捕获的外部变量并不是传值⽅式访问,⽽是以引⽤的⽅式访问。

函数的递归调用
//斐波那契数列实现,炫啊
/*1 2 3 4 5 6 7 8 9 10 11 12
1 1 2 3 5 8 13 21 34 55 89 144
*/
func fibonacci(n int)int{
if n==1||n==2{
return 1
}
return fibonacci(n-2)+fibonacci(n-1)
}
f:=fibonacci(a)
fmt.Printf("斐波那契数列第%d项是%d",a,f)

//求1+2+3+...n项和
func getsum(n int)int {
if n==1{
return 1
}
return getsum(n-1)+n
}
将⽅法还原为普通类型的函数:
var ReadFile = (*File).Read
ReadFile(f, 0, data)


老婆董香镇楼。

posted @ 2020-02-09 13:21  前方路wx  阅读(233)  评论(0编辑  收藏  举报