初识GO语言

原创内容,转载请注明出处

Posted by Weakyon Blog on January 9, 2016

记录下踩到的坑

一 接口

  
package main                                                                       
                                                                                   
import (                                                                           
    "fmt"                                                                          
    "bytes"                                                                        
)                                                                                  
                                                                                   
type Stringer interface{                                                           
    String() string                                                                
}                                                                                  
                                                                                   
type Point struct{X,Y float64}                                                     
                                                                                   
func (p *Point) String() string{                                                   
    var buf bytes.Buffer                                                           
    fmt.Fprintf(&buf, "%f-%f",p.X,p.Y)                                             
    return buf.String()                                                            
}                                                                                  
                                                                                   
func main(){                                                                       
    r1 := Point{1,2}                                                               
    var r2 Stringer                                                                
    r2 = &r1                                                                       
    fmt.Println(&r1)            //1.000000-2.000000                                
    fmt.Println(r1)             //{1 2}
    fmt.Println(r1.String())    //1.000000-2.000000                                          
    fmt.Println(r2)             //1.000000-2.000000                                
}

第一个语句是调用了带指针的String()结果

第二个Print语句输出r1,是Point结构体的,因为没有声明带指针的String方法,因此fmt输出了默认String方法,也就是{1,2}

第三个语句r1.String()为何能正确执行呢,这是因为编译器自动加了&r1.String()

第四个语句r2是接口类型,赋值r1以后检查类型Stringer实现的接口都被Point实现了,因此可以赋值,并且在调用时调用了r2带指针的String方法,而不是默认的那个

这里要注意的是,r2 = &r1是必须的,因为Point没有声明不带指针的String方法,如果要调用带指针的方法,必须对这个结构体取地址

二 切片

  
package main                                                                       
                                                                                   
import "fmt"                                                                       
                                                                                   
func main() {                                                                      
    array1 := [3]int{1,2}                                                          
    array2 := [5]int{}                                                             
    array3 := [...]int{}                                                           
    array4 := []int{1,2,3,4}                                                       
    array5 := []int{}                                                              
    fmt.Println(array1)     //[1 2 0]                                                            
    fmt.Println(array2)     //[0 0 0 0 0]                                              
    fmt.Println(array3)     //[]                                          
    fmt.Println(array4)     //[1 2 3 4]                                                   
    fmt.Println(array5)     //[]                                            
}

切片和数组的声明很类似,困扰了我好久

array1,2,3都是数组类型,4,5是切片类型

在类型的[]里,有数字或者…的都是数组,如果什么都没有则是切片

在我的理解看来,切片是对数组的封装,保存了一个数组的指针

1 切片赋值为数组或者切片并且append小于原来的尺寸

//demo1
array1 := [3]int{1,2}                                                          
array2 := []int{}                                                              
array2 = array1[0:1]                                                           
array2 = append(array2, 3, 4)                                            

//demo2
array3 := []int{1,2}                                                          
array4 := []int{}                                                              
array4 = array3[0:1]                                                           
array4 = append(array4, 3)                                            

fmt.Println(array1)         //[1 3 4]
fmt.Println(array2)         //[1 3 4]
fmt.Println(array3)         //[1 3]
fmt.Println(array4)         //[1 3]

对于demo1来说数组能够容纳下这一块数据,因此array2依然指向array1,array1被改为[1 3 4]

对于demo2来说array3底层的数组能够容纳下这一块数据,因此array4依然指向array3底层的数组,array3被改为[1,3]

2 切片赋值为数组或者切片并且append大于原来的尺寸

//demo1
array1 := [3]int{1,2}                                                          
array2 := []int{}                                                              
array2 = array1[0:1]                                                           
array2 = append(array2, 3, 4, 5, 6)                                            

//demo2
array3 := []int{1,2}                                                          
array4 := []int{}                                                              
array4 = array3[0:1]                                                           
array4 = append(array4, 3, 4)                                            

fmt.Println(array1)         //[1 2 0]
fmt.Println(array2)         //[1 3 4 5 6]
fmt.Println(array3)         //[1 2]
fmt.Println(array4)         //[1 3 4]

对于demo1来说由于数组是不可变长度的,因此使用append扩容以后,array2指向了一个匿名的数组[1 3 4 5 6],此时array1并没有被影响

