代码块和作用域:作用域简单来说就是变量作用的范围,变量在哪些函数哪些范围可以使用,而在其他部分不可以,要用得重新定义。一个程序实体的作用域总是会被限制在某个代码块中,而这个作用域最大的用处就是对程序实体的访问权限的控制。对”高内聚,低耦合“这种程序设计思想的实践恰恰可以从这里开始
下面的命令源码文件有四个代码块,它们是:全域代码块,main包代表的代码块,main函数代表的代码块,在main函数中的一个用花括号包起来的代码块
package main
import "fmt"
var block = "package"
func main() {
block := "function"
{
block := "inner"
fmt.Printf("The block is %s.\n", block)
}
fmt.Printf("The block is %s.\n", block)
}
那么代码能通过编译吗,答案是肯定能的,运行后打印出的内容是:
The block is inner
The block is function
对于不同的代码块来说,其中的变量重名没什么大不了,照样可以通过编译 ,即便这些代码块有着直接的嵌套关系也是如此
这样规定方便且合理,但同时也引来另一个问题:我们引用变量的时候到底用的是哪一个?
先来看一下go语言中查找变量的过程(同样适用于任何程序实体)
1.代码引用变量时总会最优先查找当前代码块中(不包含任何子代码块)的那个变量
2.如果当前代码块中没有声明以此为名的变量,那么程序会沿着代码块的嵌套关系,一层一层的查找
3.一般情况下,程序会一直查到当前代码包代表的那层代码块。如果仍然找不到,那么go语言的编译器就会报错了
再考虑是否会从导入的其他包中查找
1.如果我们导入了其他包,那么引用其中的程序实体时需要以限定符为前缀的。所以程序在找代表变量的那个未加限定符的名字的时候,是不会去被导入的那些代码包查找的
2.有个特殊情况,如果把导入包写成import .XXX的形式(注意中间的那个“.”),那么就会让这个“XXX"包中公开的程序实体被当前源码文件的代码视为当前代码包中的程序实体(比如代码包中有import .fmt,那么我们在引用fmt.Printf函数的时候直接用Printf)
明白上述过程后再看示例,在示例中,main函数中的block变量屏蔽了全域的block(package),在main函数中的inner block,又不会影响main函数中用短变量定义的变量block。而在导入包中也不存在import .XXX形式,自然也无需考虑会从导入包查找block变量。
1.重声明的变量一定是同在某一个代码块内。而可重名变量指的正是在多个代码块之间的由相同的标识符代表的
2.重声明是对同一个变量的多次声明,变量只有一个。而可重名的变量是有多个的
3.重声明的变量类型必须始终一致的,而可重名变量的类型是可以任意的
4.可重名变量所在的代码块会屏蔽与它直接或者间接的子代码块的同名变量,这一情况在变量重声明场景下不会发生
除了一些import常用的几种方式,但是还有一些特殊的import,让很多新手很费解,下面我们来一一讲解一下
到底是怎么一回事
1. 点操作
我们有时候会看到如下的方式导入包
import(
. "fmt"
)
这个点操作的含义就是这个包导入之后在你调用这个包的函数时,你可以省略前缀的包名,也就是前面你调
用的fmt.Println("hello world")可以省略的写成Println("hello world")
2. 别名操作
别名操作顾名思义我们可以把包命名成另一个我们用起来容易记忆的名字
import(
f "fmt"
)
别名操作的话调用包函数时前缀变成了我们的前缀,即f.Println("hello world")
3. _操作
这个操作经常是让很多人费解的一个操作符,请看下面这个import
import (
"database/sql"
_ "github.com/ziutek/mymysql/godrv"
)
_操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。
通过import .XXX这种方式导入的代码包中的变量与当前代码包中的变量重名了,那么Go语言是会把它们当作“可重名变量”看待还是报错呢?
答:会报redeclared。
采用import . xxx如文章所说,基本上就会认为引入的代码包的代码,如同在本包中一样,那作用域其实是同一个,自然不允许重复声明。