Golang奇葩點

RyuGou發表於2018-07-28

本文即Go語言的那些坑二。

Golang中函式被看做是值,函式值不可以比較,也不可以作為map的key

請問以下程式碼能編譯通過嗎?

import (
	"fmt"
)

func main(){
	array := make(map[int]func ()int)

	array[func()int{ return 10}()] = func()int{
		return 12
	}

	fmt.Println(array)
}```

**答案:**
複製程式碼

可以正常編譯通過。



稍作改動,改為如下的情況,還能編譯通過嗎?

​```Go
import (
	"fmt"
)

func main(){
	array := make(map[func ()int]int)

	array[func()int{return 12}] = 10

	fmt.Println(array)
}```
**答案:**
複製程式碼

不能編譯通過。


在Go語言中,函式被看做是第一類值:(first-class values):函式和其他值一樣,可以被賦值,可以傳遞給函式,可以從函式返回。也可以被當做是一種“函式型別”。例如:有函式``func square(n int) int { return n * n }``,那麼就可以賦值``f := square``,而且還可以``fmt.Println(f(3))``(將列印出“9”)。
Go語言函式有兩點很特別:
+ 函式值型別不能作為map的key
+ 函式值之間不可以比較,函式值只可以和nil作比較,函式型別的零值是``nil``

# 匿名函式作用域陷阱

請看下列程式碼輸出什麼?

​```Go
import (
	"fmt"
)

func main(){
	var msgs []func()
	array := []string{
		"1", "2", "3", "4",
	}

	for _, e := range array{

			msgs = append(msgs, func(){
			fmt.Println(e)
		})
	}

	for _, v := range msgs{
		v()
	}
}
複製程式碼

答案:

4
4
4
4
複製程式碼

在上述程式碼中,匿名函式中記錄的是迴圈變數的記憶體地址,而不是迴圈變數某一時刻的值。

想要輸出1、2、3、4需要改為:

import (
	"fmt"
)

func main(){
	var msgs []func()
	array := []string{
		"1", "2", "3", "4",
	}

	for _, e := range array{
		elem := e
		msgs = append(msgs, func(){
			fmt.Println(elem)
		})
	}

	for _, v := range msgs{
		v()
	}
}
複製程式碼

其實就加了條elem := e看似多餘,其實不,這樣一來,每次迴圈後每個匿名函式中儲存的就都是當時區域性變數elem的值,這樣的區域性變數定義了4個,每次迴圈生成一個。

[3]int[4]int 不算同一個型別

請看一下程式碼,請問輸出true還是false

import (
    "fmt"
    "reflect"
)

func main(){
    arrayA := [...]int{1, 2, 3}
    arrayB := [...]int{1, 2, 3, 4}
    fmt.Println(reflect.TypeOf(arrayA) == reflect.TypeOf(arrayB))
}

複製程式碼

答案是:

false
複製程式碼

陣列長度是陣列型別的一個組成部分,因此[3]int和[4]int是兩種不同的陣列型別。

陣列還可以指定一個索引和對應值的方式來初始化。

例如:

import (
    "fmt"
)

func main(){
    arrayA := [...]int{0:1, 2:1, 3:4}
    fmt.Println(arrayA)
}
複製程式碼

會輸出:

[1 0 1 4]
複製程式碼

有點像PHP陣列的感覺,但是又不一樣:arrayA的長度是多少呢?

import (
    "fmt"
)

func main(){
    arrayA := [...]int{0:1, 2:1, 3:4}
    fmt.Println(len(arrayA))
}
複製程式碼

答案是:

4
複製程式碼

沒錯,定義了一個陣列長度為4的陣列,指定索引的陣列長度和最後一個索引的數值相關,例如:r := [...]int{99:-1}就定義了一個含有100個元素的陣列r,最後一個元素輸出化為-1,其他的元素都是用0初始化。

不能對map中的某個元素進行取地址&操作

a := &ages["bob"] // compile error: cannot take address of map element
複製程式碼

map中的元素不是一個變數,不能對map的元素進行取地址操作,禁止對map進行取地址操作的原因可能是map隨著元素的增加map可能會重新分配記憶體空間,這樣會導致原來的地址無效

當map為nil的時候,不能新增值

func main() {
    var sampleMap map[string]int
    sampleMap["test"] = 1
    fmt.Println(sampleMap)
}
複製程式碼

輸出報錯:

panic: assignment to entry in nil map
複製程式碼

必須使用make或者將map初始化之後,才可以新增元素。

以上程式碼可以改為:

func main() {
    var sampleMap map[string]int
    sampleMap = map[string]int {
        "test1":1,
    }
    sampleMap["test"] = 1
    fmt.Println(sampleMap)
}
複製程式碼

