出现原因:需要一款既有高效的执行速度,又有较快的编译速度和开发速度
变量
-
单个变量的声明语法:
var 变量名 变量类型 -
多个变量的声明语法:
var ( 变量1 类型 变量2 类型 ) -
自动推导写法(只能用在函数内部,且必须显式初始化):
变量名 := 初始化值 -
变量交换:
a,b = b,a -
匿名变量:用下划线
_表示匿名变量,相当于一个占位符,任何赋给它的值都会被抛弃适用情景:函数有多个返回值,但是程序只需要其中某个或某些返回值,其他返回值就能用匿名变量接收
下划线
_在go语言中的作用:- 在
import中,import _ 包名可以仅仅调用该包中的init()函数,而不导入该包中的其他函数 - 匿名变量
- 在
常量
-
常量的数据类型只能是布尔型、数字型(整数、浮点数、复数)和字符串
-
语法:
const 常量名 [类型],类型可以不写(通过初始化的类型自动推导得到常量类型) -
iota:一个特殊常量(常量计数器)-
每次遇到
const关键字,iota就重置为0 -
iota可以理解为在const语句块内部的常量索引 -
常量语句块中,如果某个常量没有初始值,则默认与上行一致,于是可以有如下简写:
const ( a = iota b c ) //b和c默认都为iota,又由于iota是语句块内部的常量索引,所以a=0, b=1, c=2
-
输出
fmt.Printf的格式化占位符%p:打印内存地址%T:打印变量类型%t:打印布尔类型%v:相应值的默认格式%+v:打印结构体时,会添加字段名%#v:相应的go语法表示
数据类型
数字类型
- 整型:
uint8,uint16,uint32,uint64,int8,int16,int32,int64 - 浮点型:
float32,float64 - 复数:
complex64,complex128
字符与字符串
- 字符用单引号表示,实际上是
int32类型 - 字符串用双引号表示
- 字符串可以用
+连接
数据类型转换
go语言不存在隐式类型转换,必须用显式类型转换,语法为:数据类型(变量)
数组(Array)
数组是值类型,而不是引用类型
-
定义
var arr [5]int = [5]int{1,2,3} //没初始化的默认为0 var arr = [5]int{1,2,3} //当然也可以自动推导 arr := [5]int{1,2,3} //局部范围内还可以更加简化 -
初始化(以局部变量为例)
a := [3]int{1, 2} // 未初始化元素值为 0。 b := [...]int{1, 2, 3, 4} // 通过初始化值确定数组长度。 c := [5]int{2: 100, 4: 200} // 使用索引号初始化元素。
切片(Slice)
切片是基于一个底层数组的。基于同一个数组的任何切片,都是在同一个内存地址上进行访问和修改的
slice是结构体包装的,本质上是一个结构体{ pointer, len, cap }
指针
Go语言中的指针不能进行偏移和运算,是安全指针。
Map
- 定义:
map[KeyType]ValueType - 判断某个key是否存在:
value, ok := map[key] - map是由指针包装的
结构体
Go语言中通过结构体来实现面向对象
类型定义和类型别名
//类型定义
type NewInt int //编译完成后,该类型还会一直存在
//类型别名
type MyInt = int //MyInt类型只会在代码中存在,编译完成时并不会有MyInt类型
结构体初始化
- 使用键值对初始化(最后一行记得也要加逗号)
- 使用值列表初始化
注意:
- 使用值列表初始化时,必须初始化所有字段,且要按声明的顺序初始化
- 两种初始化方式不能混用
调用结构体的字段
不论是结构体的值还是指针,都可以用.取某个字段,这是go语言的一个语法糖
方法
- 接收者的类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法
- 非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法
匿名字段
-
结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段
-
匿名字段默认采用类型名作为字段名,结构体要求字段名称必须唯一,因此一个结构体中同种类型的匿名字段只能有一个
-
结构体内可以嵌套另一个结构体,且可以是匿名的。如果想访问嵌套的匿名结构体内的字段,有两种方法:
- 匿名结构体类型名.字段名
- 直接访问匿名结构体的字段名(原理:当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找)
嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段
“继承”
通过嵌套匿名结构体(这样就可以通过直接访问字段名的方式访问被嵌套的结构体的字段)实现继承
可见性
结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)
结构体标签(Tag)
-
Tag是结构体的元信息,可以在运行的时候通过反射的机制读取出来
-
Tag在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下
`key1:"value1" key2:"value2"`
内存分配
-
new:func new(Type) *Typenew用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针
-
make:func make(t Type, size ...IntegerType) Typemake只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身
运算符
位运算符
&^:位清空。对于a &^ b,每个对应位上,b为0,则取a的值;b为1,则结果为0
流程控制
条件语句if
- go语言不支持三元操作符
a > b ? a : b - 注意大括号、
else if和else的位置,不能换行 if语句后可以跟初始化语句,定义代码块局部变量
条件语句switch
-
Go语言的switch分支表达式可以是任意类型,不限于常量。可省略 break,默认自动终止
-
如果switch关键字后没有表达式,它会匹配true
-
当匹配成功后,默认自动终止,但也可以使用
fallthrough强制执行后面的case代码 -
switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型
switch x.(type){ case type: statement(s) case type: statement(s) /* 你可以定义任意个数的case */ default: /* 可选 */ statement(s) }
条件语句select
-
每个case都必须是一个通信
-
select会随机执行一个可运行的case
-
如果没有default语句,且没有case可运行,它将阻塞,直到有case可运行
-
如果有default语句,则会在没有通信可执行的时候执行default的代码
循环语句for
1. 分号形式
跟一般的for循环用法基本一致
2. condition形式
for condition {
}
这种形式与一般的while循环类似,如果condition不写,则默认为true
3. range形式
- 类似迭代器操作,返回 (索引, 值) 或 (键, 值)
- for 循环的 range 格式可以对 slice、map、数组、字符串、channel等进行迭代循环
- range 会复制对象,即range是在副本中进行遍历的(对于slice这种引用类型,复制的是
struct slice { pointer, len, cap })
循环控制
goto,continue,break- 三个控制语句都能配合标签(label)使用
- 标签格式为
标签名:,用于标记某个循环体。与控制语句配合使用,可以做到跳出多层循环
函数
- 无需声明原型
- 支持不定变参
- 支持多返回值
- 支持命名返回参数
- 支持匿名函数和闭包
- 函数也是一种类型,一个函数可以赋值给变量
- 不支持嵌套 (nested) 一个包不能有两个名字一样的函数
- 不支持重载 (overload)
- 不支持默认参数 (default parameter)
可变参数
- 可变参数本质上就是 slice。只能有一个,且必须是最后一个
- 格式:
args ...类型名称 - 使用 slice 对象做变参时,必须展开。
slice...
返回值
-
返回值可以被命名,并且就像在函数体开头声明的变量那样使用
-
命名返回参数可看做与形参类似的局部变量,最后由 return 隐式返回。但如果被同名局部变量遮蔽,就需要显式返回。
-
显式 return 返回前,会先修改命名返回参数,例如:
func add(x, y int) (z int) { defer func() { println(z) }() z = x + y return z + 200 // 执行顺序: (z = z + 200) -> (call defer) -> (return) }
闭包
- 闭包是由函数及其相关引用环境组合而成的实体(即:闭包=函数+引用环境)
- 闭包(Closure),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外
延迟调用(defer)
1. 关键字 defer 用于注册延迟调用。
2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。
3. 多个defer语句,按先进后出的方式执行。
4. defer语句中的变量,在defer声明时就决定了。
- 当defer后面的语句是一个闭包,在return前会调用这个闭包,并且闭包中的变量值为当前值,而不是定义defer时的值
- defer后面的语句在执行的时候,函数调用的参数会被保存起来,但是不执行。也就是复制了一份。但是对于方法中的this指针,go语言并不会复制一份,而是会使用return前那个时刻this指针指向的对象
异常处理
- defer会在两种情况下执行:1. 函数正常执行完毕后,返回前;2. 发生(抛出)异常时
- 延迟调用中引发的错误,可被后续延迟调用捕获,但仅最后一个错误可被捕获
- 可以用
panic()抛出异常,也可以用errors.New()定义一个error并通过函数返回值返回
方法
定义
func (recevier type) methodName(参数列表)(返回值列表){}
//参数和返回值可以省略
表达式
- method value:绑定实例,隐式传参
- method expression:必须显式传参
package main
import "fmt"
type User struct {
id int
name string
}
func (self *User) Test() {
fmt.Println(self)
}
func main() {
u := User{1, "Tom"}
mValue := u.Test
mValue() // 隐式传递 receiver
mExpression := (*User).Test
mExpression(&u) // 显式传递 receiver
}
- 需要注意,method value 会复制 receiver。
package main
import "fmt"
type User struct {
id int
name string
}
func (self User) Test() {
fmt.Println(self)
}
func main() {
u := User{1, "Tom"}
mValue := u.Test // 立即复制 receiver,因为不是指针类型,不受后续修改影响。
u.id, u.name = 2, "Jack"
u.Test()
mValue()
}
//输出结果:
//{2 Jack}
//{1 Tom}
接口
接口是一种类型
定义
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
值接收者和指针接收者实现接口的区别
- 使用值接收者实现接口之后,不管是结构体还是结构体指针类型的变量都可以赋值给接口变量。
- 使用指针接收者实现接口之后,只能将结构体指针类型的变量赋值给接口变量
注意事项
-
一个接口的方法,不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其他类型或者结构体来实现
-
接口与接口间可以通过嵌套创造出新的接口
-
空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。空接口类型的变量可以存储任意类型的变量。
空接口的作用:
- 接收任意类型的函数参数
- 可以保存任意值的字典
-
类型断言
x.(T) //x:表示类型为interface{}的变量 //T:表示断言x可能是的类型。