包的导入

包导入语法

在写Go代码的时候经常用到import这个命令用来导入包文件,导入方式如下

  1. import(
  2. "fmt"
  3. )

然后在代码里面可以通过如下的方式调用

  1. fmt.Println("hello world")

上面这个fmt是Go语言的标准库,他其实是去 $GOROOT 路径下去加载该模块,当然Go的import还支持如下两种方式来加载第三方的模块

  1. 相对路径
  2. import "./model" //当前文件同一目录的model目录,但是不建议这种方式import,因为一旦目录结构发生变化,路径就需要修改
  3. 绝对路径
  4. import "shorturl/model" //加载GOPATH/src/shorturl/model模块

其他包导入相关操作

  1. 点操作 有时候会看到如下的方式导入包 import(. "fmt") 这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名,也就是前面你调用的fmt.Println("hello world") 可以省略的写成Println("hello world")
  2. 别名操作 别名操作顾名思义可以把包命名成另一个用起来容易记忆的名字 import( f "fmt") 别名操作调用包函数时前缀变成了重命名的前缀,即 f.Println("hello world")
  3. 匿名导包操作 这个操作经常是让很多人费解的一个操作符,请看下面这个
    1. import (
    2. "database/sql"
    3. _ "github.com/ziutek/mymysql/godrv"
    4. )

当我们导入一个没有使用的包的时候,ide就会提示Unused import,并且提供了一个选项 import for side-effect, 应用选项后就会转化成 _ "github.com/ziutek/mymysql/godrv", 从字面意思理解,这个表示导入某个包并不是真的要使用某个包,而是为了导入包时的副作用: 比如计算包级变量的初始化表达式和执行导入包的init初始化函数,这个操作被称作包的匿名导入

关于匿名导包的应用可以查看这篇:https://www.jianshu.com/p/742ec19d2988

包的初始化

包的初始化是从初始化包级别的变量开始,按照声明的顺序初始化,在依赖已解析完毕的情况下,根据依赖的顺序进行初始化。如果包由多个.go文件组成,初始化按照编译器收到的文件顺序进行:go的编译工具会在调用编译器之前将.go文件先进行排序。
对于包级别的每一个变量,生命周期从其值被初始化开始,但是对于其他的一些变量,比如数据表,初始化表达式不是简单的设置他的初始化的值。这种情况下,init函数的机制会解决这类的问题。每一个包中可以在多个文件声明init函数,在进行包的初始化时,会依次调用执行

  1. func init() {...}

package和文件夹的关系

  1. 一个文件夹下一般只能有一个包名,但是以test.go作为结尾的源码文件,可以使用<被测试的包名>test作为包名,放置在相同的源码目录下而不会产生编译错误。
  2. 一个包可以存在于两个文件夹下,但是在导入时需要使用别名来区分,否则会产生重复声明的错误,但是这样使用比较诡异,不建议这样使用
  3. 包名可以和目录名不一样,但不建议这么做,这样容易造成调用这个包的人,无法快速知道这个包的名称是什么
  4. 在执行导入的时候,若不手动定义包名,则从导入路径的源码文件中的 package 行获取包名,也即目录名和包名没有直接的关系
  5. 在导入时的路径实际上是包的文件系统的路径,包名是由别名指定或者有源码文件中的package行指定的

注意点

  1. main包表示一个可独立执行的程序,每个go应用程序都包含一个名为main的包。main包下每一个源码文件只能有一个main方法
  2. 包名应该是用小写字母
  3. main包不能在其他的源码文件中import,会提示
  1. import "xx/xx" is a program, not an importable package
  1. 一个包的不同源码文件中不能有同名的函数,否则会有编译期错误

变量声明和赋值

var

  1. var a = "咻咻" // 声明一个变量并初始化,不需要显示的指定类型,这一点和动态语言类似
  2. var b int // 没有初始化就为零值,或者"",或者为nil,以下的类型为nil
  3. var a *int
  4. var a []int
  5. var a map[string] int
  6. var a chan int
  7. var a func(string) int
  8. var a error // error 是接口

:=

  1. // 省略 var, 注意 := 左侧如果没有声明新的变量,就产生编译错误,格式:
  2. var intVal int
  3. intVal := 1 // 这时候会产生编译错误
  4. intVal,intVal1 := 1,2 // 此时不会产生编译错误,因为有声明新的变量,因为 := 是一个声明语句

var和 := 不同时使用,使用操作符 := 可以高效地创建一个新的变量,称之为初始化声明。因为其短小灵活,故而在局部变量的声明和初始化中主要使用短声明。var声明通常为那些跟初始化表达式类型不一致的局部变量保留的,或者用于后面才对变量赋值以及变量初始值不重要或者包级别变量声明。

例如

  1. var a float64 = 100 // 和初始值的类型不一致,所以需要用var声明

