2.1 名称
名称开头通常为字母或下划线且区分大小写。
存在25个关键字只能用于合法处不能用作名称:
break | default | func | interface | select |
---|---|---|---|---|
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthrough | if | range | type |
continue | for | import | return | var |
此外还存在预声明的常量、类型和函数:
- 常量:true fales iota nil
- 类型:int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
float32 float64 complex64 complex128 - 函数:make len cap new append copy
close delete complex real imag panic recover
区别:
- 与传通语言相比浮点数改用float32/64指定而不用double
- char用byte代替,并添加rune
- 新增复数类型complex64/128
- 新增常量:计数器iota,只能在常量的const表达式中使用 这个作用很奇怪
- 常量nil
- 只有关键字不能重定义,预声明的常量、类型和函数可以,但应尽量避免
go中习惯短名称,且采用驼峰式命名通常名称越长越有意义。
开头字母大小写代表其可见性;大写字母开头即为可导出的 (代表包外可见)
2.2 声明
Go程序存储在n个以.go结尾的文件;每一个文件以package开头表明属于哪个包。
然后是多个import " “或import(” “,” “,” ")。
此之后是包级别的类型、变量、常量、函数的声明。
包级别变量是包内可见,变量大开头写是包外可见
2.3 变量
var name type = expression
type和expression可省其一;如果省略expression则初始化为0、fales、""或nil这样的“零值”。
对于一个像数组或结构体这样的复合类型,零值是其所有元素或成员的零值。
可以声明一个变量列表,并选择使用对应的表达式对其初始化。
如果忽略类型则允许声明多个不同类型的变量
var i, j, k int // int,int,int
var b, f, s = ture, 2.3, "four" // bool,float64,string
var f, err = os.open(file) // OS.open返回一个文件和一个错误
var a,b,c="" // × assignment mismatch: 3 variables but 1 value
var a,b,c string="" // × assignment mismatch: 3 variables but 1 value
2.3.1 短变量声明
短变量声明的可选形式可以用来声明和初始化局部变量;使用name:= expression 不能用于包级别变量
freq := rand.Float64() * 3.0
anim := gif.GIF{LoopCount: nframes}
phase := 0.0
i, j := 0, 1
var声明通常是为那些跟初始化表达式类型不一致的局部变量保留的,或者用于后面才对变量赋值以及变量初始值不重要的情况。
in, err := os.Open(infile)
out, err := os.Create(outfile) // 合理,第二个err是赋值
2.3.2 指针
与C语言相同,*p代表指向地址的变量,&x为对x变量取址。
uintptr可以保存一个地址但不是指针!
指针类型的零值是nil。如果p!=nil结果为true则说明P指向一个变量。
if err!=nil 就是这个nil
2.3.2 new函数
另一种创建变量的方式是使用内置的new函数表达式new(T)。
p := new(int) // *int类型的p,指向未命名int空间
使用new创建的变量和取址与局部变量没有什么不同,只是不需要引入一个虚拟名字。因此new只是一个语法上的便利,而不是基础概念。
这个规则有一个例外:两个变量的类型不携带任何信息且是零值,例如struct{}或[0]int;当前的实现里面他们有相同的地址。
因为最常见的未命名变量都是结构体类型它的语法比较复杂,所以new函数使用的相对较少。
2.3.4 变量的生命周期
包级别的变量的生命周期是整个程序的执行时间;局部变量是动态生命周期,直到变得不可访问时会被回收。
那么垃圾回收器如何知道一个变量是否应该被回收?说来话长,基本思路是每一个包级别的变量以及每一个当前执行函数的局部变量,可以作为追溯该变量的路径源头,通过指针和其他方式的引用可以找到变量。如果变量的路径不存在,那么变量变得不可访问,因此它不会影响任何其他的进程过程。
编译器可以选择使用栈或堆上来分配空间,但这个选择并不是基于vsr或new关键字来声明的。
上一节结束不说内置函数new吗?怎么到这儿变关键字了?
从书上的例子来看,变量分配于堆或栈取决于变量逃逸;即当函数结束后是否有其他地方持有内部变量。如果持有则分配到堆空间,否则分配到栈空间。
2.4 赋值
由‘=’组成。包括+=,-=,*=,/=,++,–
2.4.1 多重赋值
多重赋值允许几个变量同一时被赋值,如:
func swap(arr []int, i int, j int) {
arr[i], arr[j] = arr[j], arr[i]
}
More Effictive Go!!!
通常函数使用额外的返回值来指示一些错误情况例,如通过欧os.Open返回的error类型或者一个通常叫ok的布尔类型变量。我们会再后面章节中看到,这里有三个操作符也有类似行为。如果map查询、类型断言、或者通道接收动作出现两个结果的赋值语句中都会额外产生一个布尔结果。
v, ok = m[key]
v, ok = x.(T)
v, ok = <-ch
2.4.2 可赋值性
slice:medals := []string{"Red", "Green", "Blue"}
如果隐式的赋值可以写成如下这样:
medal[0] = "Red"
medal[1] = "Green"
medal[2] = "Blue"
map和通道的元素尽管不是普通变量但他们也遵守相似的隐式赋值。
不管是隐式赋值还是显示赋值,如果左边和右边类型相同它就是合法的。通俗的说,赋值只有在对于变量类型是可赋值的时才合法。
可赋值性: 对于已讨论过的类型规则很简单,类型必须精确匹配,nil可以被复制给任何接口变量或引用类型。常量有更灵活的可赋值性规则来规避显式的转换。
2.5 类型声明
type name underlying-type
type声明定义一个新的类型,它和某个已有类型使用同样的底层类型,通常出现在包级别,同样遵守首字母大写为【导出类型】。
如:
type Aint int
type Bint int
虽然Aint和Bint底层同为int类型,但此时Aint和Bint已经成为两种独立类型,两者之间不可运算不可比较。但新声明的类型都可以与其底层依赖类型做比较、运算
2.5 包和文件
每一个包提供一个独立的命名空间,这样在不同包中的相同命名可以通过不同命名空间来指定。
如:image.Decode
和utf16.Decode
。
package声明前紧压着文档注释,对整个包进行描述。习惯上在开头用一句话对包进行总结性的描述。
每个包里只有一个文件应该包含该包的文档注释。扩展的文档注释通常放在一个文件中,按惯例叫做doc.go。
2.6.1 导入
在go程序里每一个包通过称为导入路径的唯一标识字符串来标识,它们出现在诸如 “gopl.io/ch2/tempconv” 之类的import声明中。语言的规范并没有定义哪些字符串从哪里来,以及它们的含义,这依赖于工具来解释。当使用go工具时,一个导入路径标注一个目录,目录中包含构成包的一个或多个go源文件。除了导入路径之外,每一个包还有一个包名。它以短名字的形式(且不必是唯一的)出现在包的声明中。按约定,包名匹配导入中最后一段,这样可以方便预测gopl.io/ch2/tempconv的包名是tempconv。
如果导入一个没有被引用的包,就会触发一个错误。
2.6.2 包初始化
包的初始化从初始化包的级别开始,这些变量按照声明顺序初始化。在依赖已解析完毕的情况下根据依赖顺序进行。
var a = b + c // 3.初始化a
var b = f() // 2.调用f()初始化b
var c = 1 // 1.初始化为1
只如果包由多个.go文件组成,初始化按照编译器收到的文件顺序进行:go工具会在调用编译器前将.go文件进行排序。
对于包级别的每一个变量,生命周期从其被初始化开始。但对于其他变量,比如数据表,初始化表达式不是简单的设置它的初始值。这种情况下init函数的机制会比较简单。
func init() {}
init函数不能被调用或引用,在程序启动时init函数按照他的声明顺序自动执行。
2.7 作用域
作用域指用到声明时所需声明名字的源代码段,是一个编译时属性;而生命周期是运行时属性。
语法块是由大括号围起来的语句序列,在语法块内部的声明变量对外部不可见。但有些语法块不会显示的出现在大括号中,这时我们可以将定义的概念推广到词法块。
全局块:包含了全部源码的词法块。
每一个包文件,每一个for if switch语句以及switch和select语句中的每一个条件,都是在一个词法块里的,这也包括显示大括号内的。
一个程序可以包含多个同名的声明,前提是它们在不同词法块中。但不要滥用,否则可能会影响代码。
当内层词法块和外层词法块声明了同一个变量,这时内层词法块会在内层覆盖外层的词法块。
func main() {
x := "hello!"
for i := 0; i < len(x); i++ {
x := x[i]
if x != '!' {
x := x + 'A' - 'a'
fmt.Printf("%c", x) // "HELLO" 每次迭代一个字母
}
}
}
表达式x[i]和x:=x+‘A’-'a’都在引用外层的x。
在包级别声明顺序和作用域没有关系。所以声明可以引用自己或者在其后面的其他声明,使我们可以声明递归或者相互递归类型的函数。如果常量或变量声明引用他自己,编译器会报错。
if f, err := os.Open(fname); err != nil { // 编译错误:未使用f
return err
}
f.Stat() // 编译错误:未定义f
f.Close() // 编译错误:未定义f
通常做法是在判断条件前声明f或在同一词法块内使用后两条语句。
获取当前工作目录然后把它保存在一个包级别的变量里。
考虑如下程序:
var cwd string
func init() {
cwd, err := os.Getwd() // 编译错误:未使用cwd
if err != nil {
log.Fatalf("%v", err)
}
}
cwd和err在init函数模块中都未声明,所以:=语句将他们声明为局部变量。
此时如果在if词法块下方init词法块内部添加log.Fatalf("%s", cwd)
就会通过编译。
但此时的cwd变量依然未初始化。
这就造成了一个bug,且违背了将普通日志输出到全局变量cwd的目标。
这时我们可以在init函数内部再次声明一个err变量并改内部定义为赋值。
var cwd string
func init() {
var err error
cwd, err = os.Getwd()
if err != nil {
log.Fatalf("%v", err)
}
}
标签:变量,err,int,程序结构,Programming,类型,Go,声明,赋值
From: https://blog.csdn.net/Cokie12345_/article/details/136790086