golang学习笔记
读《go学习笔记第四版》 《学习go语言》 《gopl-zh》《Go语言实战》记录
多变量赋值时,先计算所有相关值,然后再从左到右依次赋值。
data, i := [3]int{1, 2, 3}, 0 i, data[i] = 2, 6 fmt.Println(i) //2 fmt.Println(data) //[6 2 3]
用{}区分代码块
常量值必须是编译期可确定的数字、字符串、布尔值。 未使用局部常量不会引发编译错误。
const ( _ = iota // iota = 0 KB int64 = 1 << (10 * iota) // iota = 1 MB // 与 KB 表达式相同,但 iota = 2 GB TB )
const ( a = iota b = iota c = iota )
可以简写
const ( a = iota b c )
const ( a = iota //0 b //1 c //2 d = "ha" //独立值,iota += 1 e //"ha" iota += 1 f = 100 //iota +=1 g //100 iota +=1 h = iota //7,恢复计数 i //8 ) fmt.Println(a,b,c,d,e,f,g,h,i) // 0 1 2 ha ha 100 100 7 8
const ( i=1<<iota j=3<<iota k l ) func main() { fmt.Println("i=",i) fmt.Println("j=",j) fmt.Println("k=",k) fmt.Println("l=",l) } //output: i= 1 j= 6 k= 12 l= 24
var s []int // len(s) == 0, s == nil s = nil // len(s) == 0, s == nil s = []int(nil) // len(s) == 0, s == nil s = []int{} // len(s) == 0, s != nil
map中的元素并不是一个变量,不能对map的元素进行取址操作
range 会复制对象
a := [3]int{0, 1, 2} for i, v := range a { // index、value 都是从复制品中取出。 if i == 0 { // 在修改前,我们先修改原数组。 a[1], a[2] = 999, 999 fmt.Println(a) // 确认修改有效,输出 [0, 999, 999]。 } a[i] = v + 100 // 使⽤用复制品中取出的 value 修改原数组。 } fmt.Println(a) // 输出 [100, 101, 102]。
s := []int{1, 2, 3, 4, 5} for i := range s { // 复制 struct slice { pointer, len, cap }。 if i == 0 { s = s[:3] // 对 slice 的修改,不会影响 range。 s[2] = 100 // 对底层数据的修改。 } } fmt.Println(s) //[1 2 100 4 5]
//range也可以用来枚举Unicode字符串。第一个参数是字符的索引,第二个是字符(Unicode的值)本身。 for i, c := range "go" { fmt.Println(i, c) }
var employeeOfTheMonth *Employee = &dilbert employeeOfTheMonth.Position += " (proactive team player)"
相当于
(*employeeOfTheMonth).Position += " (proactive team player)"
var c = [...]int {1, 2, 3, 4, 5} //由初始化列表决定数组长度,不可省去标识符 "...",否则将变成切片Slice
x := []int{1, 2, 3} i := 2 switch i { case x[1]: println("a") case 1, 3: //同时匹配1,3 println("b") default: println("c") }
switch i := x[2]; { // 带初始化语句 case i > 0: println("a") case i < 0: println("b") default: println("c") }
MAP
/* map插入key - value对,各个国家对应的首都 */ countryCapitalMap [ "France" ] = "Paris" countryCapitalMap [ "Italy" ] = "罗马" countryCapitalMap [ "Japan" ] = "东京" countryCapitalMap [ "India " ] = "新德里" /*使用键输出地图值 */ for country := range countryCapitalMap { fmt.Println(country, "首都是", countryCapitalMap [country]) } /*查看元素在集合中是否存在 */ captial, ok := countryCapitalMap [ "美国" ] /*如果确定是真实的,则存在,否则不存在 */ /*fmt.Println(captial) */ /*fmt.Println(ok) */ if (ok) { fmt.Println("美国的首都是", captial) } else { fmt.Println("美国的首都不存在") }
break 可用于 for、switch、select,而 continue 仅能用于 for 循环。
type User struct { id int name string } func (self *User) Test() { fmt.Printf("%p, %v ", self, self) } func main() { u := User{1, "Tom"} u.Test() mValue := u.Test mValue() // 隐式传递 receiver mExpression := (*User).Test mExpression(&u) // 显式传递 receiver }
/*
0x210230000, &{1 Tom}
0x210230000, &{1 Tom}
0x210230000, &{1 Tom}
*/
type User struct { id int name string } func (self User) Test() { fmt.Println(self) } func main() { u := User{1, "Tom"} mValue := u.Test // ⽴立即复制 receiver,因为不是指针类型,不受后续修改影响。 u.id, u.name = 2, "Jack" u.Test() mValue() } /* {2 Jack} {1 Tom} */
func (self *User) TestPointer() { fmt.Printf("TestPointer: %p, %v ", self, self) } func (self User) TestValue() { fmt.Printf("TestValue: %p, %v ", &self, self) } func main() { u := User{1, "Tom"} fmt.Printf("User: %p, %v ", &u, u) mv := User.TestValue mv(u) mp := (*User).TestPointer mp(&u) mp2 := (*User).TestValue // *User ⽅方法集包含 TestValue。 mp2(&u) // 签名变为 func TestValue(self *User)。 } // 实际依然是 receiver value copy /* User : 0x210231000, {1 Tom} TestValue : 0x210231060, {1 Tom} TestPointer: 0x210231000, &{1 Tom} TestValue : 0x2102310c0, {1 Tom} */
type Data struct{} func (Data) TestValue() {} func (*Data) TestPointer() {} func main() { var p *Data = nil p.TestPointer() (*Data)(nil).TestPointer() // method value (*Data).TestPointer(nil) // method expression //p.TestValue() // invalid memory address or nil pointer dereference // (Data)(nil).TestValue() // cannot convert nil to type Data // Data.TestValue(nil) // cannot use nil as type Data in function argument }
简短变量声明左边的变量可能并不是全部都是刚刚声明的。如 果有一些已经在相同的词法域声明过了,那么简短变量声明语句对这些已经声明过 的变量就只有赋值行为了,简短变量声明语句中必须至少要声明一个新的变量。如果变量 是在外部词法域声明的,那么简短变量声明语句将会在当前词法域重新声明一个新的变量
//交换两个元素 x, y = y, x a[i], a[j] = a[j], a[i]
//获得整数对应二进制数1的个数 var pc [256]byte func init() { for i := range pc { pc[i] = pc[i/2] + byte(i&1) } fmt.Printf("%b", pc) } func PopCount(x uint64) int { return int(pc[byte(x>>(0*8))] + pc[byte(x>>(1*8))] + pc[byte(x>>(2*8))] + pc[byte(x>>(3*8))] + pc[byte(x>>(4*8))] + pc[byte(x>>(5*8))] + pc[byte(x>>(6*8))] + pc[byte(x>>(7*8))]) }
表达式 x&(x-1) 用于将x的最低的一个非零的bit位清零。可已用来计算二进制数1的个数
作用域
if x := f(); x == 0 { fmt.Println(x) } else if y := g(x); x == y { fmt.Println(x, y) } else { fmt.Println(x, y) } fmt.Println(x, y) // compile error: x and y are not visible here
var cwd string func init() { cwd, err := os.Getwd() // compile error: unused: cwd if err != nil { log.Fatalf("os.Getwd failed: %v", err) } }
虽然cwd在外部已经声明过,但是 := 语句还是将cwd和err重新声明为新的局部变量。因为内 部声明的cwd将屏蔽外部的声明,因此上面的代码并不会正确更新包级声明的cwd变量。
最直接的方法是通过单独声明err变量,来避免使 用 := 的简短声明方式:
var cwd string func init() { var err error cwd, err = os.Getwd() if err != nil { log.Fatalf("os.Getwd failed: %v", err) } }
运算符号:
在Go语言中,%取模运算 符的符号和被取模数的符号总是一致的,因此 -5%3 和 -5%-3 结果都是-2。除法运算符 / 的 行为则依赖于操作数是否为全为整数,比如 5.0/4.0 的结果是1.25,但是5/4的结果是1,因为 整数除法会向着0方向截断余数。
位运算:
^ 位运算 XOR //只有一个1时返回1
&^ 位清空 (AND NOT)
位操作运算符 ^ 作为二元运算符时是按位异或(XOR),当用作一元运算符时表示按位取反
结构
点操作符也可以和指向结构体的指针一起工作:
var employeeOfTheMonth *Employee = &dilbert employeeOfTheMonth.Position += " (proactive team player)"
相当于
(*employeeOfTheMonth).Position += " (proactive team player)"
dilbert.Position = "hello" 相当于 (&dilbert).Position = "hello2"
pp := &Point{1, 2} 相当于 pp := new(Point) *pp = Point{1, 2}
w = Wheel{Circle{Point{8, 8}, 5}, 20} 等价于 w = Wheel{ Circle: Circle{ Point: Point{X: 8, Y: 8}, Radius: 5, }, Spokes: 20, //NOTE: trailing comma necessary here (and at Radius) }
// squares返回一个匿名函数。 // 该匿名函数每次被调用时都会返回下一个数的平方。 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" }
结构
var ( mu sync.Mutex // guards mapping mapping = make(map[string]string) ) func Lookup(key string) string { mu.Lock() v := mapping[key] mu.Unlock() return v }
下面这个版本在功能上是一致的,但将两个包级别的变量放在了cache这个struct一组内:
var cache = struct { sync.Mutex mapping map[string]string }{ mapping: make(map[string]string), } func Lookup(key string) string { cache.Lock() v := cache.mapping[key] cache.Unlock() return v }
type Point struct{ X, Y float64 } func (p Point) Add(q Point) Point { return Point{p.X + q.X, p.Y + q.Y} } func (p Point) Sub(q Point) Point { return Point{p.X - q.X, p.Y - q.Y} } type Path []Point func (path Path) TranslateBy(offset Point, add bool) { var op func(p, q Point) Point if add { op = Point.Add } else { op = Point.Sub } for i := range path { // Call either path[i].Add(offset) or path[i].Sub(offset). path[i] = op(path[i], offset) } }
deferred
func ma() { fmt.Println("im ma") } func main() { defer ma() fmt.Println("start") } /* start im ma */
func ma() func() { fmt.Println("im ma") return func() { fmt.Print("ma func end") } } func main() { defer ma()() fmt.Println("start") } /* im ma start ma func end */
接口
type IntSet struct { /* ... */ } func (*IntSet) String() string var _ = IntSet{}.String() // compile error: String requires *IntSet receiver
但是我们可以在一个IntSet值上调用这个方法: var s IntSet var _ = s.String() // OK: s is a variable and &s has a String method
然而,由于只有*IntSet类型有String方法,所以也只有*IntSet类型实现了fmt.Stringer接口: var _ fmt.Stringer = &s // OK var _ fmt.Stringer = s // compile error: IntSet lacks String method
array := [5]*int{0: new(int), 1: new(int)}
*array[0] = 10
*array[1] = 20
创建一个包含 100 万个 int 类型元素的数组
var array [1e6]int
nil slice 和 empty slice 区别 https://blog.csdn.net/bobodem/article/details/80658466
nil slice
var slice []int
empty slice
slice := make([]int,0)//或者
slice := []int{}
根据内存和性能来看,在函数间传递数组是一个开销很大的操作。在函数之间传递变量时,
总是以值的方式传递的。如果这个变量是一个数组,意味着整个数组,不管有多长,都会完整复
制,并传递给函数。
在函数间传递切片就是要在函数间以值的方式传递切片。由于切片的尺寸很小,在函数间复
制和传递切片成本也很低。由于与切片关联的数据包含在底层数组里,不属于切片本身,所以将切片
复制到任意函数的时候,对底层数组大小都不会有影响。复制时只会复制切片本身,不会涉及底
层数组。
在函数间传递映射并不会制造出该映射的一个副本。实际上,当传递映射给一个函数,并对
这个映射做了修改时,所有对这个映射的引用都会察觉到这个修改。这个特性和切片类似,保证可以用很小的成本来复制映射。
func main() { slice := []int{10, 20, 30, 40, 50} newSliceA := slice[1:3:3] newSliceB := slice[1:3] newSliceA = append(newSliceA, 22) fmt.Println(slice) fmt.Println(newSliceA) newSliceB = append(newSliceB, 22) fmt.Println(slice) fmt.Println(newSliceA) } /* [10 20 30 40 50] [20 30 22] [10 20 30 22 50] [20 30 22] */
func main() { slice := make([]int ,1E6) fmt.Println(len(slice)) fmt.Println(cap(slice)) slice = append(slice, 99) fmt.Println(len(slice)) fmt.Println(cap(slice)) /** 1000000 1000000 1000001 1250304 */ }
Go 语言里的引用类型有如下几个:切片、映射、通道、接口和函数类型
break
//利用 break 可以提前退出循环,break 终止当前的循环。 fo r i := 0 ; i < 10 ; i++ { i f i > 5 { break ← 终止这个循环,只打印 0 到 5 } println(i) } 循环嵌套循环时,可以在 break 后指定标签。用标签决定哪个循环被终止: J: for j := 0 ; j < 5 ; j++ { for i := 0 ; i < 10 ; i++ { if i > 5 { break J ← 现在终止的是 j 循环,而不是 i 的那个 } println(i) } }
Switch
//Go 的 switch 非常灵活。表达式不必是常量或整数,执行的过程从上至//下,直到找到匹 //配项,而如果 switch 没有表达式,它会匹配 true 。这产生一种可能——//使用 switch //编写 if-else-if-else 判断序列。 func unhex(c byte) byte { switch { case '0' <= c && c <= '9': return c - '0' case 'a' <= c && c <= 'f': return c - 'a' + 10 case 'A' <= c && c <= 'F': return c - 'A' + 10 } return 0
} //它不会匹配失败后自动向下尝试,但是可以使用 fallthrough 使其这样做。没有 fallthrough: switch i { case 0: // 空的 case 体 case 1: f() // 当 i == 0 时,f 不会被调用! } 而这样: switch i { case 0: fallthrough case 1: f() // 当 i == 0 时,f 会被调用! } //用 default 可以指定当其他所有分支都不匹配的时候的行为。 switch i { case 0: case 1: f() defaul t: g() // 当 i 不等于 0 或 1 时调用 } //分支可以使用逗号分隔的列表。 func shouldEscape(c byte) bool { switch c { case ' ', '?', '&', '=', '#', '+': ← , as ”or” return true } return false }
Slice
s1 := []int{1, 2, 3} s2 := s1 s1[1] = 4 fmt.Println(s1, s2) //[1 4 3] [1 4 3] d := func (s1 []int) { s1[2] = 5 } d(s1) fmt.Println(s1, s2) //[1 4 5] [1 4 5]
闭包
func plusX(x int) func(int) int { .0 return func(y int) int { retu rn x + y } .1 } .0 再次定义一个函数返回一个函数; .1 在函数符号中使用局部变量 x。
defer
//在这个(匿名)函数中,可以访问任何命名返回参数: //Listing 2.10. 在 defer 中访问返回值 func f() (ret i n t) { ← ret 初始化为零 defer func() { ret++ ← ret 增加为 1 }() return 0 ← 返回的是 1 而不是 0! }