初学go的那些坑

zxy_666 · · 2686 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

版权声明:本文为博主原创文章,博客地址:
https://blog.csdn.net/zxy_666/article/details/80390843,未经博主允许不得转载。

前言

初学go时,总是在不经意间邂逅各种坑。那些坑说大不大,但总能耗费你不少精力陪它。今天,给大家分享:那些年我们追过的go坑。

包package

package是go管理代码的重要工具。

下面主要针对在GoLand(go的一款开发工具)实践中的说明

package基本使用

  • 项目中的每个文件夹都是一个package。package下可以嵌套package。但每个.go文件的package以其所在的当前文件夹为准。

  • 在GoLand中新建文件夹时,在该层目录下新建的.go文件,其package默认为当前文件夹名(大小写一致)。当.go文件创建后,再去修改文件夹名时,需要注意其下的.go文件中的package是否自动修改。

  • ==同一层目录下的.go文件,package必须一致==,否则编译出错。

同层目录不包括嵌套目录

如下所示:
a1.go和a2.go属于dir A这一级目录下,其package必须一致。
b1.go和b2.go属于dir B这一级目录。

projectName
    src
        dir A
             dir B
                b1.go
                 b2.go
        a1.go
        a2.go

例如,文件夹world下的所有.go文件,package必须一致,package可以随便起,不一定与文件夹名一样。

image

包(package)可见性

在了解可见性之前,我们先了解一个概念:可导出的。
在go语言中,变量或函数名的首字母大写时,其就是可导出的,小写时则是不可导出的。

  • 函数和变量的可访问性是以包做隔离的。
包          函数或变量      可访问性

同一个包    可/不可导出的     都能访问
其他的包    可导出的          可访问
其他的包    不可导出的        不能访问

同一个包内,均可访问导出/不可导出的变量或函数。无论变量是全局变量还是struct的成员变量,无论函数是全局函数还是隶属于某个struct的成员函数。

这里的“全局”指的是不通过“对象.变量或函数”,而是直接通过package.变量或函数即可访问的变量或函数。
· 全局变量在同一个包内,直接访问,包外通过package.变量访问,函数亦然。当然struct对象也是一种特殊的全局数据结构,遵循这个规则。
· 对象成员变量或函数,必须通过“对象实例.变量或函数”访问。

例如:

//项目结构
project
    src
        dir A
            a1.go
            a2.go
        dir B
            b.go

//项目代码
------------- a1.go
package A

//可导出的:包内包外都能访问
//不可导出的:包内可见,包外不可见

//A1是可导出的
type A1 struct{
    Name string //可导出的
    age  int //不可导出的
}

//a1是不可导出的
type a1 struct{

}

var V string //可导出(全局变量)

var v string //不可导出(全局变量)

func F1(){} //可导出的(全局函数)

func f1(){} //不可导出的(全局函数)

func (A *A1)F1(){} //可导出的(对象函数)

func (A *A1)f1(){} //不可导出的(对象函数)

func (a *a1)F1(){} //可导出的(对象函数)

func (a *a1)f1(){} //不可导出的(对象函数)


------------- a2.go
package A

func A2(){
    //同一个包内,无论导出性与否,均可直接访问
    obj_A1:=new(A1) 
    obj_a1:=new(a1)
    _:=V        //"_"表示接收右边的变量,但不使用
    _:=v
    F1()
    f1()
    obj_A1.F1() //对象成员(包括函数和变量),需要通过"对象.成员"方式访问
    obj_A1.f1()
    obj_a1.F1()
    obj_a1.f1()
}

------------- b.go
package B

import (
    "A"
)

//引用其他包,需要import "指定的包"
//使用其他包的成员,需要通过 "指定包.成员" 方式访问
//"指定包.成员"中,只有可导出的成员是可见的
func B(){
    //下面都是正确的(除了最后一个)
    obj_A1:=new(A.A1)
    _:=A.V
    A.F1()
    obj_A1.F1()
    obj_A1.f1()     //错误

    //下面都是错误的。不可导出成员包外不可见
    obj_a1:=new(A.a1)
    _:=A.v
    A.f1()
    obj_a1.F1()
    obj_a1.f1()
}

package main之间运行时不能互相访问

备注:
main是个特殊的package。go的可执行方法,只有main包下的main方法。