对于demo2来说由于array3底层的数组不可变长度的,因此使用append扩容以后,array4指向了一个匿名的数组[1 3 4],此时array3并没有被影响

这样的坑让我意识到,对赋值得到的切片最好慎用append,因为你一不小心就把原来的给改了

三 协程

var wg sync.WaitGroup                                                          
indexs := []int{1,2,3,4,5,6}                                                   
for _, i := range indexs {                                                     
    wg.Add(1)                                                                  
    go func(i int){                                                            
        defer wg.Done()                                                        
        fmt.Println(i)                                                         
    }(i)                                                                       
}                                                                              
wg.Wait()

这里会乱序输出一段数字,但是第一个数字一般都是6,因为在协程实际调度的时候,for循环应该已经完成了

如果这里把协程的匿名函数改为无参数的,会输出6个6来。这个坑相当低级,我却踩到了。。

四 map

type cardInfo struct {                                                             
    CardType    int                                                                
    Id          int                                                                
}                                                                                   
                                                                                   
func (c *cardInfo) GetId() int{                                                    
    return c.Id                                                                    
}                                                                                  
                                                                                    
type personInfo struct {                                                           
    Name string                                                                    
    Card map[string]cardInfo                                                       
}                                                                                  
                                                                                   
func main(){                                                                       
    p := &personInfo{                                                              
        Name:"Tom",                                                                
    }                                                                               
    p.Card = make(map[string]cardInfo)                                              
    p.Card["1"] = cardInfo {                                                       
        CardType:100,                                                              
        Id:10086,                                                                  
    }                                                                              
    p.Card["1"].GetId()                                                            
}

这个编译会报错

cannot call pointer method on p.Card["1"]
cannot take the address of p.Card["1"]

map的value值是不能取地址的,因为map会因为扩容等使得value值的地址改变,也就不支持对地址取值了,因此无法调用结构体指针的方法

而结构体字面值的方法用的很少,因为不能修改调用方的值,大都是结构体指针的方法

所以一般来说map存储的value如果是结构体的话,想要调用他的方法,那只能存指针

五 json序列化

type cardInfo struct {                                                             
    CardType    int                                                                
    Id          int                                                                
}                                                                                  
                                                                                   
type personInfo struct {                                                           
    Name string                                                                    
    Card map[string]*cardInfo                                                      
}                                                                                  
                                                                                   
func main() {                                                                      
    p := &personInfo{                                                              
        Name:"Tom",                                                                
    }                                                                              
    p.Card = make(map[string]*cardInfo)                                            
    p.Card["1"] = &cardInfo {                                                      
        CardType:100,                                                              
        Id:10086,                                                                  
    }                                                                              
    data,err := json.Marshal(p)                                                    
    fmt.Println(p)                                                                 
    fmt.Println(string(data))                                                      
    if (err != nil) {                                                              
        fmt.Println("Marshal",err)                                                 
    }                                                                              
    var OtherP personInfo                                                          
    if err := json.Unmarshal(data,&OtherP); err != nil {                           
        fmt.Println("Unmarshal",err)                                               
    }                                                                              
    fmt.Println(OtherP)                                                            
}

输出

&{Tom map[1:0xc820072260]}
{"Name":"Tom","Card":{"1":{"CardType":100,"Id":10086}}}
{Tom map[1:0xc820072510]}

即使是指针类型的cardInfo,一样可以序列化和反序列化,反序列化后该指针将指向另一块地址

这里有三个小坑:

1 如果被序列化的成员名字首字母小写,将不会被序列化

2 如果要修改成员的输出结果,例如Name成员输出name,在后面加上json:"name"即可

3 如果这里Card成员是map[int]*cardInfo,将不能被序列化,这是json的要求,因为json的key不能为数字只能为字符串

六 数组转化为切片

x := [2]int{1,2}                                                               
y := []int{}                                                                   
y = x

报错

cannot use x (type [2]int) as type []int in assignment

数组是不能直接转换为切片的,要这么写才行

y = x[:]

参考: why-can-not-convert-sizebyte-to-string-in-go

如果byte数组要转换为string,必须写成string(byte[:]),也就是先转换成byte切片才能转换为string

09 Jan 2016