一、程序结构
1、第一行非注释行用 package xxx 定义包名。
2、import 用于导入程序使用的包
3、文件中的 首字母大写的标识符是对外暴露导出的,首字母小写的标识符为只对内有效的。
4、变量的声明格式为
1、var identifier1, identifier1, ... type
2、var identifier = 1 根据值决定类型
3、identifier := value 省略 var 直接用 := 申明变量并初始化
5、iota 特殊的变量,可以理解为能被编译器修改的常量,每次遇到const关键字则重置为0,const中每新增一行常量申明将使用iota计数一次。
const (
a = iota
b = iota
c = iota
)
或
const (
a = iota
b
c
)
上面两者都是等价的, a = 0, b = 1, c = 2
6、switch 接口 case 后自带break,只要匹配成功则不会执行其他case,如果需要执行其他的可以使用 fallthrough
switch var {
case val1:
...
case val2:
...
case val3:
...
fallthrough //会执行下一个case
case val4:
...
default:
...
}
二、函数
1、函数申明格式
func 函数名字(形式参数列表)(返回值列表){
函数体实现
}
2、Go 函数可以支持多返回值,并且可以为返回值命名,但是不支持混合使用,即要么返回值都命名要不都不命名。
func swap(a int, b int)(x int, y int){// 此处返回两个值,x,y
x = b;
y = a;
}
3、Go 中函数参数传递除了 指针、切片、map 等引用型对象是引用传递否则都是按值传递。
4、Go 支持可变参数格式如下
func myfunc(args ...int){
for _, arg := range args {
fmt.Println(arg)
}
}
//可变参数以 ...type 方式定义,实际上 args 就是一个切片,但是这种方式避免了调用者手动将多个参数组装成切片。
//另外如果需要传递不同类型参数 可以将类型定义为 ...interface{}
//如果要讲可变参数再次传递 直接 args ...
// example
func test(args ...interface{}){
myfunc(args...)
}
5、Go 中支持 defer (延迟执行),通过defer申明的语句将在函数结束后多个defer语句按后进先出的顺序执行
func main() {
fmt.Println("defer begin")
// 将defer放入延迟调用栈
defer fmt.Println(1)
defer fmt.Println(2)
// 最后一个放入, 位于栈顶, 最先调用
defer fmt.Println(3)
fmt.Println("defer end")
}
// 输出
// defer begin
// defer end
// 3
// 2
// 1
6、Go 用异常处理使用 panic 函数触发程序宕机,可以使用 recover 恢复 panic 触发的宕机。panic 会打印宕机的堆栈信息
package main
import (
"fmt"
"runtime"
)
// 崩溃时需要传递的上下文信息
type panicContext struct {
function string // 所在函数
}
// 保护方式允许一个函数
func ProtectRun(entry func()) {
// 延迟处理的函数
defer func() {
// 发生宕机时,获取panic传递的上下文并打印
err := recover()
switch err.(type) {
case runtime.Error: // 运行时错误
fmt.Println("runtime error:", err)
default: // 非运行时错误
fmt.Println("error:", err)
}
}()
entry()
}
func main() {
fmt.Println("运行前")
// 允许一段手动触发的错误
ProtectRun(func() {
fmt.Println("手动宕机前")
// 使用panic传递上下文
panic(&panicContext{
"手动触发panic",
})
fmt.Println("手动宕机后")
})
// 故意造成空指针访问错误
ProtectRun(func() {
fmt.Println("赋值宕机前")
var a *int
*a = 1
fmt.Println("赋值宕机后")
})
fmt.Println("运行后")
}
// ====== 代码输出结果如下:
// 运行前
// 手动宕机前
// error: &{手动触发panic}
// 赋值宕机前
// runtime error: runtime error: invalid memory address or nil pointer dereference
// 运行后
7、Go 提供了Test 功能做单元测试,使用规则如下
- 测试用例文件不会参与正常源码的编译,不会被包含到可执行文件中;
- 测试用例的文件名必须以
_test.go
结尾; - 需要使用 import 导入 testing 包;
- 测试函数的名称要以
Test
或Benchmark
开头,后面可以跟任意字母组成的字符串,但第一个字母必须大写,例如 TestAbc(),一个测试用例文件中可以包含多个测试函数; - 单元测试则以
(t *testing.T)
作为参数,性能测试以(t *testing.B)
做为参数; - 测试用例文件使用
go test
命令来执行,源码中不需要 main() 函数作为入口,所有以_test.go
结尾的源码文件内以Test
开头的函数都会自动执行。
// demo.go
package demo
// 根据长宽获取面积
func GetArea(weight int, height int) int {
return weight * height
}
// demo_test.go
package demo
import "testing"
func TestGetArea(t *testing.T) {
area := GetArea(40, 50)
if area != 2000 {
t.Error("测试失败")
}
}
func BenchmarkGetArea(t *testing.B) {
for i := 0; i < t.N; i++ {
GetArea(40, 50)
}
}
// go test -v 单元测试
// go test -bench="." 性能测试
// go test -cover 覆盖测试
三、结构体
1、结构体定义格式
type 类型名 struct{
字段1 字段1类型
字段2 字段2类型
...
}
2、结构体实例化方式
1、 var ins T // T 为结构体类型
2、 ins := new(T) // ins 为 *T 指针类型
3、ins := &T{} // go 中对结构体进行取地址操作,视为进行一次 new 操作,所以 ins 还是指针类型
3、结构体初始化
1、键值对初始化
ins := 结构体类型名{
字段1 : 字段1值
字段2 : 字段2值
}
2、使用多个值的列表
ins := 结构体类型名{
字段1值,
字段2值,
}
4、Go 中是没有构造函数的概念,所以一般使用命名函数初始化结构体的过程当成构造函数
type Cat struct {
Color string
Name string
}
func NewCatByName(name string) *Cat {
return &Cat{
Name: name,
}
}
func NewCatByColor(color string) *Cat {
return &Cat{
Color: color,
}
}
5、结构体方法
func (接收器变量 接收器类型) 方法名(参数列表) (返回参数) {
函数体
}
type Bag struct {
items []int
}
func (b *Bag) Insert(itemid int) {
b.items = append(b.items, itemid)
}
接收器分为指针类型和非指针类型,区别在于,非指针类型的只能读取结构体值但无法修改结构体的值。
type Point struct {
X int
Y int
}
// 非指针接收器的加方法
func (p Point) Add(other Point) Point {
// 成员值与参数相加后返回新的结构
return Point{p.X + other.X, p.Y + other.Y}
}
6、结构体内嵌
package main
import "fmt"
type innerS struct {
in1 int
in2 int
}
type outerS struct {
b int
c float32
int // anonymous field
innerS //anonymous field
}
func main() {
outer := new(outerS)
outer.b = 6
outer.c = 7.5
outer.int = 60
outer.in1 = 5
outer.in2 = 10
fmt.Printf("outer.b is: %d\n", outer.b)
fmt.Printf("outer.c is: %f\n", outer.c)
fmt.Printf("outer.int is: %d\n", outer.int)
fmt.Printf("outer.in1 is: %d\n", outer.in1)
fmt.Printf("outer.in2 is: %d\n", outer.in2)
// 使用结构体字面量
outer2 := outerS{6, 7.5, 60, innerS{5, 10}}
fmt.Printf("outer2 is:", outer2)
}
7、Go 自带垃圾回收GC机制,可以给对象指针设置在对象被gc时的回调函数。SetFinalizer
func SetFinalizer(x, f interface{})
四、接口
1、接口的申明方式
type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
…
}
2、接口类型之间的转换
t,ok := i.(T) //i 代表接口变量,T 代表转换的目标类型,t 代表转换后的变量, ok 代表是否转换成功
五、包 package
1、Go中使用包管理代码,每个源文件都必须属于一个包,申明文件属于哪个包用
package packageName
2、导入包
1、普通方式
import "包的路径" //可以为相对路径也可以是绝对路径,绝对路径以 GOPATH/src/ 开始
//还可以一次导入多个包
import (
"path/package1"
"path/package2"
)
2、
//还可以给导入包取别名
import othername "packagename1"
3、
//还可以直接将包的内容合并到当前程序中,这样包中的内容就不需要通过包名使用了,有点像c++ using namespace std;这种写法
import . "fmt"
3、匿名引用格式,只会执行包的初始化的init函数,不会使用包内部的数据。
import _ "fmt"
3、包内标识符如果希望外部能访问则需要将标识符首字母大写。
4、包可以定义init 函数,他会在 main函数执行前被调用。
五、并发
1、goroutine 直接 go func() 就能启动一个并行的携程
2、通道 channel
var 通道变量 chanel 动刀类型 //申明
通道实例 := make( chan 数据类型) //创建通道
往通道发送数据的方式为:
通道变量 <- 值 //把数据往通道发送时,如果一直没有接受则发送操作会持续阻塞。
接收通道数据的方式为
1、阻塞接收 data := <-ch
2、非阻塞接收 data, ok := <-ch
3、接收任意数据,忽略接收数据 <-ch
4、循环接收 for data := range ch {}
2.1 单向通道,单向通道是一种对通道使用上的一种限制,当通道传递给函数时我们通过申明单向通道来限制他的使用方式
var 通道实例 chan<- 元素类型 // 只能发送通道
var 通道实例 <-chan 元素类型 // 只能接收通道
2.2 缓冲通道,创建时如果不指定缓冲大小,则默认为无缓冲的通道,则需要发送和接收方都阻塞等待。
通道实例 := make(chan 通道类型, 缓冲大小) //缓冲通道创建方法
缓冲通道的特性为:
- 带缓冲通道被填满时,尝试再次发送数据时发生阻塞。
- 带缓冲通道为空时,尝试接收数据时发生阻塞。
2.3 通道超时机制
select {
case <-chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}
原理是select从上到下评估每个发送和接收语句,如果其中的任意一语句可以继续执行(即没有被阻塞),那么就从那些可以执行的语句中任意选择一条来使用。
如果没有任意一条语句可以执行(即所有的通道都被阻塞),那么有如下两种可能的情况:
如果给出了 default 语句,那么就会执行 default 语句,同时程序的执行会从 select 语句后的语句中恢复;
如果没有 default 语句,那么 select 语句将被阻塞,直到至少有一个通信可以进行下去。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
quit := make(chan bool)
//新开一个协程
go func() {
for {
select {
case num := <-ch:
fmt.Println("num = ", num)
case <-time.After(3 * time.Second): //time.After 会返回一个通道 3s 后收到数据
fmt.Println("超时")
quit <- true
}
}
}() //别忘了()
for i := 0; i < 5; i++ {
ch <- i
time.Sleep(time.Second)
}
<-quit
fmt.Println("程序结束")
}
六、反射 reflect
1、