同一级文件夹下的package是main,当该文件夹下的某个.go文件调用同级目录下的变量或方法时,静态编译不报错,但是执行run动态编译时,就会报undefined错误。

# command-line-arguments
src/main.go:11: undefined: B

如:

dir A
    a1.go
    a2.go


---- a1.go
package main

func main(){
    //静态编译(即写代码时)不报错
    //运行该main()方法时,报错
    B() //调用a2.go文件的B()方法
}


----- a2.go
package main

func B(){
    fmt.Plintln("I am B()")
}

main包的文件夹下嵌套main包的子目录时,文件夹之间的.go文件彼此不可见。如

dir A
    dir B
        b1.go
        b2.go
    a1.go
    a2.go

---- a1.go
package main

func A1(){
    A2()    //静态编译通过,动态编译找不到
    B1()    //静态编译报错,B1()在这里不可见
}

---- a2.go
package main

func A2(){}

---- b1.go
package main

func B1(){
    B2()    //静态编译通过,动态编译找不到
    A2()    //静态编译报错,A2()在这里不可见
}

---- b2.go
package main

func B2(){}

包内嵌套同样的包(非main包)

相互调用是允许的,但不建议这么做,容易出错。

src 
    dir world
        dir sag
            a2.go 
        a1.go
    main.go

---- a1.go
package world

func A(){
    fmt.Println("I am A1()")
}


---- a2.go
package world

func A(){
    fmt.Println("I am A2()")
}

---- main.go
package main

//注意这里!!!
//存在包内嵌套同样的包时,使用全目录名代表对应目录下的包package
import (
    "world"     
    world2 "world/sag"  
)

func main(){
    world.A()   //a1.go的
    world2.A()  //a2.go的
}

运行结果:
I am A1()
I am A2()


注意:

由于2个嵌套目录下的包package都是world,为了做区分
,import引用相应包时,go使用全目录名(默认是src目
录下)代表该目录下的包。即使这2个目录下的包不是world,
而是其他值,只要包相同,则使用目录代替。


总结:

1. 其实,无论是否存在相同的包,go import时都使用
全目录名代表引用该目录下的包。使用该包的变量或函
数时,用"包.变量or函数“;如果import时给引用的目录
起了别名,就用"别名.变量or函数"引用。

2. 目录名与包名可以相同,也可不同。

看一个相同包的例子

image

再强调一次
==总结:==

  1. 其实,无论是否存在相同的包,go import时都使用
    全目录名” 代表”引用该目录下的包package”。使用该包的变量或函
    数时,用”包.变量or函数“;如果import时给引用的目录
    起了别名,就用”别名.变量or函数“引用。

  2. 目录名与包名可以相同,也可不同

go初始化顺序init

package main

import(
    //以下引用均为举例,"项目名/src/"是默认前缀
    "a/b/c" //引用"项目名/src/a/b/c目录下的包"
    "b/c/d" 
    "x/xx/xxx"  //"项目名/src/x/xx/xxx目录下的包"
)

func main(){
    //程序的执行入口
}

前面提过,main包下的main函数是程序的可执行函数,即入口函数。
go程序启动时,执行过程如下:

image

从图中可知,运行main方法时:

  1. 先初始化import的所有包
  2. 先初始化const常量,然后是全局变量var,再是init()函数
  3. 当import下的包都初始化结束后,最后执行main的const,var,init(),main()

包循环引用import

从go的初始化图中可知,go的初始化存在顺序问题,这就可能导致“循环引用”,循环引用import在go中是不允许的,动态编译会报错。

举例:

package main

import"a" //main 引用a
)

---------
package a

import"b" //a 引用b
)

---------
package b

import"c" //b 引用c
)

---------
package c

import"a" //c 引用a

从例子中可见,当指定main()函数时,由于go的初始化顺序,
· 先初始main包的import。这里引用a包
· 再初始化a包。a包引用了b包。
· 接着初始化b包。b包引用了c包。
· 然后初始化c包。c包引用了a包。
· 由于c包引用了a包,需要初始化话a包。

包初始化顺序:
main -> a -> b -> c -> a

这就形成了循环引用。动态编译不通过。所以,写go时,注意避开循环引用。


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:zxy_666

查看原文:初学go的那些坑

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

2686 次点击  
加入收藏 微博
下一篇:go 多线程
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传