基础语法
package 包
Go 语言中的包是一种封装代码的机制。Go的每一个文件都是属于一个包的。一个包可以包含多个 Go 源文件,但只能存在于一个目录中。每个包都有一个唯一的名称,用于在代码中引用和导入。
包的创建和使用
Go语言中存在两种包、一种是可执行程序的包、一种是类库函数的包。可执行程序的包:必须以main作为包名,编译完成后会生成一个可执行文件。静态库的包:包名无要求,编译之后会生成一个.a为后缀的文件,无法执行,只能被可执行包引用。
这里有一个test例子,代码结构如下。创建一个包名为hello的静态库。想要在main包中的test.go文件中使用hello包中的标识符,需要先用import module name/package name引用hello包。其中module name在go.mod中设置。
包的可见性
如果想在一个包中引用另外一个包里的标识符(如等map、结构体、变量、常量、类型、函数等)时,该标识符必须是public的。为了使将标识符的属性为public,需要让标识的首字母大写。
包的命名
源码文件的 package 可以和所在的⽬录不一致,同一个目录下,源码文件的包名要保持一致。如果你用一个名字导入多个包,这时使用包别名避免冲突。
变量和常量
变量
声明:Go语言中的变量需要声明后才能使用,并且声明后必须使用。全局变量只能通过 var 进行声明,全局变量首字母必须大写。
var 变量名 变量类型
可以批量进行声明
var (
a string
b bool
c float32
)
变量的值:整型和浮点型变量的默认值为0。 字符串变量的默认值为空字符串。 布尔型变量默认为false。 切片、函数、指针变量的默认为nil。
给变量赋值有如下方法。= 是赋值,变量必须事先被声明; := 是声明变量并赋值。
var 变量名 类型 = 表达式
var 变量名 = 表达式 //Go 可以在初始化的时候自动判断变量类型
变量名 := 表达式
可以批量进行赋值。
var b, c int = 1, 2
d, e, f := 1, 2, "I'm F"
常量
常量在编译时就会确定,之后无法修改。常量声明和变量相似,但是声明时必须要有值。可以是字符、字符串、布尔值或数值类型(整数、浮点数)。其声明方法如下。
const 变量名 类型 = 表达式
常见数据类型
数组Array
数组是同一种数据类型的固定长度的序列。数组长度必须是常量,且是类型的组成部分。一旦定义,长度无法改变。
var 变量名 [长度]类型 //声明
变量名 := [长度]类型{表达式,...} //声明加赋值
遍历数组
for 索引, 值 := range 变量名 {
...
}
切片Slice
切片:切片是数组的一个引用,切片的长度可以改变。切片声明方法如下:
var 变量名 []类型
变量名 := []类型{表达式,...} //{}可为空
变量名 := make([]类型, 长度, 容量)
当使用append往slice里面加元素时,长度大于容量,这时切片就会发生扩容。扩容原理:
- 当需要的容量cap大于两倍旧容量doublecap时,我们申请的新容量就是需要的容量
- 当需要的容量cap小于两倍旧容量doublecap时,判断是否旧切片的长度小于1024。如果小于1024,那么newcap=两倍旧cap。如果大于1024,,会反复地增加25%,直到新容量newcap超过所需要的容量cap。
指针
Go语言中的函数传参都是值拷贝。若使用指针传递数据,无须拷贝数据。Go指针为类型指针,不能进行偏移和运算。&取地址和*根据地址取值。
ptr := &v // ptr 就是指向变量v的指针,*prt指向v的值
v类型为T,则prt类型为*T
Map
Go语言中的map是引用类型,必须初始化才能使用。map定义语法为map[keyType]valueType。map类型的变量默认初始值为nil,需要使用make()函数来分配内存,这样其有一个非 0 的指针。使用make时,就算指定了len,也是不起作用的。
变量名 := make(map[键的类型]值的类型) //初始化map
变量名[键] = 值 //设置键值对
值, exists := 变量名[键] //取值or判断。exists为bool类型
delete(变量名, 键) //删除键值对。key不存在时,不会报错
for k, v= range 变量名 { //map的遍历
...
}
结构体
type 类型名 struct {
字段名 字段类型
字段名 字段类型
…
}
var 变量名 struct{字段名 字段类型; …} //匿名结构体
go语言中通过结构体的内嵌再配合接口比面向对象具有更高的扩展性和灵活性。
流程控制
条件语句
if
if or if...else... 。和其他基本类似。但是省略条件表达式括号。
if 条件表达式 {
// 条件为真运行
} else {
// 条件为假运行
}
switch
和其他基本类似。但是switch的case不必是常量或整数,执行的过程从上至下,直到找到匹配项。Go里面switch默认相当于每个case最后带有break。
匹配成功后不会自动向下执行其他case,而是跳出整个switch。但是可以使用fallthrough强制执行后面的case代码。遇到一个fallthrough时,向下执行下一个case。
switch x.(type){ //switch语句还可以被用于type-switch来判断某个interface变量中实际存储的变量类型。
case type:
statement(s)
default: /* 可选 */
statement(s)
}
select
用来监听和channel有关的IO操作,当IO操作发生时,触发相应的case动作。select 语句类似于 switch 语句,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行。
循环语句 for
Go 只有一种循环结构——for 循环。和其他基本类似。但是省略括号。
for init; condition; post { }
for condition { }
for { } //无限循环
函数
函数声明包含一个函数名,参数列表, 返回值列表和函数体。如果函数没有返回值,则返回列表可以省略。
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
匿名函数即为没有名字的函数
res := func (n1 int, n2 int) int { //方法一
return n1 + n2
}(10,10) //传入参数,res结果为20
add := func (n1 int, n2 int) int { //方法二
return n1 + n2
}
res := add(10,10)
延迟调用-defer
defer用于注册延迟调用。这些调用直到 return 跳转前才被执行,一般用于资源的释放和异常的捕捉。defer常见用途:1. 关闭文件句柄 2. 锁资源释放 3. 数据库连接释放
当有多个 defer 行为被注册时,它们会以逆序执行(类似栈,即后进先出), 相当于开辟了一个延时调用栈。
延迟函数的参数在 defer 语句出现时就已经确定下来了。
func deferFuncTest() {
i := 1defer
fmt.Println(i) //1
i++
return
}
func main() {
deferFuncTest()
}
defer函数可以操作主函数的返回值
func deferFuncTest() (result int) {
i := 1
defer func() {
result++
}()
fmt.Println(i) //1
return i
}
func main() {
res := deferFuncTest()
fmt.Println(res) //2
}