短变量声明不需要声明所有在左边的变量,如果一些变量在同一个词法块中声明了,那么对于那些变量,短声明的行为等同于赋值

  1. in, err := os.Open(infile)
  2. ...
  3. out, err := os.Open(outfile)
  4. 第一条语句声明了inerr。第二条语句仅仅声明了out,但向已经有的err变量赋予了新值

但是短变量声明至少声明一个新的变量,否则编译将无法通过。

还存在一种问题

  1. var cwd string
  2. func init() {
  3. cwd, err := os.Getwd()
  4. if err != nil {
  5. log.Fatalf("os.Getwd failed: %v", err)
  6. }
  7. }

这里 := 带来的问题是导致全局变量没有初始化成功,而是新创建了一个局部变量,通常的解法是

  1. var cwd string
  2. func init() {
  3. var err error
  4. cwd, err = os.Getwd()
  5. if err != nil {
  6. log.Fatalf("os.Getwd failed: %v", err)
  7. }
  8. }

多变量声明

  1. var b, c int = 1, 2 // 一次声明多个变量
  2. var x, y int
  3. var ( // 这种因式分解关键字的写法一般用于声明全局变量
  4. a int
  5. b bool
  6. )
  7. var c, d int = 1, 2
  8. var e, f = 123, "hello" // 一起赋值的时候可以类型不同
  9. //这种不带声明格式的只能在函数体中出现
  10. //g, h := 123, "hello"
  11. func main(){
  12. g, h := 123, "hello"
  13. println(x, y, a, b, c, d, e, f, g, h)
  14. }

变量互换

  1. a, b = b, a

抛弃变量

空白标识符 也被用于抛弃值,如值 5 在:, b = 5, 7 中被抛弃。_ 实际上是一个只写变量,你不能得到它的值。这样做是因为Go语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有的返回值。
并行赋值也被用于当一个函数返回多个返回值时,比如这里的 val 和错误 err 是通过调用 Func1 函数同时得到:val, err = Func1(var1)

用于获取函数的某几个值

  1. _,numb,strs := numbers()
  2. func numbers()(int,int,string){
  3. a , b , c := 1 , 2 , "str"
  4. return a,b,c
  5. }

new函数创建变量

表达式 new(T) 创建一个未命名的T类型变量,初始化为T类型的零值,并返回其地址

  1. p := new(int) // *int类型的p,指向未命名的变量
  2. fmt.Println(*p) // 输出0

使用new创建的变量和普通的局部变量没有什么不同,只是不需要引入和声明一个变量名称,通过new(T) 就可以直接在表达式中使用,提供了语法上的便利

  1. func newInt() *int {
  2. return new(int)
  3. }
  4. func newInt() *int {
  5. var dummy int
  6. return &dummy
  7. }

注意new是一个预声明的函数,不是一个关键字,所以它可以重定义为另外其他类型

  1. func delta(old, new int) int {
  2. a := new(int)
  3. return new - old
  4. }
  1. ./struct.go:9:10: cannot call non-function new (type int)

在delta函数中,就不可以使用new函数,否则会报以上的错误

作用域

包可见性

如果一个实体在函数中声明,它只在函数局部有效。如果声明在函数外,它将对包里面的所有源文件可见,感觉这个变量范围会比java宽泛很多。实体第一个字母的大小写决定其可见性是否跨包。如果名称以大写字母开头,那么它对包内包外都是可见,可访问的。

变量作用域

当编译器遇到一个名字的引用时,将从最内层的封闭词法块到全局块寻找其声明。如果没有找到,它会报“undeclared name”错错误;如果在内层和外层都存在这个声明,内层的会先被找到,覆盖外层的变量

  1. func main() {
  2. x := "hello!"
  3. for i := 0; i < len(x); i++ {
  4. x := x[i]
  5. if x != '!' {
  6. x := x + 'A' - 'a'
  7. fmt.Printf("%c", x)
  8. }
  9. }
  10. }
  1. func main() {
  2. x := "hello!"
  3. for _, x := range x {
  4. x := x + 'A' - 'a'
  5. fmt.Printf("%c", x)
  6. }
  7. }

这个函数中的三个x,每一个都在不同的词法块中声明:函数体,for语句,循环体。for循环创建了两个词法块:一个是循环体本身的显示块,以及一个隐式块,它包含了一个闭合结构,其中就有初始化语句中声明的变量,比如变量x或者i。所以以上两段代码的x变量其实互相之间是不干扰的。

和for循环一样,switch,if等语句也都会在条件字段创建出一个隐式词法块,生成一个隐式的作用域

  1. fname := "xiuxiu"
  2. if f, err := os.Open(fname); err != nil { // 编译错误:未使用f
  3. return err
  4. }
  5. f.Stat() // 编译错误:未定义f

原因就是因为if语句中创建了一个单独的作用域。

包级别变量

在包级别,声明的顺序和它们的作用域没有关系,所以一个声明可以引用它后面的声明,使我们可以声明递归或者相互递归的类型和函数

  1. var a = b
  2. var b = 1

命名规范