【Go入门可能会踩的坑-01】

Go语言是一门简单有趣的编程语言,与其他语言一样,在使用时不免会遇到很多坑,不过它们大多不是Go本身的设计缺陷。如果你刚从其他语言转到Go,那这篇文章可能会帮到你。旨在帮助你跳过这些坑,能减少大量调试代码的时间。

1. 左大括号{ 不能单独放一行

在其他大多数语言中,{ 位置可以自行决定。Go比较特别,遵守分号注入规则(automatic semicolon injection) : 编译器会在没喊代码尾部特定分隔符后加 ; 来分隔多条语句,比如会在 ) 后面加分号 :

// 错误示例
func main()
{
    fmt.Println("hello world")
}

// 等效于
func main(); // 无函数体
{
    fmt.Println("hello world")
}

./main.go : missing function body
./main.go : syntax error: unexpected semicolon or newline before {
// 正确示例
func main() {
    fmt.Println("hello world")
}

2. 未使用的变量

如果在函数体代码中有未使用的变量,则无法编译通过,不过全局变量声明但是不使用是可以的。
即使变量声明后为变量赋值,依旧无法通过编译,需在某处使用它。

// 错误示例
var gVar int   // 全局变量,声明不使用是可以的

func main() {
    var one int			// error : one declared and not used
    two : = 2 			// error : two declared and not used
    var three int 		// error : three declared ant not used
    three = 3
}

// 正确示例, 可以直接注释或移除未使用的变量
func main(){
    var one int
    _ = one
    
    two := 2
    fmt.Println(two)
    
    var three int
    one = three
    
    var four int 
    four = four
}

3. 未使用的import

如果 import 一个包,但是包中的变量、函数、接口和结构体一个都没使用的话,将编译失败。

可以使用_ 下划线符号作为别名来忽略导入的包,从而避免编译错误,这只会执行 package 的 init()

// 错误示例
import (
    "fmt"		// imported and not used : "fmt"
    "log"		// imported and not used : "log"
    "time"		// imported and not used : "time"
)

func main() {
}

// 正确示例, 可以使用goimports 工具来注释或移除未使用的包
import (
	_ "fmt"
    "lgo"
    "time"
)

func main() {
    _ = log.Println()
    _ = time.Now
}

4. 简短声明的变量只能在函数内部使用

// 错误示例
tvar := 1 		// syntax error : non-declaration statement outside function body 
func main() {
}

// 正确示例
var tvar = 1
func main(){
    
}

5. 使用简短声明来重复声明变量

不能用简短声明方式来单独为一个变量重复声明, := 左侧至少有一个新变量,才允许多变量的重复声明

// 错误示例
func main() {
    one := 0
    one := 1  	// error : no new variables on left side of :=
}

// 正确示例
func main() {
    one := 0
    one, two := 1, 2 // two 是新变量,允许 one的重复声明。
    
    one, two = two, one  	// 交换两个变量的值
}

6. 不能使用简短声明来设置字段的值

struct 的变量字段不能使用 := 来赋值

// 错误示例
type info struct{
    result int
}

func work() (int, error) {
    return 2, nil
}

func main(){
    var data info
    data.result, err := work() 		// error : non-name data.result on left side of :=
    fmt.Println("info : %+v\n", data)
}


// 正确示例
func main(){
    var data info
    var err error   // err 需要预声明
    
    data.result, err = work()
    if err != nil {
        fmt.Println(err)
        return 
    }
    
    fmt.Println("info : %+v", data)
}

7. 不小心覆盖了变量

对从动态语言转过来的开发者来说,简短声明很好用,但可能会让人误会 := 是一个赋值操作符。

如果在新的代码块中误用了:= ,编译不会报错,但是变量不会按照预期工作。

func main() {
    x := 1
    println(x)   		// 1
    {
        println(x)		// 1
        x := 2
        println(x)		// 2    
    }
    println(x) 			// 1
}

这是Go开发者常犯的错,而且不易被发现。 可使用 vet 工具来诊断这种变量覆盖,Go默认不做覆盖检查,添加 -shadow 选项来启用:

go tool vet -shadow main.go

注意vet 不会报告全部覆盖的变量, 可以使用 go-nyet 来做进一步的检测。

8. 显式类型的变量无法使用nil来初始化

nil 是interface、function、pointer、map、slice和channel类型变量的默认初始值。 但声明时不指定类型,编译器也无法推断出变量的具体类型。

// 错误示例
func main(){
    var x = nil 		//  error : use of untyped nil
    _ = x
}

// 正确示例
func main() {
    var x = interface{} = nil
    _ = x
}

9. 直接使用值为nil的slice、map

允许对值为nil的slice添加元素,但是对值为nil的map添加元素则会造成运行时panic。

// map错误示例
func main(){
    var m map[int]int
    m[1] = 1  			// error: panic: assignment to entry in nil map
    // m := make(map[int]int)   // map的正确声明,分配实际内存
}

// slice 正确示例
func main() {
    var s []int
    s = append(s, 1)
}

10.map容量

在创建map类型的变量时可以指定容量,但是不能像slice一样使用 cap() 函数来检测分配空间的大小:

// 错误示例
func main(){
    m := make(map[int]int, 100)
    println(cap(m)) 		// error : invalid argument m(type map[int]int) for cap
}

11. String 类型的变量值不能为nil

// 错误示例
func main() {
    var s string = nil 		// can not use nil as type string in assignment
    if s == nil {			// invalid operation: s == nil (mismatched types string and nil)
        s = "default"
    }
}

// 正确示例
func main() {
    var s string 	 	// 字符串类型的零值默认是 ""
    if s == "" {
        s = "default"
    }
}

12.Array类型的值作为函数参数

在C/C++中,数组(名)是指针。将数组作为参数传进函数时,相当于传递了数组内存地址的引用,在函数诶不会改变该数组的值。
在Go中,数组是指。作为参数传进函数时,传递的是数组的原始值的拷贝,此时在函数内部是无法更新该数组的。

// 数组使用值拷贝传参
func main() {
    x := [3]int{1,2,3}
    
    func(arr [3]int){
        arr[0] = 7
        fmt.Println(arr)  // [7 2 3]
    }(x)
    
    fmt.Println(x)  // [1 2 3]  并不是以为的 [7 2 3]
}

如何修改参数数组?

  • 直接传递指向这个数组的指针类型

    // 传址会修改原数据
    func main() {
        x := [3]int{1,2,3}
        
        func(arr *[3]int) {
            (*arr)[0] = 7
            fmt.Println(arr)     // &[7,2,3]
        }(&x)
        
        fmt.Println(x)   // [7 2 3]
    }
    
  • 直接使用slice:即使函数内部的到的是slice的值拷贝,但依旧会更新slcie的原始数据(底层array)

    // 会修改 slice 的底层array,从而修改 slice
    func main() {
        x := []int{1,2,3}
        
        func(arr []int) {
            arr[0] = 7
            fmt.Println(arr)     // &[7,2,3]
        }(x)
        
        fmt.Println(x)   // [7 2 3]
    }
    

13. range 遍历 slice 和array 时混淆了返回值

与其他编程语言中 for -inforeach 遍历语句不同,Go中的 range在遍历时会生成两个值,第一个是元素的索引,第二个是元素的值:

// 错误示例
func main() {
    x := []sting{"a","b","c"}
    
    for v := range x {
        fmt.Println(v)  // 0 1 2
    }
}

// 正确示例
func main() {
    x := []sting{"a","b","c"}
    
    for _, v := range x {
        fmt.Println(v)  // 0 1 2
    }
}

14.slice和array其实是一维数据

看起来Go支持多维array和slice,可以创建数组的数组、切片的切片,但其实并不是。

对依赖动态计算多维数组值的应用来说,就性能和复杂度而言,用Go实现的效果并不理想。可以使用原始的一维数组、"独立"的切片、“共享底层数组”的切片来创建动态的多维数组。

  1. 使用原始的以为数组:要做好索引检查、溢出检测、以及当数组满时再添加值时要重新做内存分配。
  2. 使用“独立”的切片分两步:
    • 创建外部slice
    • 对每个内部slice进行内存分配
      注意内部的slice 相互独立,使得任意内部slice增说都不会影响到其他slice
// 使用独立的slice创建[2][4]的动态多维数组
func main() {
    x := 2
    y := 4
    
    table := make([][]int,x)
    for i := arnge table {
        table[i] = make([]int, y)
    }
}

使用“共享底层数组”的切片

  • 创建一个存放原始数据的容器slice
  • 创建其他slice
  • 切割原始slice来初始化其他slice
func main() {
    h ,w := 2, 4
    raw := make([]int, h*w)
    
    for i := range raw {
        raw[i] = i
    }
    
    // 初始化原始 slice
    fmt.Println(raw, &raw[4])    // [0 1 2 3 4 5 6 7] 0xc420012120

    table := make([][]int, h)
    for i := range table {

        // 等间距切割原始 slice,创建动态多维数组 table
        // 0: raw[0*4: 0*4 + 4]
        // 1: raw[1*4: 1*4 + 4]
        table[i] = raw[i*w : i*w + w]
    }

    fmt.Println(table, &table[1][0])    // [[0 1 2 3] [4 5 6 7]] 0xc420012120
}

15.访问 map 中不存在的 key

和其他编程语言类似,如果访问了map中不存在的key希望返回nil。在Go中会返回元素对应数据类型的零值,比如 nil 、 '' 、 false 、0 ,取值操作总有值返回,故不能通过取出来的值来判断key 是不是存在map中。检查key是否存在可以用map直接访问,检查返回的第二个参数即可:

// 错误的 key 检测方式
func main() {
    x := map[string]string{"one" : "1", "two": "", "three" : "3"}
    if v := x["two"]; v == "" {
        fmt.Println("key two is no entry")    	// 键two存不存在都会返回空的字符串
    }
}

// 正确示例
func main() {
    x := map[string]string{"one" : "1", "two": "", "three" : "3"}
    if _,ok := x["two"]; !ok {
        fmt.Println("key two is no entry")    	// 键two存不存在都会返回空的字符串
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值