Go: 基本语法
Go基本语法
包管理
package关键字表示当前文件属于哪个包,主函数文件想要当作入口文件,必须声明package main,包名设计和路径不同,不像java通过.分隔,而是单纯的一个字段表示包名,但仍然是一个目录对应一个包,不会进行递归扫描不强制目录名和包名相同,导入后使用的是
package后声明的字段而不是目录名,但一般目录名和包名相同import是导入其它包的关键字,这里引入的是用/分隔的包含包所在网址或本地项目路径的绝对包名因此如果导入多个不同路径的同时,它们包含一个相同的包名,会发生冲突,需要重命名
import可以通过()对导入进行分组,也可以重命名:1
2
3
4
5
6
7
8
9
10
11package main
import (
"fmt"
strs "strings"
)
func main() {
fmt.Print("Hello world!")
strs.Split("a,b,c", ",")
}import起别名时可以用_表示匿名引入,只加载其中的init函数1
import _ "mysql-driver-go"
内部包:
Go约定,在命名为internal包内的函数无法被外部包导入可以导入内部包的文件,只有该内部包直接父目录的所有子文件
数据类型
值类型
整型:
uint8/16/32/64:无符号整k型int8/16/32/64uint和int:至少32位的整型byte:等价于uint8uintptr:保证足够大以至于能存储任何指针
浮点型:
float32/64复数:
complex64/128,本质为两个float32/64的封装字符型:
byte/uint8:可存储ASCII码rune:等价于int32,可存储utf8字符,用rune来在语义上区分整型和字符,也可表示一个Unicodestring:字符序列,可视为一个只读[]byte,它同样可以表示utf8(编码为byte序列后)字符串string是一个不可变类型
布尔型:
bool数组类型:
[n]Type,其中n必须是编译时可确定的数组也是一种值类型
结构体:
struct {}自定义类类型,是值类型指针类型:
*Type,指针类型的对象可通过&作用于对象来创建,同时可以不使用解引用符访问值的属性和方法因此解引用符
*更多作用于右值,此时解出来的是副本通过使用指针,可以实现类似引用类型的效果
引用类型
切片类型:
[]Type,是可变长的序列映射类型:
map[keyType]valueType方法:
func(T...) R自定义方法通道类型:
Go擅于多线程场景,通道是Go提供的线程间通信手段chan Type:双向通道,chan表明它是一个通道,Type是通道传输的数据的类型chan<- Type:只写通道<-chan Type:只读通道
接口:
interface {}自定义接口类型任意类型(别名):
any = interface{}可比较类型(别名):
comparable = interface{ comparable }comparable应该作为函数参数的类型以限制形参,而不是用来定义一个变量所有基本数据类型、以及由可比较类型组成的数组、以及由可比较类型组成的
struct都是comparable的Go的引用类型只有切片、映射、方法、通道、接口五种,它们像指针那样共享同一份堆中的数据,拷贝的是地址使用类型断言:
obj.(type)获取obj的类型,obj.(Type)返回(value, ok)
字面量
true和false整型可使用
_分割加强可读性iota:是预定义的字面量,只在const块下生效,表示自增的自然数序列,即0-idxconst组的一个特性是:后续没有显式赋值的常量会延用最近使用的表达式,因此只需要第一项指定为iota,后续可默认生成1
2
3
4
5
6
7
8const (
a = iota // 0
b // 延用 iota, 为1
c = iota * 2 // 2 * 2
d // 延用 iota * 2, 为3 * 2
e = 1
f = iota // 5
)实际上,
iota并不算很好的命名,目前只有Cpp与APL以及Go在使用一种场景是:
1
2
3
4
5const (
a = 1 << iota
b // 1 << 1
c // 1 << 2
)nil:空值,它不是类型,而是用于初始化指针类型、内置引用类型的零值数组/切片字面量:一维数组如下,高维数组类似
1
2
3
4a := [5]int32{1, 2, 3, 4, 5}
// 可以通过 idx:value 局部赋值
// 0-idx
b := [5]int32{1: 2, 4: 5}结构体字面量:结构体字面量有若干种写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27// 一个类型的字面量是紧跟该类型后的大括号
var a AStruct = AStruct{
1, // 如果不带字段名, 则按顺序赋值, 且必须全部手动赋值
2, // 所有属性值需要紧跟 ','
}
// 可以指定字段名, 此时顺序无影响
// 此时会将所有未手动赋值的属性赋为零值
var b BStruct = BStruct{
a: 1,
}
// 匿名结构体习惯使用 := 赋值
a := struct {
a int32
b int32
}{
1,
2,
}
// 允许混合声明, 指定字段必须放在最后
// 但这是不好的代码习惯
b := struct {
a int32
b int32
}{
1,
a: 2,
}new()与make():用于值类型和引用类型的内存分配new(Type) *Type:new只能用于创建值类型,且使用零值初始化,返回指针不常用,更常用的是先创建一个值类型的值然后取地址
make(Type, ...int) Type:make专门用于为映射、切片、通道这三个内置引用类型分配内存,并返回它们的值引用这三种类型对应的
make函数格式不同,根据第一个参数解析后续的参数
声明语句
变量声明
Go是静态强类型语言,虽然可以使用var与const,但是最终需要编译时确定类型,即指定类型或指定初始值必须含一根据字面量动态推断类型:
1
2
3a := 1
const b = 1 // const变量只能进行一次初始化
c := 1 // 等价于 var c = 1短变量赋值语句只能在函数作用域内使用
指定数据类型:
Go是类型后置的,如果使用var声明变量不附带初始值,则必须指定类型1
2
3var a int
const b int = 1类型后置能带来一系列优点,首先是可以抛弃
void而不影响观感,最重要的是方法签名和函数类型易于阅读变量分组:通过
()对变量进行分组能提高可读性1
2
3
4
5
6
7
8
9var (
a int32
b float32
)
const (
a = 1
b
)多返回值:
Go支持多返回值以及函数多返回值1
2
3
4
5a, b := 1, 2
a, b := b, a // swap
a, b = func() (int32, int32) {
return 1, 2
}()
类型声明
类型声明(创建新类型)使用
type TypeName OriginTypeName,基于其它类型创建新类型(两者类型的头信息不同)所有在之前数据类型小节讲述的类型以及自定义的类型都可以作为
OriginTypeName与
var和const类似,type关键字也可以使用type ()分组声明同一个
type分组的类型声明,遵循先后顺序,且在前语句声明的类型可以在后语句立刻使用,需要注意循环引用Go的引用类型不像Java的引用类型,其更相似的是指针类型,使用指针类型可以实现循环引用示例-数组:
type IntArr5 [5]int32示例-切片:
type IntSlice []int32示例-结构体:结构体是一系列属性的集合
1
2
3
4type StructName struct {
a int64
b int32
}示例-接口:接口是一系列方法的集合
1
2
3
4type InterfaceName interface {
A() int
B(a int32)
}示例-通道:
1
2
3
4
5type (
DEIntChan chan int32
RIntChan <-chan int32
WIntChan chan<- int32
)示例-函数:
1
type RetIntFunc func() int32
类型别名
类型别名使用
type TypeName = OriginTypeName,使用=后,这个类型是类型别名,两者等价别名,意味着
OriginTypeName必须已经声明,而不能是匿名的(如匿名结构体、匿名接口),但函数类型是允许的类型别名可以和类型声明放进同一个
type分组里1
2
3
4
5
6
7
8
9
10type (
RetIntFunc = func() int32 // ok
StrcutA = struct { // error
a int32
}
StructB struct {
a int32
}
StructC = StructB
)同一个
type分组里,类型别名的位置没有任何影响,即使其引用的原类型在同一分组内的后面定义1
2
3
4
5type (
A = B
B int32
)
运算与表达式
运算
- 大多数运算符和其它语言类似
- 不含前缀
++或前缀--,且后缀++和后缀--都不具有返回值,即它们变成了原子操作 - 不支持三元表达式
- 条件表达式不需要小括号括住,且条件表达式必须返回布尔值,其它类型不会自动隐式地转换为布尔值
条件控制
if表达式与其它语言类似switch表达式则有较大不同:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// switch 后可跟简单的计算语句
// 特殊的是这些语句需要以 ; 结尾
switch t := nbr; {
// case 语句后面跟条件表达式
case t == 1:
stmt
// 和其它语言不同, 默认情况下,
// 每个条件块执行结束结束即退出(默认break)
// 如果想继续执行下一分支则应使用 fallthrough
case t == 2:
stmt
fallthrough
default:
default_stmt
}虽然支持
label和goto语句,但是尽量不用goto,倒是可以结合break和continue使用标签
循环控制
没有
while表达式,全部使用for:1
2
3
4
5
6
7
8
9for init_stmt; expr; post_stmt {
for_stmt
}
for expr {
while_stmt
}
for { // 不带表达式默认为 true
while_true_stmt
}for-range:1
2
3for idx, val := range iterable {
stmt
}不支持省略
if、for语句的大括号不支持左大括号换行
大部分风格可以由格式化工具如
goimport和gofumpt决定,不需要过多注意