type F interface {
f()
}
type S1 struct{}
func (s S1) f() {}
type S2 struct{}
func (s *S2) f() {}
var f1 F = S1{}
var f2 F = &S2{}
// f1.f() 无法修改底层数据
// f2.f() 可以修改底层数据,给接口变量 f2 赋值时使用的是对象指针
只有方法的接收者是一个指针,才能修改底层数据。无论方法的调用者是否是指针,底层数据能否被修改取决于 “方法的接收者” 是否是指针。上面的 S2 方法接收者是指针,所以可以完成数据的修改。
2、方法接收者是值,调用者可以是值也可以是指针,但如果接收者是指针,只能指针调用type F interface {
f()
}
type S1 struct{}
func (s S1) f() {}
type S2 struct{}
func (s *S2) f() {}
s1Val := S1{}
s1Ptr := &S1{}
s2Val := S2{}
s2Ptr := &S2{}
var i F
i = s1Val
i = s1Ptr
i = s2Ptr
// 下面代码无法通过编译。因为 s2Val 是一个值,而 S2 的 f 方法中没有使用值接收器
// i = s2Val
上面的代码,因为 S2 函数的接收者是指针,则只能通过指针调用。这个其实非常容易理解,对于值接收者,需要的是值,如果直接传值肯定没有问题,如果传递的指针,通过指针隐式转化获取对应的值,然后再调用即可。
3、接口编译检测这个一个好习惯,先看下面的 bad case。
// 如果 Handler 没有实现 http.Handler,会在运行时报错
type Handler struct {
// ...
}
func (h *Handler) ServeHTTP(
w http.ResponseWriter,
r *http.Request,
) {
...
}
如果我们提前判断,就可以在编译期间提前发现问题了。
type Handler struct {
// ...
}
// 用于触发编译期的接口的合理性检查机制
// 如果 Handler 没有实现 http.Handler,会在编译期报错
var _ http.Handler = (*Handler)(nil)
func (h *Handler) ServeHTTP(
w http.ResponseWriter,
r *http.Request,
) {
// ...
}
通过接口转化,便可以检查是否实现对应的接口。如果接收者是值,则可以通过 “{}” 初始化一个对象用于检测。
var _ http.Handler = LogHandler{}
func (h LogHandler) ServeHTTP(
w http.ResponseWriter,
r *http.Request,
) {
// ...
}
二、mutex
mutex 是 golang 的互斥锁,可以保障在多协程的情况下,数据访问的安全。
1、零值有效我们并不需要 mutex 指针
mu := new(sync.Mutex)
mu.Lock()
直接可以使用 mutex 的零值。
var mu sync.Mutex
mu.Lock()
2、mutex 可见性
go 的 map 非线程安全,所以我们经常会通过 mutex 给 map 加一个锁,大家先看一下第一种方式:
type SMap struct {
sync.Mutex
data map[string]string
}
func (m *SMap) Get(k string) string {
m.Lock()
defer m.Unlock()
return m.data[k]
}
然后我们看一下第二种方式
type SMap struct {
mu sync.Mutex
data map[string]string
}
func (m *SMap) Get(k string) string {
m.mu.Lock()
defer m.mu.Unlock()
return m.data[k]
}
感觉差别不大,有啥区别?从封装的角度来看,第二种方法更加优秀。因为第一种方式,SMap 中的 mutex 是大写的,意味着,外部可以直接调用 lock 和 unlock 方法,破坏了内部封装原则,所以方法二更好。
3、defer更安全虽然我们可以通过下面的代码,按照需求unlock
p.Lock()
if p.count < 10 {
p.Unlock()
return p.count
}
p.count++
newCount := p.count
p.Unlock()
return newCount
但上面的代码存在两个问题,一是如果分支太多很容易导致unlock ,二是可读性较差,到处是unlock。所以更加推荐下面的写法
p.Lock()
defer p.Unlock()
if p.count < 10 {
return p.count
}
p.count++
return p.count
defer 的损耗非常少,大家不必纠结。
三、Slices 和 Mapsslice 和 map 的原理类型。我们先看 slice 定义
type SliceHeader struct {
Pointer uintptr
Len int
Cap int
}
包含了一个指向数据的指针以及 slice 的长度(len)和容量(capacity)。
所以我们将 slice 当做参数传递的时候,底层共享的是同一份数据。比如下面的代码,我们先定义一个 SetTrips 方法,传入一个 slice 给 driver。
func (d *Driver) SetTrips(trips []Trip) {
d.trips = trips
}
trips := ...
d1.SetTrips(trips)
// 你是要修改 d1.trips 吗?
trips[0] = ...
然后我们在外部修改 trips,那么 driver 里面 trips 也会跟着发生变化,这是我们不希望看到的。所以更加安全的方式是,在方法里面创建一个新的 slice,然后逐一拷贝原生数据,这样外部的数据变化就不会影响到 driver 了。如下:
func (d *Driver) SetTrips(trips []Trip) {
d.trips = make([]Trip, len(trips))
copy(d.trips, trips)
}
trips := ...
d1.SetTrips(trips)
// 这里我们修改 trips[0],但不会影响到 d1.trips
trips[0] = ..
回看上一篇通过 mutex 创建线程安全 map 的文章,如果想返回整个 map 的内容,可以通过下面的方式。
func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
return s.counters
}
但这样直接返回 map 的方式,会导致调用者获取了一个非安全的 map的。如果在调用的地方修改这个 map,就会发生数据冲突。更加安全的做法是
func (s *Stats) Snapshot() map[string]int {
s.mu.Lock()
defer s.mu.Unlock()
result := make(map[string]int, len(s.counters))
for k, v := range s.counters {
result[k] = v
}
return result
}
创建一个新的 map 返回,这样只是返回这个 map 此时的快照,后续对 result 的修改,并不会影响 stats 中 map 的内容。
四、时间处理 1、timego time 是基于 int 所以可以通过直接对比 int 大小确定时间早晚
func isActive(now, start, stop int) bool {
return start
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?