可以正確輸出:

map[test1:1 test:1]
複製程式碼

&dilbert.Position(&dilbert).Position是不同的

&dilbert.Position相當於&(dilbert.Position)而非(&dilbert).Position

請看例子:

請問輸出什麼?

func main(){
    type Employee struct {
        ID int
        Name string
        Address string
        DoB time.Time
        Position string
        Salary int
        ManagerID int
    }
    var dilbert Employee

    dilbert.Position = "123"

    position := &dilbert.Position
    fmt.Println(position)

}
複製程式碼

輸出:

0xc42006c220
複製程式碼

輸出的是記憶體地址

修改一下,把&dilbert.Position改為(&dilbert).Position

func main(){
    type Employee struct {
        ID int
        Name string
        Address string
        DoB time.Time
        Position string
        Salary int
        ManagerID int
    }
    var dilbert Employee

    dilbert.Position = "123"

    position := &dilbert.Position
    fmt.Println(position)

}
複製程式碼

輸出:

123
複製程式碼

Go語言中函式返回的是值的時候,不能賦值

請看下面例子:

type Employee struct {
    ID int
    Name string
    Address string
    DoB time.Time
    Position string
    Salary int
    ManagerID int
}

func EmployeeByID(id int) Employee {
    return Employee{ID:id}
}

func main(){
    EmployeeByID(1).Salary = 0
}
複製程式碼

請問能編譯通過嗎?

執行,輸出報錯:cannot assign to EmployeeByID(1).Salary

在本例子中,函式EmployeeById(id int)返回的是值型別的,它的取值EmployeeByID(1).Salary也是一個值型別;值型別是什麼概念?值型別就是和賦值語句var a = 1var a = hello world等號=右邊的1Hello world是一個概念,他是不能夠被賦值的,只有變數能夠被賦值。

修改程式如下:

type Employee struct {
    ID int
    Name string
    Address string
    DoB time.Time
    Position string
    Salary int
    ManagerID int
}

func EmployeeByID(id int) Employee {
    return Employee{ID:id}
}

func main(){
    var a = EmployeeByID(1)
    a.Salary = 0
}
複製程式碼

這就可以編譯通過了

在宣告方法時,如果一個型別名稱本身就是一個指標的話,不允許出現在方法的接收器中

請看下面的例子,請問會編譯通過嗎?

import (
	"fmt"
)

type littleGirl struct{
	Name string
	Age int
}

type girl *littleGirl

func(this girl) changeName(name string){
	this.Name = name
}

func main(){
	littleGirl := girl{Name:"Rose", Age:1}
	
	girl.changeName("yoyo")
	fmt.Println(littleGirl)
}

複製程式碼

答案:

不能編譯通過,會提示“invalid receiver type girl(girl is a pointer type)”
複製程式碼

Go語言中規定,只有型別(Type)和指向他們的指標(*Type)才是可能會出現在接收器宣告裡的兩種接收器,為了避免歧義,明確規定,如果一個型別名本身就是一個指標的話,是不允許出現在接收器中的。

函式允許nil指標作為引數,也允許用nil作為方法的接收器

請看下面的例子,請問能編譯通過嗎?

import (
	"fmt"
)

type littleGirl struct{
	Name string
	Age int
}


func(this littleGirl) changeName(name string){
	fmt.Println(name)
}

func main(){
	little := littleGirl{Name:"Rose", Age:1}

	little = nil
	little.changeName("yoyo")
	fmt.Println(little)
}
複製程式碼

答案:

不能編譯通過,顯示"cannot use nil as type littleGirl in assignment"
複製程式碼

Go語言中,允許方法用nil指標作為其接收器,也允許函式將nil指標作為引數。而上述程式碼中的littleGirl不是指標型別,改為*littleGirl,然後變數little賦值為&littleGirl{Name:"Rose", Age:1}就可以編譯通過了。 並且,nil對於物件來說是合法的零值的時候,比如map或者slice,也可以編譯通過並正常執行。

Golang的時間格式化

不同於PHP的date("Y-m-d H:i:s", time()),Golang的格式化奇葩的很,不能使用諸如Y-m-d H:i:s的東西,而是使用2006-01-02 15:04:05這個時間的格式,請記住這個時間,據說這是Golang的誕生時間。

time := time.Now()

time.Format("20060102") //相當於Ymd

time.Format("2006-01-02")//相當於Y-m-d

time.Format("2006-01-02 15:04:05")//相當於Y-m-d H:i:s

time.Format("2006-01-02 00:00:00")//相當於Y-m-d 00:00:00
複製程式碼

更多精彩內容,請關注我的微信公眾號 網際網路技術窩 或者加微信共同探討交流:

Golang奇葩點

相關文章