Go 程序存储在一个或多个以 .go 为后缀的文件里。每一个文件以 package 声明开头,表明文件属于哪个包。package 声明后面是 import 声明,然后是包级别的变量、常量、函数的声明,不区分顺序。
package main import "fmt" const a = "hello " func main() { var b = "world!" fmt.Println(a + b) var c = add(1, 2) fmt.Println(c) } func add(n1, n2 int) int { return n1 + n2 }
Go 中函数、变量、常量、类型、语句标签和包的命名规则:名称的开头是一个字母或下划线,后面可以跟任意数量的字符、数字和下划线,区分大小写。一般使用驼峰方式进行命名,如果文件名由多个部分组成,则使用下划线 _ 对进行分隔。
关键字只能用在语法允许的地方,不能作为名称:
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 |
预定义标识符可以在声明中使用,但有冲突的风险,最好不要使用:
append | bool | byte | cap | close | complex | complex64 | complex128 | uint16 |
---|---|---|---|---|---|---|---|---|
copy | false | float32 | float64 | imag | int | int8 | int16 | uint32 |
int32 | int64 | iota | len | make | new | nil | panic | uint64 |
println | real | recover | string | true | uint | uint8 | uintptr |
一个实体在函数中声明,它只在函数局部有效。如果声明在函数外,它对包里面的所有源文件可见。第一个字母的大小写决定其可见性是否跨包。如果名称以大写字母的开头,它是导出的,意味着它对包外是可见和可访问的,可以被自己包之外的其他程序所引用。
使用 var 声明并初始化一个变量。使用方式: var name type = expression。类型和表达式部分可以省略一个,但是不能都省略。如果类型省略,它的类型将由初始化表达式决定;如果表达式省略,其初始值对应于类型的零值——对于数字是 0,对于布尔值是 false,对于字符串是 "",对于接口和引用类型(slice、指针、map、通道、函数)是nil。对于一个像数组或结构体这样的复合类型,零值是其所有元素或成员的零值。 var声明通常是为那些跟初始化表达式类型不一致的局部变量保留的,或者用于后面才对变量赋值以及变量初始值不重要的情况。
package main import "fmt" func main() { var i, j, k int fmt.Println(i, j, k) var l, m, n = 3, "a", 3.12 fmt.Println(l, m, n) }
主要在局部变量的声明和初始化中使用。使用方式:name:= expression,name 的类型由 expression 的类型决定。
如果一些变量在同一个词法块中声明,那么对于那些变量,短声明的行为等同于赋值。短变量声明最少声明一个新变量,否则,代码编译将无法通过。
package main import "fmt" func main() { i, j, k := 3, "a", 3.12 fmt.Println(i, j, k) //i, j, k := 4, "a", 4.12 //编译不通过 }
指针的值是一个变量的地址。不是所有的值都有地址,但是所有的变量都有。使用指针,可以在无须知道变量名字的情况下,间接读取或更新变量的值。
如果声明为一个变量: var x int,表达式 &x(x的地址)表示获取一个指向整型变量的指针,它的类型是整型指针(*int)。如果值叫作 p,可以说 p 指向 x,或者 p 包含 x 的地址。p 指向的变量写成 *p。表达式 *p 获取变量的值,*p 代表一个变量,所以它也可以出现在赋值操作符左边,用于更新变量的值。
package main import "fmt" func main() { var a int = 10 var p *int fmt.Println(p) //初始值为 nil p = &a fmt.Println(p) //变量 a 的地址 *p = 20 fmt.Println(*p) //20 }
表达式new(T)创建一个未命名的 T 类型变量,初始化为 T 类型的零值,并返回其地址(地址类型为*T)。使用 new 创建的变量和取其地址的普通局部变量没有什么不同,只是不需要引入(和声明)一个虚拟的名字,通过new(T)就可以直接在表达式中使用。
package main import "fmt" func main() { p := new(int) fmt.Println(*p) *p = 6 fmt.Println(*p) }
生命周期指在程序执行过程中变量存在的时间段。包级别变量的生命周期是整个程序的执行时间。局部变量有一个动态的生命周期:每次执行声明语句时创建一个新的实体,变量一直生存到它变得不可访问,这时它占用的存储空间被回收。函数的参数和返回值也是局部变量,它们在其闭包函数被调用的时候创建。
赋值语句用来更新变量所指的值,它最简单的形式由赋值符 =,以及符号左边的变量和右边的表达式组成。
package main import "fmt" func main() { i := 10 i *= 10 fmt.Println(i) i++ fmt.Println(i) i-- fmt.Println(i) }
多重赋值允许几个变量一次性被赋值。
package main import "fmt" func main() { i, j := 10, 20 fmt.Println(i, j) i, j = j, i //交互两个变量 fmt.Println(i, j) }
赋值语句是显式形式的赋值,程序中很多地方的赋值是隐式的:一个函数调用隐式地将参数的值赋给对应参数的变量;一个return 语句隐式地将 return 操作数赋值给结果变量。复合类型的字面量表达式。 不管隐式还是显式赋值,如果左边的(变量)和右边的(值)类型相同,它就是合法的。nil可以被赋给任何接口变量或引用类型。
package main import "fmt" func main() { var s []string s[1] = "abc" // 隐式赋值 s[2] = "def" fmt.Println(s) }
type 声明定义一个新命名类型,它和某个已有类型使用同样的底层类型。使用方式:type name underlying-type。命名类型提供了一种方式来和底层类型经区分。如果两个类型具有相同的底层类型或二者都是指向相同底层类型变量的未命名指针类型,则二者是可以相互转换的。
package main import ( "fmt" "reflect" ) func main() { type t int var a t var b int //a = b // 编译不通过,类型不同,不能经行赋值操作 fmt.Println(reflect.TypeOf(a), reflect.TypeOf(b)) //a,b 为不同的类型,不能直接进行赋值操作 fmt.Println(a, b) a = 100 b = int(a) //但 a,b 拥有相同的底层数据结构,进行强制类型转换后可赋值 fmt.Println(a, b) }
在 Go 语言中包的作用和其他语言中的库或模块作用类似,用于支持模块化、封装、编译隔离和重用。一个包的源代码保存在一个或多个以 .go 结尾的文件中,它所在目录名的尾部就是包的导入路径,每一个文件的开头用 package 声明定义包的名称。package 声明前面紧挨着的文档注释对整个包进行描述。
在 Go 程序里,每一个包通过称为导入路径(import path)的唯一字符串来标识。导入声明可以给导入的包绑定一个短名字,用来在整个文件中引用包的内容。如果导入一个没有被引用的包,就会触发一个错误。
package main import ( a "fmt" //重命名包名 //"reflect" //未使用的包,编译不通过 ) func main() { s := "abc" a.Println(s) }
包的初始化从初始化包级别的变量开始,这些变量按照声明顺序初始化,在依赖已解析完毕的情况下,根据依赖的顺序进行。
package main import "fmt" var a = b + c //3.初始化 a var b = f() //2.调用 f 函数初始化 b var c = 1 //1.先初始化 c func main() { fmt.Println(a) } func f() int { return c + 1 }
如果包由多个 .go 文件组成,初始化按照编译器收到文件的顺序进行:go 工具会在调用编译器前将 .go 文件进行排序。任何文件可以包含任意数量的声明。
package main import "fmt" var a []int var b string func init() { a = []int{1, 2, 3} //利用 init 函数初始化全局变量 a } func init() { b = "hello world" //利用 init 函数初始化全局变量 b } func main() { fmt.Println(a) fmt.Println(b) }
声明将名字和程序实体关联起来,如一个函数或一个变量。声明的作用域是指用到声明时所声明名字的源代码段。声明的作用域是声明在程序文本中出现的区域,它是一个编译时属性。变量的生命周期是变量在程序执行期间能被程序的其他部分所引用的起止时间,它是一个运行时属性。
语法块(block)是由大括号围起来的一个语句序列,在语法块内部声明的变量对块外部不可见。包含了全部源代码的词法块,叫作全局块。每一个包,每一个文件,每一个for、if 和 switch 语句,以及 switch 和 select 语句中的每一个条件,都是写在一个词法块里的,显式写在大括号语法里的代码块也算是一个词法块。
像int、len和true等内置类型、函数或常量在全局块中声明并且对于整个程序可见。在包级别(就是在任何函数外)的声明,可以被同一个包里的任何文件引用。导入的包是文件级别的,可以在同一个文件内引用,但是不能在没有另一个 import 语句的前提下被同一个包中其他文件中的东西引用。