个人参考文章
易忘知识点
包相关
-
go 组织包的方式有两种
a.go path(使用需要关闭go mod => go env -w GOMODULE=off)。 gopath有三个固定目录:src、pkg、bin。 pkg目录用来保存非main包编译生成的.a文件。 gopath可以有多个,windows用;隔开多个gopath,linux用:隔开多个gopath。 编译: go build 项目目录(项目目录从src之后的目录开始)。 导入时需要写包的路径名(先用go install 三方包文件夹),使用的时候需要使用包名。
b. go mod
使用需要执行go env -w GOMODULE=on go 1.11之后加入,每个项目都需要使用go mod init MODULENAME初始化。 使用go mod tidy 自动添加go.mod。
-
go build 如果不指定编译的go文件则会使用目录下所有go文件进行编译 -x可以查看编译详情
defer
- 在函数内使用,所在函数为外围函数,调用外围函数的函数称为调用函数,当所有defer执行完,外围函数才执行完。
- 外围函数有return,当函数内的所有defer执行完后,return才会返回结果(此时return中的值已经是确定的了)。
func main() {
a := new(int)
f := func() int {
*a = 1
defer func() {
*a = 2
fmt.Println("defer", *a) // 2
}()
return *a // 注意此时返回的值是1,return获得变量a值的时机比执行defer更早
}
ret := f()
fmt.Println(ret) // 1
}
- 当外围函数引起恐慌时,只有所有defer函数执行完毕后才会被扩散至外围函数。所以,一般解决函数恐慌的问题可以在函数中用defer接受到恐慌,做相应的处理。
defer func(){
if p := recover(); p!= nil{ // 若程序不幸发生paic,仍希望程序能继续进行下去,在函数可能会painc的地方使用recover接收,做相应的处理
zap.L.Error("recover panic...",zap.Strings("p",p))
}
}
//自定义的函数,处理painc
func testFunc() (err error){
panic("new panic")
defer func(){ // defer 在panic之前执行
if re := recover();re != nil{ // recover 捕获显式错误
err = fmt.Errorf("panic error %s",re) // 显式声明返回变量名称,把painc返回成error自己处理
}
}()
}
注意点:
注意点1:
defer调用外部变量必须传参
func printNumber(){
for i:=1 ;i <=5;i++{
defer func(){
fmt.Println(i)
}
}
}
// 输出结果是5 5 5 5 5
// 这是因为defer函数执行的时机决定的,当defer执行的时候,外部变量i为5。
注意点2:
函数内的多个defer函数,被压入同一个栈中,先进后出。
os.Signal api相关
- signal.Notifiy(c chan<- os.Signal, sig… syscall.Signal)
程序监听系统发送给程序的信号,其中syscall.SIGSTOP syscall.SIGKILL 只会走系统默认的操作。 - signal.Stop(c chan <- os.Signal)
程序取消掉chan监听来自系统的所有信号,如再次使用notifiy,chan最好相同,不同时,如果两个chan有相同的signal则两个signal都会被取消。
函数传参和方法接收者的不同
- 函数传参必须是规定的类型,即若函数定义入参为指针类型,则只能传指针类型。
- 方法的接受者可以是值类型,也可以是指针类型。但是接收者是否是值拷贝取决于方法的定义,即方法定义的接收者是值类型则即使传递指针接收者,还是值拷贝。
结构体String方法
// 访问结构体,自动调用了结构体对应的String方法
package main
type person struct {
name string
}
func (a person) String() string {
return a.name
}
func main() {
a := person{name: "王小二"}
fmt.Println(a) // 输出:王小二
}
接口经典案例
package main
import (
"fmt"
"math/rand"
"sort"
)
type person struct {
name string
age int
}
type persons []person
// Len is the number of elements in the collection.
func (p persons) Len() int {
return len(p)
}
// Less reports whether the element with
// index i should sort before the element with index j.
func (p persons) Less(i, j int) bool {
return p[i].name < p[j].name
}
// Swap swaps the elements with indexes i and j.
func (p persons) Swap(i, j int) {
p[i], p[j] = p[j], p[i]
}
func main() {
var peoples persons // persons实现了内部sort包中的所有方法
// peoples = make([]person, 0, 10)
for i := 1; i <= 10; i++ {
peoples = append(peoples, person{
name: fmt.Sprintf("hero-%d", rand.Int31n(100)),
age: rand.Intn(100),
})
}
//
fmt.Printf("sort after: %v\n", peoples)
sort.Sort(peoples) // 调用Sort方法对peoples排序
fmt.Printf("sort later: %v\n", peoples)
}
接口断言的案例:
package main
import "fmt"
type camera struct{}
type phone struct{}
type usber interface {
start()
stop()
}
// camera实现了相机接口
func (c camera) start() {
fmt.Println("相机开始工作...")
}
func (c camera) stop() {
fmt.Println("相机开始结束...")
}
// phone实现了usber接口
func (p phone) start() {
fmt.Println("手机开始工作...")
}
func (p phone) stop() {
fmt.Println("手机结束工作...")
}
// phone 特有的方法
func (p phone) call() {
fmt.Println("手机暂停工作...")
}
func working(u usber) { // useber可以理解接口成多态的一种实践
u.start()
// 判断接口值类型
if phone, ok := u.(phone); ok {
phone.call()
}
u.stop()
}
func main() {
var p phone
var c camera
working(p)
working(c)
}
go的工厂模式
解决包内定义的结构体,不能包外引用和访问其字段的问题。
package factory
type person struct { // 包内才能引用的结构体
name string
wight float32 // 包内访问的字段
}
func NewPerson(n string, w float32) *person {
return &person{
name: n,
wight: w,
}
}// 返回包内的结构体
func (p *person) GetName() string {
return p.name
}// 返回包内的字段内容
----------------- main.go - --------
package main
import (
"fmt"
"test/factory"
)
func main() {
p1 := factory.NewPerson("tom", 100.1) // main包使用factory包中定义的结构体
fmt.Println(*p1,p1.GetName())
}
go面向对象的三大特征
-
继承
解决代码重复问题。
多个结构体共有的字段抽象成共有的一个结构体,实现共有的方法,其他结构体,直接引用相同的结构体即可。
匿名字段有相同的字段名,访问字段时就近访问(如:调用公共结构体定义的公共方法,则访问的是公共结构体中的字段)。 -
封装
将抽象出来的字段和对字段的操作封装起来,数据被保护在内部,程序其他的包只能通过包内指定的方法才能访问到字段和对字段进行操作。go中不像java那么强调封装。
a. 优点:
隐藏实现的细节,调用方也不需要知道怎么实现。
可以在包内做一些数据校验的操作,过滤掉无意义的数据。
b.实现步骤:
结构体名和字段名小些。
提供创建结构体的构造函数(如上面工厂模式中的NewPerson函数)。
提供Set和Get方法修改和访问字段的方法。 -
多态
多态常见的体现形式:
传入的参数是接口类型。
多态数组:一个数组里面可以存放多个类型的值。
并发相关
在并发编程的时候如果程序有资源竞态的问题,在编译的时候可以使用-race选项调试,编译后的文件运行,终端会输出竟态问题。
指针有限操作
不可以取指针的值的几个特点:
- 值是不可变的:如常量,基本类型的字面量,函数字面量,方法字面量,数组索引表达式指向的字面量(字面量相当于a:=[]int{1,2}中的a[0]是一个具体的数值,在没有赋值给新的变量时无法寻址)。
- 绝大多数的临时结果:如常规函数或者方法执行的结果值,算数运算执行的结果这些都是临时结果。
- 不安全性(取指针会对指针的操作会破坏程序的唯一性):如对字典索引取值后字面量的操作。