在看了文章Go 語言的優點,缺點和令人厭惡的設計之後,受益匪淺,覺得需要總結聯繫一下強化一下記憶。
struct操作
- 複製
struct可以通過賦值語句來複制,如下:
雖然y是複製的x,但是修改y內array內容時候,會把x的也改變了。
import "fmt"
func main() {
x := TestFo{Name: "foo1", Childs: []string{"foo1-2"}}
y := x
y.Name = "boo1"
y.Childs[0] = "boo1-2"
fmt.Printf("x is %v\n", x) //x is {foo1 [boo1-2]}
fmt.Printf("y is %v\n", y) //y is {boo1 [boo1-2]}
}
type TestFo struct {
Name string
Childs []string
}
這個例子就很清晰的描述了爲什麼會發上上面的情況,slice,map,array都是指針,複製struct時候,如果是指針只會複製指針,不會複製指針指向的值,可以看到,打印出來的兩個指針是相同的,所以複製後的struct修改指針指向的值會影響被複制的struct。
func main() {
x := TestFo{Name: "foo1", Child: &Child{Name: "foo1-2"}, Child2: Child{Name: "foo1-3"}}
y := x
y.Name = "boo1"
y.Child.Name = "boo1-2"
y.Child2.Name = "boo1-3"
fmt.Printf("x is %v x.Child is %v\n", x, x.Child) // x is {boo1 0xc42007a1b0 {foo1-3}} y.Child is &{boo1-2}
fmt.Printf("y is %v y.Child is %v\n", y, y.Child) // y is {boo1 0xc42007a1b0 {boo1-3}} y.Child is &{boo1-2}
}
type TestFo struct {
Name string
Child *Child
Child2 Child
}
type Child struct {
Name string
}
- 匿名struct
- 聲明一個struct包含另一個struct時,可以匿名聲明,匿名聲明的好處是,可以隱式調用被包含的struct方法以及屬性,如:
package main
type Car struct {
Name string
}
func (c Car) Print() {
println("this is "+c.Name)
}
type City struct {
Car
}
func main() {
c := City{}
c.Name = "bmw"
c.Print()
}
- 如果被包含的struct中屬性有重名的,那麼就沒法隱式調用了,隱式調用其實應該是一塊go的語法糖。
package main
type Car struct {
Name string
}
type People struct {
Name string
}
type City struct {
Car
People
}
func main() {
c := City{}
// c.Name = "car" // 報錯:ambiguous selector c.Name
c.Car.Name = "car"
c.People.Name = "people"
}
- struct中包含指針型struct,那麼就算是隱式調用,也得先初始化。
package main
type Car struct {
Name string
}
type Build struct {
BuildName string
}
type City struct {
Build
*Car
}
func main() {
c := City{}
// c.Name = "car" // 編譯是可以通過的,但是運行時會在這裏中斷:bad
println(fmt.Sprintln(c)) // {{}, <nil>} 這裏面 {}是struct的初值,<nil>是指針類型的初值。
c.Car = &Car{}
c.Name = "car"
c.BuildName = "big wall"
}
切片陷阱
對於[:]或[x:]這種切片進行append操作之後,修改切片內的值,要小心,如果原數組的cap(array) > len(array),而你append之後的長度有小於等於原數組的cap,那修改切片會影響到原數組,否則不會。
原因在於,array2 = append(array1, …)操作如果cap(array1) < len(array2),那麼append會重新分配內存用以拼接,那array2的地址就是新地址,修改array2不會影響到array1,如果cap(array1) >= len(array2),那麼array2的地址是包含在array1裏的,修改array2[:len(array1)-1]的值,會影響到array1。具體例子如下:
package main
import "fmt"
func main() {
s1 := []int{1, 2, 3, 4, 5}
s2 := s1[3:]
s3 := append(s1[3:], 6)
s3[0] = 7
fmt.Printf("s1=%v s2=%v s3=%v\n", s1, s2, s3) // s1=[1 2 3 4 5] s2=[4 5] s3=[7 5 6]
ss1 := make([]int, 5, 10)
for i := 1; i < 6; i++ {
ss1[i-1] = i
}
ss2 := ss1[3:]
ss3 := append(ss2, 6)
ss3[0] = 7
fmt.Printf("ss1=%v ss2=%v ss3=%v\n", ss1, ss2, ss3) // ss1=[1 2 3 7 5] ss2=[7 5] ss3=[7 5 6] ss1[3]和ss2[0]被改變了!
}
NIL接口值
package main
import "fmt"
func main() {
var t *Test
var f Foo = t
if f != nil {
println(t, f) // 0x0 (0x114f580,0x0)
fmt.Printf("%v %v\n", t, f) // <nil> <nil>
f.Can() // Test Can
f.Cannot() // panic: value method main.Test.Cannot called using nil *Test pointer
}
}
type Foo interface {
Can()
Cannot()
}
type Test struct{}
func (*Test) Can() { fmt.Println("Test Can") }
func (Test) Cannot() { fmt.Println("Test Cannot") }