from Golang small white to become a Golang engineer nearly two months, I want to share the novice in the development of common mistakes, are I personally stepped on the pit. Some of these errors can lead to failure to compile, this error is easy to find, and some errors will not be thrown at compile time, even at run time will not be panic, if the lack of knowledge, scratching the scalp can not understand where the bug is.
1. Add data to nil map, nil slice
Consider whether this code is wrong, and then run it again:
package mainfunc main() { var m map[string]string m["name"] = "zzy"}
Without an accident, this code will result in a panic:
panic: assignment to entry in nil map
This is because the code simply declares the type of map, but does not create the underlying array for the map, at which point the map does not actually exist in memory, that is, nil map, which can be verified by running the following code:
package mainimport "fmt"func main() { var m map[string]string if m == nil { fmt.Println("this map is a nil map") }}
So to use the map smoothly, be sure to create it using the built-in function make function:
m := make(map[string]string)
It is also possible to use the literal method, with the same effect as make:
m := map[string]string{}
Similarly, adding data directly to the nil slice is not allowed, because the underlying array of slice is not initialized by the Make function, only the slice type is declared, and the underlying array does not exist:
package mainfunc main() { var s []int s[0] = 1}
The code above will produce a panic runtime error:index out of range
, and the correct approach should be to use the Make function or the literal:
package mainfunc main() { //第二个参数是slice的len,make slice时必须提供,还可以传入第三个参数作为cap s := make([]int, 1) s[0] = 1}
It may be found that the use of the Append function for nil Slice is also valid without make:
package mainimport "fmt"func main() { var s []int s = append(s, 1) fmt.Println(s) // s => [1]}
That's because slice itself is like a struct, it has a Len attribute, is the current length, there is a cap property, is the length of the underlying array, the APPEND function will determine the Len and cap of the incoming slice, and if Len is going to be larger than the CAP, Call the Make function to generate a larger new array and copy the data from the original underlying array (the above are my guess, without verification, interested students can go to challenge the source code), the process is similar:
package mainimport "fmt"func main() { var s []int //len(s)和cap(s)都是0 s = append(s, 1) fmt.Println(s) // s => [1]}func append(s []int, arg int) []int { newLen := len(s) + 1 var newS []int if newLen > cap(s) { //创建新的slice,其底层数组扩容为原先的两倍多 newS = make([]int, newLen, newLen*2) copy(newS, s) } else { newS = s[:newLen] //直接在原数组上切一下就行 } newS[len(s)] = arg return newS}
Nil map, nil slice wrong use is not very scary, after all, compile time can be found, the following to say a mistake is very pit dad, accidentally in strokes, difficult to troubleshoot.
2. Misuse: = Assignment causes variable override
Look at this code first, and guess what it will print:
package mainimport ( "errors" "fmt")func main() { i := 2 if i > 1 { i, err := doDivision(i, 2) if err != nil { panic(err) } fmt.Println(i) } fmt.Println(i)}func doDivision(x, y int) (int, error) { if y == 0 { return 0, errors.New("input is invalid") } return x / y, nil}
I guess someone would think it was:
11
The actual execution of the result is:
12
Why is it so!?
This is because the scope of the variables in the Golang range is small to each lexical block (the incomprehensible classmate can simply be the {}
part of the parcel) is a separate scope, everyone knows that the internal declaration of each scope will mask the external declaration of the same name, and each if
statement is a lexical block, that is to say , if you accidentally assign a variable in a statement instead of if
:=
=
an if
outside statement, a new local variable is generated and only if
valid after the assignment statement in the statement, and an external variable of the same name is masked. There will be no change due to the logic behind this assignment statement!
At the language level this may not be a mistake, but if you misuse it in your actual work, the bugs will be very secretive. such as the code in the example, because it err
is not declared before, so use the :=
assignment (figure easy, less write var err error
), and then neither in the compile times wrong, nor in the run times wrong, it will let you baffled its solution, think their logic clearly go right, why the final result is always wrong , until you 1.1 debugging, only to find yourself accidentally wrote one more :
.
Because of this has been the pit several times, every time I have been looking at the logic of a loophole, and finally found that it was =
written :=
, alas, it is tears.
3. Passing a value as a reference
Difference between value type data and reference type data I'm sure everyone here can get it out, otherwise you don't have to look down because you don't understand.
In Golang, array
and struct
are value types, and, slice
map
chan
are reference types, so when we write code, basically do not use array
, but slice
instead of it, for struct
the use of pointers as far as possible, This avoids the time and space consumption of copying data when a variable is passed, and avoids the situation where the original data cannot be modified.
If this is unclear, the result may be a defective code and, more serious, a bug.
Consider this piece of code and run it:
package mainimport "fmt"type person struct { name string age byte isDead bool}func main() { p1 := person{name: "zzy", age: 100} p2 := person{name: "dj", age: 99} p3 := person{name: "px", age: 20} people := []person{p1, p2, p3} whoIsDead(people) for _, p := range people { if p.isDead { fmt.Println("who is dead?", p.name) } }}func whoIsDead(people []person) { for _, p := range people { if p.age < 50 { p.isDead = true } }}
I believe a lot of people see the problem at a glance, but there are certainly people do not know the for range
mechanism of grammar, I ramble about: Golang in the for range
syntax is very convenient, can easily traverse array
, slice
, and map
other structures, but it has a characteristic, is to copy the currently traversed element into the internal variable, in the whoIsDead
function for range
, each of which will be people
person
copied to the p
variable, similar to the operation:
p := person
As mentioned above, struct
is a value type, so in the assignment to p
the process, you actually need to regenerate a copy of the person
data, for for range
internal use, do not believe to try:
package mainimport "fmt"type person struct { name string age byte isDead bool}func main() { p1 := person{name: "zzy", age: 100} p2 := p1 p1.name = "changed" fmt.Println(p2.name)}
So p.isDead = true
this operation actually changes the newly generated p
data, not the people
Central Plains person
, where a bug is generated.
In the case of the for range
internal only need to read the data without modification, it does not matter how to write, at most, the code is not perfect, and need to modify the data, it is best to pass the struct
pointer:
package mainimport "fmt"type person struct { name string age byte isDead bool}func main() { p1 := &person{name: "zzy", age: 100} p2 := &person{name: "dj", age: 99} p3 := &person{name: "px", age: 20} people := []*person{p1, p2, p3} whoIsDead(people) for _, p := range people { if p.isDead { fmt.Println("who is dead?", p.name) } }}func whoIsDead(people []*person) { for _, p := range people { if p.age < 50 { p.isDead = true } }}
Run it:
who is dead? px
Everything is OK, very good code.
There is another way to use the index access people
person
, change the whoIsDead
function, also can achieve the same purpose:
func whoIsDead(people []person) { for i := 0; i < len(people); i++ { if people[i].age < 50 { people[i].isDead = true } }}
Well, for range
part of it, let's talk about map
the transfer and modification of the value in the structure.
This code changes the previous to the people []person
map
structure, we think there is a mistake, if wrong, where wrong:
package mainimport "fmt"type person struct { name string age byte isDead bool}func main() { p1 := person{name: "zzy", age: 100} p2 := person{name: "dj", age: 99} p3 := person{name: "px", age: 20} people := map[string]person{ p1.name: p1, p2.name: p2, p3.name: p3, } whoIsDead(people) if p3.isDead { fmt.Println("who is dead?", p3.name) }}func whoIsDead(people map[string]person) { for name, _ := range people { if people[name].age < 50 { people[name].isDead = true } }}
go run
, Error:
cannot assign to struct field people[name].isDead in map
This error is a bit crazy, I think a lot of people can not understand it. I answer, map
uses array
to store data, and there is no capacity limit, and with the increase of the map
element, you need to create a larger array
To store the data, the previous address is invalid because the data is copied to a new, larger array
, so the elements in the map
are not addressable and cannot be modified. This error means that the element in map
is not allowed to be modified.
Even if the element in the map
does not have the above limitation, this code is still wrong, think about it, why? The answer has been said before.
So, how to change the correct, the old routine, still use the pointer:
package mainimport "fmt"type person struct { name string age byte isDead bool}func main() { p1 := &person{name: "zzy", age: 100} p2 := &person{name: "dj", age: 99} p3 := &person{name: "px", age: 20} people := map[string]*person{ p1.name: p1, p2.name: p2, p3.name: p3, } whoIsDead(people) if p3.isDead { fmt.Println("who is dead?", p3.name) }}func whoIsDead(people map[string]*person) { for name, _ := range people { if people[name].age < 50 { people[name].isDead = true } }}
In addition, interface{}
when you attempt to modify a property directly in an assertion struct
rather than through a pointer modification:
package maintype person struct { name string age byte isDead bool}func main() { p := person{name: "zzy", age: 100} isDead(p)}func isDead(p interface{}) { if p.(person).age < 101 { p.(person).isDead = true }}
will report a compilation error directly:
cannot assign to p.(person).isDead
Even though the code is wrong to compile, always remember that struct
it is a value type, use the pointer to manipulate it, and the correct approach is:
package mainimport "fmt"type person struct { name string age byte isDead bool}func main() { p := &person{name: "zzy", age: 100} isDead(p) fmt.Println(p)}func isDead(p interface{}) { if p.(*person).age < 101 { p.(*person).isDead = true }}
Finally, it must be said that the Golang in the hands of the home travel, promotion of the necessary knowledge of salary increase, hope that the students master proficiency.