Go 语言笔记
基本概念
综述
- Go 语言将静态语言的安全性和高效性与动态语言的易开发性进行有机结合,达到完美平衡。
- 设计者通过 goroutine 这种轻量级线程的概念来实现这个目标,然后通过 channel 来实现各个 goroutine 之间的通信,这个特性是 Go 语言最强有力的部分。
- Go 语言像其它静态语言一样执行本地代码,但它依旧运行在某种意义上的虚拟机,以此来实现高效快速的垃圾回收。
- 「切片」是 go 中的重要概念。
package main
import ("fmt”
“os”
)
func main() {
target := "World”
if len(os.Args) > 1 {
/* os.Args是一个参数切片 */
target = os.Args[1]
}
fmt.Println("Hello", target)
}
引号
Go语言中字符串的可以使用双引号( " )或者反引号( ` )来创建
- 双引号用来创建可解析的字符串字面量
- 反引号用来直接储存字面量
Package
- package是最基本的分发单位和工程管理中依赖关系的体现;
- 每个GO语言源代码文件开头都拥有一一个package声明,表示源码文件所属代码包;
- 同一个路径下只能存在一个package ,一个package可以拆成多个源文件组成;
- 要生成GO语言可执行程序,必须要有main的package包,且必须在该包下有main()函数;
import
- 导入的包将顺序导入
- 导入的包中包含其他包,先导入前置包
- 包中含有 init(),则先运行该函数
import alias “package name” //别名
import . "package name" //使用时不用加 PackageName.
import _ "package name" //仅调用包的 init() 函数
基本类型
// 基本类型
float32 float64 //浮点型
complex64 complex128 //复数型,实部和虚部都是float
bool //布尔型
int(取决于OS) int8 int16 rune(int32) int64 //整型
uint(取决于OS) byte(uint8) uint16 uint32 uint64 //无符号整型
uintptr(一个可以恰好容纳指针值的无符号整型)
string //字符串
error
派生类型
函数类型(func)
- Go语言中函数是一等(first-class)类型,说明可以把函数作为值来传递和使用
- Go语言中的函数可以返回多个结果
//函数类型定义
type MyFunc func(input1 string ,input2 string) string
//标准函数定义-函数类型实现
func myFunc(part1 string, part2 string) string {
return part1 + part2
}
//标准函数定义-函数类型实现(多返回值)
func myFunc(part1 string, part2 string) part1 string, part2 string {
return
}
//其他形式函数定义-函数类型实现(返回值)
func myFunc(part1 string, part2 string) (result string) {
result = part1 + part2
return
}
//初始化一个MyFunc变量
var splice MyFunc
//匿名函数类型初始化写法
var splice = func(part1 string, part2 string) string {
return part1 + part2
}
//直接调用匿名函数
var result = func(part1 string, part2 string) string {
return part1 + part2
}("1", "2")
指针类型(Pointer)
- 指针操作涉及到两个操作符——&和*
- 出现在一个类型之前就被视为符号,代表的就是对应类型的指针类型,而原类型视为指针类型的基地类型
- 对p(Person类型对象)使用 &p 得到的是 p 的指针(* Person),两者视为相反的操作
- bp.Grow()是(&bp).Grow()的速记法
- 结构体方法的使用者为该结构体类型的指针类型
- 值传递和地址传递
- 结构化类型(struct)
//分行定义属性(名称可选),行成一个一个结构体
type Person struct {
Name string
Gender string
Age uint8
}
//结构体的方法定义,可以通过 p.Grow()调用
//可以看到方法的实际接收者是Person的指针,地址传递
func (person *Person) Grow() {
person.Age++
}
// 更复杂的方法定义
func (person *Person) Move(newAddress string) string{
oldAddress := person.Address
person.Address = newAddress
return oldAddress
}
//实例化的几种方法
p := Person{Name:"Robert", Gender:"Male", Age:33}
p := Person{"Robert", "Male", 33}
p := new(Person)
//匿名结构体实例化
p := struct {
Name string
Gender string
Age uint8
}{"Robert", "Male", 33}
接口类型(interface)
- 接口类型中的方法声明是普通方法声明的简化形式,它们只包括方法名称、参数声明列表和结果声明列表
- 空接口类型即是不包含任何方法声明的接口类型,用 interface{} 表示
//不用声明一个数据类型中实现了哪个接口,只要满足了“方法集合为其超集”的条件,就建立了“实现”关系,这是典型的无侵入式的接口实现方法
type Animal interface {
Grow()
Move(string) string
}
- 判断是否实现了接口的方法
//先将p转换成空接口类型,然后才能进行判断
v := interface{}(&p)
//类型断言,result是转换的结果,ok代表实现关系与否
result, ok := v.(Animal)
//结果自然是实现了,因此只要满足方法集合要求,就算实现了该接口
数组类型
var nums = [5]float32{10.0, 2.0, 3.4, 7.0, 50.0}//标准初始化
var nums = []float32{10.0, 2.0, 3.4, 7.0, 50.0} //不指定长度的初始化
nums[3]=4.4 //赋值
// []int 和 [4]int 在 go 中是不同的类型
func main() {
var array = []int{1, 2, 3, 4, 5}
/* 未定义长度的数组只能传给不限制数组长度的函数 */
setArray(array)
/* 定义了长度的数组只能传给限制了相同数组长度的函数 */
var array2 = [5]int{1, 2, 3, 4, 5}
setArray2(array2)
}
func setArray(params []int) {
fmt.Println("params array length of setArray is : ", len(params))
}
func setArray2(params [5]int) {
fmt.Println("params array length of setArray2 is : ", len(params))
}
切片类型(slice)
slice[n] //返回切片/数组中的第n个元素
slice[n:] //返回从第n个元素到最后一个元素的切片
slice[:n] //返回从第一个元素到第 n 个元素的切片
slice[m:n] //返回 m 下标为开始的(n-m)个元素的切片
切片保留着跟原数组的关联 这里产生了容量的概念,对数组的容量进行操作,可以读取到原数组的内容
Append(slice,x,y,z……):会对切片值进行扩展并返回一个新的切片值
Copy(slice1,slice2):该函数接受两个类型相同的切片值作为参数,并会把第二个参数值中的元素复制到第一个参数值中的相应位置(索引值相同)上
字典类型(Map)
- 对于字典值来说,如果其中不存在索引表达式欲取出的键值对,那么就以它的值类型的空值(或称默认值)作为该索引表达式的求值结果
//字典的字面量,“K”意为键的类型,而“T”则代表元素(或称值)的类型
map[K]T
demo := map[int]string{1: "a", 2: "b", 3: "c"}
b := mm[2] //值的取出
e, ok := mm[5] //e 指取出的值(可能为零值),ok 表示是否存在
delete(demo,5) //删除demo 中 K 为5 的元素
通道类型(chan)
与其它的数据类型不同,我们无法表示一个通道类型的值。因此,我们也无法用字面量来为通道类型的变量赋值。我们只能通过调用内建函数make来达到目的
close(ch1)
value := <- ch1
value, ok := <- ch1
make(chan int, 0) //非缓冲的通道值的初始化方法
type Receiver <-chan int //单向接收通道
type Sender chan<- int //单向发送通道
var myChannel = make(chan int, 3)
var sender Sender = myChannel
var receiver Receiver = myChannel
类型别名
import alias "package name" //别名
type alias name //取了别名之后,和原类型视为不同类型
reflect.TypeOf() //反射,取出变量类型
变量
变量声明和赋值
const limit = 512 // 常量,其类型兼容任何数字
const top uint16 = 1421 // 常量,类型:uint16
var last float64 // 声明,此时last 为零值
var last float64 = 1.5 // 标准声明+初始化
last:= float64(1.5) // 简化标准声明+初始化
var last = 1.5 // 推断声明 float64
last:=1.5 // 简化推断声明+初始化
var hash []bool //数组声明
var hash = []bool{} //标准数组声明+初始化
hash := []bool{} //简化数组声明+初始化
var mm map[string]string //集合声明
mm:=map[int]bool{} //简化集合声明+初始化
mm:=map[int]bool{1:true,2:false,3:false} //简化集合声明+初始化
//多变量设置法
const(
Cyan = 0
Black = 1
White = 2
)
类型转换
<结果类型> := <目标类型> ( <表达式> )
var var1 int = 7
var2 := float32(var1)
空标示符 "_"
- 是一个占位符,它用于在赋值操作的时候将某个值赋值给空标示符号,从而达到丢弃该值的目的
- 全局变量必须含有 var,局部变量可以省略
变量可见性
- 首字母大写的变量是可以导出,也就是可以被其他包读到,属于共有变量
- 反之,首字母小写的变量不可导出,属于私有变量
iota
- 仅用于常量
- 每当遇到一个const 会重置为0
- 常见用法
/*1. 跳值使用*/
const(
a=iota // a=0
b=iota // b=1
_
c // c=3
)
/*2. 插队使用*/
const(
a=iota // a=0
b=3.14 // b=3.14
c // c=2
)
/*3. 表达式隐式使用*/
const(
a=iota*2 // a=0
b // b=2
c // c=4
)
/*4. 单行使用*/
const(
a,b=iota+1,iota+3 // a=1,b=3
c,d // c=2,d=4
e // e=2
)
控制语句
选择语句
- 不必写break,可以简单地进行类型判断
a:=3.14
switch a.(type){
case int:
println("it's int")
case string:
println("it's string")
default:
println("no")
}
循环语句
//死循环(true 可省略)
for true{
xxx
}
//标准循环
for i:=0;i<n;i++{
xxx
}
//数组的遍历
for key,value:=range nums{
xxx
}
goto
- 要加标签
package main
func main(){
goto One
xxx
xxx
One:{
xxx
}
}
select 分支流程控制语句
- select语句属于条件分支流程控制方法,不过它只能用于通道。它可以包含若干条case语句,并根据条件选择其中的一个执行
- 如果一条select语句中不存在default case, 并且在被执行时其中的所有case都不满足执行条件,那么它的执行将会被阻塞
-
- break语句也可以被包含在select语句中的case语句中。它的作用是立即结束当前的select语句
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
// 省略若干条语句
// 如果该select语句被执行时通道ch1和ch2中都没有任何数据,那么肯定只有default case会被执行
// 若通道中存在数据,则执行对应case
// 有数据的通道多于一个,那么Go语言会通过一种伪随机的算法来决定执行某个case
select {
case e1 := <-ch1:
fmt.Printf("1th case is selected. e1=%v.
", e1)
case e2 := <-ch2:
fmt.Printf("2th case is selected. e2=%v.
", e2)
default:
fmt.Println("No data!")
}
defer
- defer 语句会确保之后的语句执行完毕之后再执行本语句
func deferIt() {
defer func() {
fmt.Print(1)
}()
defer func() {
fmt.Print(2)
}()
defer func() {
fmt.Print(3)
}()
fmt.Print(4)
}
// output:4321
func deferIt3() {
f := func(i int) int {
fmt.Printf("%d ",i)
return i * 10
}
for i := 1; i < 5; i++ {
defer fmt.Printf("%d ", f(i))
}
}
// 标准输出上打印出 1 2 3 4 40 30 20 10
func deferIt4() {
for i := 1; i < 5; i++ {
defer func() {
fmt.Print(i)
}()
}
}
// deferIt4函数在被执行之后标出输出上会出现5555
// 因为匿名函数会直接进栈,等到出栈的时候i 就等于5 了
异常处理 error
- Go语言的函数可以一次返回多个结果。这就为我们温和地报告错误提供了语言级别的支持
- 对 err 进行判断,若不是nil 则把该错误(这里由err代表)返回给调用方
- 对于
ioutil.ReadAll()
可以直接return,说明声明的结果的数量、类型和顺序都是相同的,因此才能够做这种返回结果上的“嫁接”
func readFile(path string) ([]byte, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
return ioutil.ReadAll(file)
}
// Error 信息的生成,跟 os.ErrPermission、io.EOF 进行比较,很容易发现异常并通过errors.New()加以创建
if path == "" {
return nil, errors.New("The parameter is invalid!")
}
运行时恐慌 panic
- 内建函数panic和recover是天生的一对。前者用于产生运行时恐慌,而后者用于“恢复”它
- 同时recover()函数需要defer 调用才会生效
- 通过
panic(errors.New("Occur a panic!"))
即可生成一个恐慌 - 合理处理panic,可以有效防止程序崩溃
go 语句
- go语句的执行与其携带的表达式语句的执行在时间上没有必然联系,即go 语句执行后会在某个随机时间执行
// 时间协调语句
time.Sleep() //ms
runtime.Gosched() //让当前正在运行的Goroutine暂时“休息”一下
// sync.WaitGroup类型有三个方法可用——Add、Done和Wait
// Add会使其所属值的一个内置整数得到相应增加
// Done会使那个整数减1
// Wait方法会使当前Goroutine(这里是运行main函数的那个Goroutine)阻塞直到那个整数为0
func main() {
var wg sync.WaitGroup
wg.Add(3)
go func() {
fmt.Println("Go!")
wg.Done()
}()
go func() {
fmt.Println("Go!")
wg.Done()
}()
go func() {
fmt.Println("Go!")
wg.Done()
}()
wg.Wait()
}