GO 代码的一些查漏补缺
一、入门
1. 地址引用错误
1.1 代码
func main() {
slice := []int{0,1,2,3}
m := make(map[int]*int)
for key,val := range slice {
m[key] = &val
}
for k,v := range m {
fmt.Println(k,"->",*v)
}
}
0 -> 3
1 -> 3
2 -> 3
3 -> 3
1.2 问题
循环时使用的 key
和 val
都是同一个。
for key, val := range slice {
fmt.Printf("%p,%p\n", &key, &val)
m[key] = &val
}
0xc00000a0c8,0xc00000a0e0
0xc00000a0c8,0xc00000a0e0
0xc00000a0c8,0xc00000a0e0
0xc00000a0c8,0xc00000a0e0
1.3 解决
通过 v := val
,基本数据类型直接拷贝值,val
的改变不会影响到 v
package main
import "fmt"
func main() {
slice := []int{0, 1, 2, 3}
m := make(map[int]*int)
for key, val := range slice {
//fmt.Printf("%p,%p\n", &key, &val)
v := val
m[key] = &v
}
for k, v := range m {
fmt.Println(k, "->", *v)
}
}
2. make
初始化时的指定长度会填充零值
2.1 代码
预期想要输出 [1 2 3]
package main
import "fmt"
func main() {
s := make([]int, 5)
s = append(s, 1, 2, 3)
fmt.Println(s)
}
[0 0 0 0 0 1 2 3]
2.2 问题
make
初始化时的指定长度会使用零值进行填充
2.3 解决
package main
import "fmt"
func main() {
s := make([]int, 0)
s = append(s, 1, 2, 3)
fmt.Println(s)
}
3. append
只能接受切片
3.1 代码
package main
import "fmt"
func main() {
list := new([]int)
list = append(list, 1)
fmt.Println(list)
}
3.2 问题
append
函数第一个参数接收的是一个切片,而不是指针
func append(slice []Type, elems ...Type) []Type
3.3 解决
package main
import "fmt"
func main() {
list := new([]int)
*list = append(*list, 1)
fmt.Println(list)
}
package main
import "fmt"
func main() {
list := make([]int, 0)
list = append(list, 1)
fmt.Println(list)
}
4. 结构体什么情况下可以比较
4.1 代码
package main
import "fmt"
func main() {
sn1 := struct {
age int
name string
}{age: 11, name: "qq"}
sn2 := struct {
age int
name string
}{age: 11, name: "qq"}
if sn1 == sn2 {
fmt.Println("sn1 == sn2")
}
sm1 := struct {
age int
m map[string]string
}{age: 11, m: map[string]string{"a": "1"}}
sm2 := struct {
age int
m map[string]string
}{age: 11, m: map[string]string{"a": "1"}}
if sm1 == sm2 {
fmt.Println("sm1 == sm2")
}
}
4.2 问题
结构体比较分为两种情况:
- 相同结构体类型间比较;
- 不同结构体类型间比较;
相同结构体
- 结构体内部都为基本数据类型;
type A struct {
Name string
Age int
}
func TestB_1(t *testing.T) {
a1 := A{}
a2 := A{}
a3 := A{Name: "xxcheng"}
if a1 == a2 {
fmt.Println("a1==a2")
} else {
fmt.Println("a1!=a2")
}
if a1 == a3 {
fmt.Println("a1==a3")
} else {
fmt.Println("a1!=a3")
}
}
a1==a2
a1!=a3
不相同结构体
- 必须为匿名结构体;
- 结构体的属性顺序必须一致;
- 其他要求同相同结构体要求;
func TestB_2(t *testing.T) {
a1 := struct {
Name string
Age int
}{}
a2 := struct {
Name string
Age int
}{}
if a1 == a2 {
fmt.Println("a1==a2")
} else {
fmt.Println("a1!=a2")
}
}
a1==a2
func TestB_2(t *testing.T) {
a1 := struct {
Name string
Age int
}{}
a2 := struct {
Age int
Name string
}{}
if a1 == a2 {
fmt.Println("a1==a2")
} else {
fmt.Println("a1!=a2")
}
}
5. 接口类型才能使用类型断言
5.1 代码
package main
func GetValue() int {
return 1
}
func main() {
i := GetValue()
switch i.(type) {
case int:
println("int")
case string:
println("string")
case interface{}:
println("interface")
default:
println("unknown")
}
}
5.2 问题
接口类型才能使用类型断言。
5.3 解决
package main
func GetValue() interface{} {
return 1
}
func main() {
i := GetValue()
switch i.(type) {
case int:
println("int")
case string:
println("string")
case interface{}:
println("interface")
default:
println("unknown")
}
}
int
6. 变长参数函数传入的切片可被修改
6.1 代码
package main
import "fmt"
func hello(num ...int) {
num[0] = 18
}
func main() {
i := []int{5, 6, 7}
hello(i...)
fmt.Println(i[0])
}
18
6.2 理解
使用一个切片作为可变参数实参传入,切片是引用类型,函数内的可变参数切片和传入的切片引用的是同一片地址。
7. defer
延迟调用时上下文问题
7.1 代码
func AreOk(i int) {
fmt.Println("i:", i)
}
func AreOk2(i *S) {
fmt.Println("i:", i.Value)
}
type S struct {
Value int
}
func TestOOO(t *testing.T) {
i := 5
j := &S{
Value: 5,
}
defer AreOk(i)
defer AreOk2(j)
defer func() {
AreOk(i)
}()
i += 10
j.Value += 10
}
i: 15
i: 15
i: 5
7.2 理解
在执行到 defer
语句时会保存一份当前环境的上下文副本,后续对变量的改动不会影响变量的变化,变量 j
是一个指针,指向的还是原来的值,defer
保存的是地址值,指针指向的还是相同的一块地址 。如果是一个函数,会形成闭包,不执行就不知道会引用哪些外部变量。
8. 字符串是只读属性
8.1 代码
func TestBBBB(t *testing.T) {
s := "Teacher"
s[0] = 'w'
}
8.2 理解
GO
中,字符串是只读的。
9. 具名返回值的输出受到 defer
影响
9.1 代码
func increaseA() int {
var i int
defer func() {
i++
}()
return i
}
func increaseB() (r int) {
defer func() {
r++
}()
return r
}
func main() {
fmt.Println(increaseA())
fmt.Println(increaseB())
}
0
1
9.2 理解
如果返回值的参数是具名返回值,那么在 return
的时候会将当前这些返回值赋值给具名返回值,如果 defer
内对具名返回值进行了修改,会影响最后的返回值。
具名返回值 defer
和 return
执行顺序
- 执行
return
,将return
中的返回值赋值给对应的具名返回值; - 按照先进后出顺序执行
defer
,对具名返回值进行修改; - 返回
defer
中修改后的值;
9.3 加深理解示例
func JustFn() (a, b, c int) {
d := 10
a++
b++
c++
d++
defer func() {
a++
b = 99
c++
d=111
}()
b++
fmt.Println(a, b, c, d)
return a, b, d
}
func Test_JustFn(t *testing.T) {
fmt.Println(JustFn())
}
1 2 1 11
2 99 12
10. map
无法直接寻址
10.1 代码
type Math struct {
x, y int
}
var m = map[string]Math{
"foo": Math{2, 3},
}
func Test9(t *testing.T) {
m["foo"].x = 4
fmt.Println(m["foo"].x)
}
10.2 理解
map
类型无法直接寻址访问,只能通过中间变量或者指针的方式访问。
10.3 解决
var m = map[string]Math{
"foo": Math{2, 3},
}
var m2 = map[string]*Math{
"foo": &Math{2, 3},
}
func Test9(t *testing.T) {
a := m["foo"]
a.x = 111
m["foo"] = a
fmt.Println(m)
fmt.Println("-----")
m2["foo"].x = 999
fmt.Println(m2["foo"])
}
map[foo:{111 3}]
-----
&{999 3}
二、刨析深入
1. ==
深入理解
1.1 ==
使用的前提必须是数据类型是相同的。
var b1 int
var b2 int8
if b1 == b2 {
fmt.Println("b1==b2")
} else {
fmt.Println("b1!=b2")
}
1.2 浮点数运算精度缺失判断错误的情况
var a float64 = 0.1
var b float64 = 0.2
var c float64 = 0.3
if (a + b) == c {
fmt.Println("b1==b2")
} else {
fmt.Println("b1!=b2")
}
如何解决?设置一个极限值,浮点数运算的差值的绝对值小于这个极限值就视为相等。
1.3 切片之间无法比较、map
之间无法比较
切片之间无法直接比较,但是切片可以和 nil
比较
func TestCCC(t *testing.T) {
a := []int{1, 2, 3}
b := []int{1, 2, 3}
if a == b {
}
}
func TestCCC(t *testing.T) {
a := []int{1, 2, 3}
if a == nil {
}
}
切片之间无法直接比较,但是切片可以和 nil
比较
func TestCCC(t *testing.T) {
a := map[string]string{}
b := map[string]string{}
if a == b {
}
}
func TestCCC(t *testing.T) {
a := map[string]string{}
if a == nil {
}
}
1.4 指针可以比较,比较地址值
func TestCCC(t *testing.T) {
a := 1
b := &a
c := &a
if b == c {
fmt.Println("equal")
} else {
fmt.Println("not equal")
}
}
equal
1.5 接口可以比较,比较动态类型和动态值
type OKI interface {
Ok() bool
}
type OBJK struct {
}
func (o *OBJK) Ok() bool {
return true
}
func TestDDD(t *testing.T) {
var a OKI
var b OKI = new(OBJK)
fmt.Println(a == b)
fmt.Println(a == nil)
}
false
true
三、参考链接
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
跟着大佬进步!!
期待大佬的回归