1、变量声明关键字:var
var a, b int
//带初始值变量声明
var a, b int = 1, 2
//如果带上初始值,变量类型可省略
var c, python, java = 1, true, "no!"
//多个不同类型变量声明,可以用括号括起来
var (
ToBe bool = false
MaxInt uint64 = 1<<8 - 1
z complex128 = cmplx.Sqrt(-5 + 12i)
)
注:
(1)只有在函数中声明变量才能省略掉var关键词,如使用 k := 5声明一个整形变量,相当于一个语法trick;
(2)变量如果声明时没有显式给出初始值,则会被赋予默认的零值:
数字类型被赋予0, 布尔类型被赋予false,字符串类型被赋予"",interface和引用类型(slice, point er, map, channel, function)被赋予nil;
(3)package级别变量在main函数执行前初始化,而局部变量在函数执行到是才初始化
2、基本的数据类型:
bool, string, int, int8, int32, uint32, float32,float64
3、类型转换:
在不同类型变量直接的赋值需要进行显式类型转换:
var i int = 1
var j float64 = float64(i)
4、类型推断:
当声明一个变量时,如果没有显式指明数据类型,则编译器会从初始化值推断其类型:
func main() {
v := 2 + 3i
j := v
fmt.Println("v is of type %T\n", v)
fmt.Println("j is of type %T\n", j)
}
5、作用域(scope):
(1)如果一个entity在函数内声明,则该entity的作用域仅为该函数;如果在函数外声明,则对于该package下的所有文件可见;
(2)entity的首字母大小写决定了其对于其他package的可见性,如果首字母大写,则为被导,即其他模块可以导入和引用该entity;
(3)package名称全部为小写字母
场景a:test1.go里面定义了func demo(), 而同一个package下(同一个文件下)的test2.go中想使用demo函数
解决方法:test1.go和test2.go的package要相同,且test1.go中不能包含func main() {},然后编译时要执行go build test2.go test1.go,如果go build指令不带上test1.go(即执行go build test2.go),则会报undefined: demo错误
场景b:在/User/hehe/go/src/mygopl/lissajous/下的main.go中定义了Demo(), 而/User/hehe/go/src/mygopl/server2/下的main.go想使用Demo函数
解决方法:server2/main.go中import "mygopl/lissajous", 同时调用时:lissajous.Demo()
6、函数中短变量声明:
(1)在一个短变量声明中,如果左边包含多个变量,则可以包含已声明过的变量a,此时短变量声明对于a相当于赋值操作,例如:
in, err := os.Open(infile)
// ...
out, err := os.Create(outfile)
(2)一个短变量声明至少包含一个新的未声明的变量,否则编译报错,如:
f, err := os.Open(infile)
// ...
f, err := os.Create(outfile) // compile error: no new variables
f, err = os.Create(outfile) //correct
7、变量的生命周期:
package变量(全局变量?)的生命周期是整个程序的执行时间;而局部变量的生命周期在其不可达(unreachable)时结束
8、如果一个package被导入但是没有被使用,或者一个变量声明了但是没被使用,则编译时会报错:"imported and not used"
9、package初始化:
待补充。
10、字符串
定义:不可变字节序列,用双引号包住
(1)在Go中,raw字符串(等价于python的r''字符串)所有的符号都不会被转义,用`str_conten`表示;注意,raw字符串中的'\r'(return carriage)会被删除,从而使得字符串值在所有平台都一样;raw字符串常用于正则表达式、html模版和json;
(2)当使用range遍历字符串时,Go会隐式对字符串进行UTF-8解码;
(3)当UTF-8解码器遇到意料外的输入字节,会产生一个特殊的代替符'\uFFFD',即
(4)处理字符串常用的4个标准库:bytes, strings, strconv, unicode
11、Go源代码总是使用UTF-8编码,Go的字符串也为UTF-8编码
12、Go中双引号和单引号的使用区别:
双引号用于修饰字符串,而单引号用于修饰字符,跟C语言一致
13、Go的rune就是unicode字符;UTF-8更常用作信息交换格式;而程序中使用rune更方便,因为字节大小一致
14、数组:
(1) [3]int和[4]int是不同的数据类型,例如:
q := [3]int{1,2,3}
q = [4]int{1,2,3,4} // compile error: cannot assign [4]int to [3]int
(2)由于数组大小是固定的,因此数组很少作为函数的参数,而用切片(slice)作为函数的参数
15、切片(Slice)
定义:同数据类型的可变长序列,由三部分组成:指针,长度和容量;指针指向通过切片访问到的切片底层数组的第一个元素;长度为切片包含的元素数;容量为切片开始的元素到底层数组的最后一个元素的元素数
生成切片:s[i:j], 0 <= i <= j <= cap(s), 其中i的缺省值为0, j的缺省值为len(s),而s[:]表示引用整个数组s
(1)不同的切片可以共享同一个底层数组
(2)对一个切片summer := s[6:9]进行再切片, 当切片范围上限大于cap(s) = 7,如summer[:20], 会产生运行时错误(panic);但切片范围上限大于len(s) = 3, 则可以扩大切片,参考代码:
package main
import "fmt"
func main() {
months := [...]string{1: "January", 2: "February", 3: "March", 4: "Apr il", 5: "May", 6: "June", 7: "July", 8: "August", 9: "September", 10: "October ", 11: "November", 12: "December"}
Q2 := months[4:7]
summer := months[6:9]
fmt.Println(Q2)
fmt.Println(summer)
//fmt.Println(summer[:20]) // panic: out of range
endlessSummer := summer[:5]
fmt.Println(endlessSummer)
(3)切片间不可比较,即不能用==来比较两个切片是否包含相同的元素
相关参考:深度解密Go语言之Slice
16、fmt.Printf的用法:
%x用于以16进制打印数组或者slice of bytes的所有元素;%t用于打印boolean;%T用于打印value的类型
17、Go的函数调用使用值传递,也即函数的参数接收到的值是传入值的copy
18、Map
(1)key的类型必须为可比较的,即可以用==进行比较;
(2)创建map:
ages := make(map[string]int) // mapping from strings to ints
// another way to create map
ages := map[string]int{
"alice": 31,
"charlie": 34
}
(3)移除map的元素:delete(ages, "alice")
(4)map的元素不是变量,因此不能取其元素地址,因为随着map的增长,可能会对已存在的元素进行rehash,导致原有的元素地址改变:
_ = &ages["bob"] // compile error: cannot take address of map element
(5)判断某个key是否在map中:
if age, ok := ages["bob"]; !ok {/* ... */}
19、结构体(struct)
(1)若两个strct的字段顺序不一样,则为两个不同的struct类型;
(2)若结构体某字段的首字母为大写,则该字段可被导出;反之则不可导出;
(3)一个结构体的字段类型不能为该结构体类型,但可以为该结构体类型变量指针(如链表或者树);
(4)空的结构体不包含任何字段,记为strcut{};
(5)函数传参时应使用结构体指针来提高性能;
(6)当且仅当结构体的所有字段都是可比较的,两个该结构体类型的实例才能用==比较,此时会对其每个字段进行比较;
(7)Go的结构体允许使用匿名字段(即一个字段只有类型没有名字),但类型只能为named type或者指向named type的指针,如:
type Point struct {
X, Y int
}
type Circle struct {
Point
Radius int
}
type Wheel struct {
Circle
Spokes int
}
var w Wheel
w.X = 8 // equivalent to w.Circle.Point.X = 8
同时,有嵌套结构的struct初始化的时候必须要遵循结构体定义,否则会编译报错:
w = Wheel{8,8,5,20} // compile error: unknown fields
w = Wheel{X: 8, Y: 8, Radius: 5, Spoke: 20} // compile error: unknown fields
并且,由于匿名字段有隐式名字,因此在一个结构体中,不能含有两个同类型的匿名字段;而由于匿名字段的名字由其类型隐式决定,因此该匿名字段的可见性也由其类型首字母大小写决定
20、JSON
(1)Go将数据转换为json:使用encoding/json下的json.Marshal(pending_data);Marshal函数使用Go的struct字段作为JSON对象的字段,只有可导出的字段才会被marshaled;
(2)json反序列化为Go数据结构:json.Unmarshal
21、函数
基本定义:
func name(parameter-list) (result-list) {
body
}
注意:参数和返回结果列表均可为空;如果函数体为空,则该函数使用Go之外的其他语言实现
(1)调用多值返回函数的结果是一个元组,调用方必须将该函数每个返回结果显式赋予变量,如果想忽略某个返回结果,则需将其赋予给Go的blank identifier(空标志符) --- '_', 即单下划线
(2)bare return: 当一个函数的返回结果均有名字时,return后面可以不带任何东西,如
func CountWordsAndImages(url string) (words, images int, err error) {
resp, err := http.Get(url)
if err != nil {
// this is so called bare return.
return
}
}
注: 由于该特性会降低代码可读性,实践中尽量避免使用
(2)错误处理:编写Go代码,习惯先处理错误情况,再到正常处理流程;
(3)函数是Go的第一类值(first-class values),函数名可以赋值给变量;函数类型的零值(zero value)是nil,调用此函数会引起panic:
var f func(int) int
f(3) //panic: call of nil function
(4)匿名函数
strings.Map(func(r rune) rune {return r + 1}, "hehe")
注意:当在for循环中使用匿名函数时,需要注意捕捉迭代变量
// 以下为错误demo
var rmdirs []func()
for _, dir := range tempDirs() {
os.MkdirAll(dir, 0755)
rmdirs = append(rmdirs, func() {
os.RemoveAll(dir) // NOTE: incorrect!!!
}
}
//以下为正确demo
var rmdirs []func()
for _, d := range tempDirs() {
dir := d // NOTE: necessary!!!
os.MkdirAll(dir, 0755)
rmdirs = append(rmdirs, func() {
os.RemoveAll(dir)
}
}
(5)可变参数函数:
定义:最后一个参数类型前面加上"..."
func sum(vals ...int) int {
total := 0
for _, val := range vals {
total += val
}
return total
}
调用方式:
values := []int{1,2,3,4}
fmt.Println(sum(values...))
(6)defer语句
定义: defer + 函数/method调用;当程序执行到defer语句时,其后的函数/method完成传参过程,但是该函数/method则是在defer语句所在的函数块执行完后才被调用(可以类比python的with语句相关的__exit__方法);如果一个函数块中有多个defer语句,则最后的defer语句最先执行(跟栈一样,先进后出)
应用场景:go使用Defer的几个场景
(7)panic(有点类似于python的exception,但panic后程序最终会变为为crash态,即退出,而不是像python那样捕捉异常后还可以继续执行后续其他处理逻辑)
定义:Go程序在运行时发生错误,或者程序中直接调用内置panic函数时的状态;当处于panic时,正常的执行被终止,该goroutine里面的defer函数调用被执行,然后程序到达crash态,并给出相应的log消息
(8)recover
定义:有时我们不希望因为无法处理错误panic而导致整个进程挂掉,因此需要像python一样能够handle panic(异常处理机制);
golang在这种情况下可以在panic的当前goroutine的defer中使用recover来捕捉panic;注意recover只在defer的函数中有效,如果不是在refer上下文中调用,recover会直接返回nil
相关参考:Golang: 深入理解panic and recover
22、method
定义:类似于函数声明,只是在func关键字和函数名直接多了一个该method的receiver,例如:
type Point struct{X, Y float64}
// define a method of the Point type.
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
p := Point{1, 2}
q := Point{4, 6}
fmt.Println(p.Distance(q)) // method call
注:其中p叫做method的receiver;表达式p.Distance叫做selector
(1)由于方法和字段在同一个命名空间,因此如果对于Point类型再定义一个名称为X的method,则编译时会报错,因为Point已经有了一个字段叫X;
(2)只要底层类型不是指针或者接口类型,就可以在同一个package里对任意命名类型声明method;
(3)对于同一个类型,其所有的method必须使用不同的名字;
(4)在一个receiver声明中,类型要么为命名类型(Point)或者指向其的指针(*Point):
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
23、Interface
定义:
24、Interface value:
定义:由concrete type(具体类型)和该类的值组成,它们又被称为动态类型和动态值
注:由于Go为静态类型语言,类型是一个编译时概念,因此一种类型不是一个值(a type is not a value),
25、Go内建类型error为接口类型
26、Reflection
请参考:Golang的反射reflect深入理解和示例
27、类型转换
请参考:GO语言总结(5)——类型转换和类型断言
28、三个'.'号(即'...')的使用场景:
请参考:3 dots in 4 places
29.指针
相关参考:Golang研学:在用好Golang指针类型
补充
golang找到的一些参考资料:
Go语法指南,你遇到过哪些高质量的 Go 语言面试题?, golang 面试题(从基础到高级),Golang 面试题搜集
Golang 新手可能会踩的 50 个坑,
n、go run go build go install:
go run:go run 编译并直接运行程序,它会产生一个临时文件(但不会生成 .exe 文件),直接在命令行输出程序执行结果,方便用户调试。
go build:go build 用于测试编译包,主要检查是否会有编译错误,如果是一个可执行文件的源码(即是 main 包),就会直接生成一个可执行文件。
go install:go install 的作用有两步:第一步是编译导入的包文件,所有导入的包文件编译完才会编译主程序;第二步是将编译后生成的可执行文件放到 bin 目录下($GOPATH/bin),编译后的包文件放到 pkg 目录下($GOPATH/pkg)
相关参考:go run go build go install 命令解释