悟云

let it be

0%

package main

import “fmt”

type Base struct {
Name string
Age int
}

func (b * Base) Test() {
fmt.Println(“test successfully”)
}

type Child struct {
Base // 匿名字段, 默认把Base的所有字段都继承过来了。 这样看起来才像真正的继承
Age int
}

func main() {
c := new(Child)
c.Name = “hello” // 可以直接使用Base中的字段
c.Age = 20 // 如果有重复的, 则最外的优先

fmt.Println(c.Name)     // hello
fmt.Println(c.Age)      // 20
fmt.Println(c.Base.Age) // 要访问Base中的,可以这样写 0
c.Test()

}
go语言中的”继承“并不是真正的意义上的继承,确切地说是组合。匿名字段让习惯了C++/java之类的程序员用go更加得心应手。
作为一门年轻的语言,不得不说go语言对程序员还是非常友好的.

Time

首先有必要了解下go中Time的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Time struct {
// sec gives the number of seconds elapsed since
// January 1, year 1 00:00:00 UTC.
sec int64
// nsec specifies a non-negative nanosecond
// offset within the second named by Seconds.
// It must be in the range [0, 999999999].
nsec int32
// loc specifies the Location that should be used to
// determine the minute, hour, month, day, and year
// that correspond to this Time.
// The nil location means UTC.
// All UTC times are represented with loc==nil, never loc==&utcLoc.
loc *Location
}

可以看到go语言中对于时间的定义:基于元年一月一日零点零分开始计算,精确到纳秒级别,至于底层是否能真正精确到这个级别就不得而知了。
还有一个重要的概念是Location,我在本地测试时区是CST,即大中华标准时间.
同时,go还提供了很多方便的辅助函数。
比较函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// After reports whether the time instant t is after u.
func (t Time) After(u Time) bool {
return t.sec > u.sec || t.sec == u.sec && t.nsec > u.nsec
}

// Before reports whether the time instant t is before u.
func (t Time) Before(u Time) bool {
return t.sec < u.sec || t.sec == u.sec && t.nsec < u.nsec
}

// Equal reports whether t and u represent the same time instant.
// Two times can be equal even if they are in different locations.
// For example, 6:00 +0200 CEST and 4:00 UTC are Equal.
// Do not use == with Time values.
func (t Time) Equal(u Time) bool {
return t.sec == u.sec && t.nsec == u.nsec
}

加减运算:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
func (t Time) Add(d Duration) Time {
t.sec += int64(d / 1e9)
nsec := t.nsec + int32(d%1e9)
if nsec >= 1e9 {
t.sec++
nsec -= 1e9
} else if nsec < 0 {
t.sec--
nsec += 1e9
}
t.nsec = nsec
return t
}

func (t Time) Sub(u Time) Duration {
d := Duration(t.sec-u.sec)*Second + Duration(t.nsec-u.nsec)
// Check for overflow or underflow.
switch {
case u.Add(d).Equal(t):
return d // d is correct
case t.Before(u):
return minDuration // t - u is negative out of range
default:
return maxDuration // t - u is positive out of range
}

func (t Time) AddDate(years int, months int, days int) Time {
year, month, day := t.Date()
hour, min, sec := t.Clock()
return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec), t.Location())
}

Timer

看下定时器的定义:

1
2
3
4
5
6
7
8
// The Timer type represents a single event.
// When the Timer expires, the current time will be sent on C,
// unless the Timer was created by AfterFunc.
// A Timer must be created with NewTimer or AfterFunc.
type Timer struct {
C <-chan Time
r runtimeTimer
}

根据文档内容,定时器是一个单独的事件,除非调用AfterFunc,当超时时,C会接收到当前时间。定时器只能通过NewTimer或者AfterFunc创建。
启动定时器,和停止定时器:

1
2
func startTimer( * runtimeTimer)
func stopTimer( * runtimeTimer) bool

NewTimer 和 AfterFunc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// NewTimer creates a new Timer that will send
// the current time on its channel after at least duration d.
func NewTimer(d Duration) * Timer {
c := make(chan Time, 1)
t := &Timer{
C: c,
r: runtimeTimer{
when: when(d),
f: sendTime,
arg: c,
},
}
startTimer(&t.r)
return t
}

func AfterFunc(d Duration, f func()) * Timer {
t := &Timer{
r: runtimeTimer{
when: when(d),
f: goFunc,
arg: f,
},
}
startTimer(&t.r)
return t
}

NewTimer初始化一个定时器,当超时之后会调用sendTime函数发送当前时间;AfterFunc接受一个func作为参数,当时间到了之后,执行func。

重设定时器

1
2
3
4
5
6
7
8
9
10
func (t * Timer) Reset(d Duration) bool {
if t.r.f == nil {
panic("time: Reset called on uninitialized Timer")
}
w := when(d)
active := stopTimer(&t.r)
t.r.when = w
startTimer(&t.r)
return active
}

After函数

1
2
3
4
5
6
7
8
9
// After waits for the duration to elapse and then sends the current time
// on the returned channel.
// It is equivalent to NewTimer(d).C.
// The underlying Timer is not recovered by the garbage collector
// until the timer fires. If efficiency is a concern, use NewTimer
// instead and call Timer.Stop if the timer is no longer needed.
func After(d Duration) <-chan Time {
return NewTimer(d).C
}

释放定时器资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// Stop prevents the Timer from firing.
// It returns true if the call stops the timer, false if the timer has already
// expired or been stopped.
// Stop does not close the channel, to prevent a read from the channel succeeding
// incorrectly.
//
// To prevent a timer created with NewTimer from firing after a call to Stop,
// check the return value and drain the channel.
// For example, assuming the program has not received from t.C already:
// if !t.Stop() {
// <-t.C
// }
// This cannot be done concurrent to other receives from the Timer's
// channel.
//
// For a timer created with AfterFunc(d, f), if t.Stop returns false, then the timer
// has already expired and the function f has been started in its own goroutine;
// Stop does not wait for f to complete before returning.
// If the caller needs to know whether f is completed, it must coordinate
// with f explicitly.
func (t * Timer) Stop() bool {
if t.r.f == nil {
panic("time: Stop called on uninitialized Timer")
}
return stopTimer(&t.r)
}

最近在做支付,支付平台通知支付结果时,可能会重复通知。
为了解决这一问题,有三种思路。 锁,事务,缓存队列。
这里选择用缓存队列结合chan实现支付重复反馈可重入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
var inputTradeno = make(chan string)
var outputStatus = make(chan bool)

func chanLoop(){
hashmap := make(map[string]interface{}, 10000)
var queue common.StringFIFO
queue.Init(10000)
for {
tradeno := <- inputTradeno
if _, ok := hashmap[tradeno]; ok{
outputStatus <- true
}else {
if queue.Full(){
oldestTradeno, _ := queue.Pop()
delete(hashmap, oldestTradeno)
}
queue.Push(tradeno)
hashmap[tradeno] = struct {}{}
outputStatus <- false
}
}
}

func look(tradeno string) bool{
inputTradeno <- tradeno
ret := <- outputStatus
return ret
}

func main() {
go chanLoop()
}

func wftNotify(c * gin.Context){
//检验威富通是否重复通知
if look(notifyReq.Out_trade_no){
//已经缓存当前订单,说明威富通重复通知
common.LogError("该订单已经处理")
c.Writer.WriteString(wxpay.WFT_RETURN_MSG_SUCCESS)
return
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>
using namespace std;
/*
* 利用c++类嵌套内部类实现闭包
*/
class ITest
{
public:
void operator() ()
{
process();
}

virtual ~ITest(){}
protected:
virtual void process() = 0;
};
ITest * test()
{
static int count = 0;
class Test: public ITest
{
public:
virtual void process()
{
cout << "嵌套内部类:" << " Count is " << count++ <<endl;
}
};

return new Test();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* 利用函数内部类实现闭包
*/
typedef void (*Func)(const char *);

Func testFunc()
{
static int count = 100;

class Test
{
public:
static void process(const char * pName = NULL)
{
if(pName == NULL){
pName = "moses";
}
cout << "函数内部类:"<< pName << " Count is "<< count-- << endl;
}
};

return Test::process;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
* c++新特性lambda表达式实现闭包
*/
std::function<void(const char *)> lambdaTest()
{
int count = 100;

auto func = [count] (const char * pName = NULL) mutable {
if(pName == NULL){
pName = "moses";
}
cout << "lambda : " << pName << " Count is " << count-- << endl;
};

return func;
}

反射简介

Reflection(反射)在计算机中表示 程序能够检查自身结构的能力,尤其是类型。 它是元编程的一种形式。
本文中,我们主要理解Go语言中的的反射运作机制。每个编程语言的反射模型不大相同,很多语言根本不支持反射(C、C++)。笔者在学校主要就是学的C/C++,因此对于反射这个概念不是很清晰,最近接触Go语言,所以从go的角度探索下反射, 下文中谈到“反射”时,默认为时Go语言中的反射。

类型和接口

反射建立在类型系统之上,因此我们从类型基础知识说起。
Go是静态语言。每个变量都有且只有一个静态类型,在编译时就已经确定。
关于类型,一个重要的分类是接口类型(interface),每个接口类型都代表固定的方法集合。一个接口变量可以指向(接口变量类似于C中的指针)任何类型的具体值,只要这个值实现了该接口类型的所有方法。
一个非常非常重要的接口类型是空接口,即:

1
interface{}	

它代表一个空集,没有任何方法。由于任何具体的值都有零个或更多个方法,因此类型为interface{}的变量能够存储任何值。
有人说,Go的接口是动态类型的。这个说法是错误的!接口变量也是静态类型的,它永远只有一个相同的静态类型。如果在运行时它存储的值发生了变化,这个必须满足接口类型的方法集合。这是Go的特性。

接口变量的表示

interface变量存储一对值:赋给改变量的具体的值、值类型的描述符。更确切的说,值就是实现该接口的底层数据,类型就是底层数据类型的描述。举个例子:

1
2
3
4
5
6
var r io.Reader
tyy, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
return nil, err
}
r = tty

在这个例子中,变量r在结构上包含一个(key, value)对:(tty, * os.File)。注意:类型os.File不仅仅实现了Read方法。虽然接口变量只提供Read函数的调用权,但是底层的值包含了关于这个值的所有类型消息。所以我们能够做这样的类型转换:

1
2
var w io.Writer
w = r.(io.Writer)

上面的第二行代码是一个类型断言,它判定变量r内部的实际值也继承了io.Writer接口,所以才能被赋值给w。赋值之后,w就指向了(tty, * os.File)对,和变量r指向的是同一个(value, type)对。
不管底层具体值的方法集有多大,由于接口的静态类型限制,接口变量只能调用特定的一些方法。
继续看下面的代码:

1
2
var empty interface{}
empty = w

这里的空接口变量也包含(tty, * os.File)对。这一点很容易理解:空接口变量可以存储任何具体值以及该值的所有描述信息。
这里没有使用类型断言,因为w满足空接口的所有方法。另外需要注意的一点是,(value, type)对中的type必须是具体的类型(struct 或 基本类型),不能是接口类型。

反射可以将“接口类型变量”转换为“反射类型”对象

这里反射类型指reflect.Type和reflect.Value。
首先了解下reflect包的两种类型Type和Value。这两种类型使访问接口内的数据成为可能。它们对应两个简单的方法,分别是reflect.TypeOf和reflect.ValueOf,分别是用来读取接口变量的reflect.Type和reflect.Value部分。
首先,我们看下reflect.TypeOf:

1
2
3
4
5
6
7
8
9
package main
import (
"fmt"
"reflect"
)
func main() {
var x float64 = 3.4
fmt.Println("type:", reflect.TypeOf(x))
}

这行代码会打印出:

1
type: float64

为什么没看到接口? 这段代码看起来只是把一个float64类型的变量x传递给reflect.TypeOf,事实上查阅一下TypeOf的文档:

1
func TypeOf(i interface{}) Type

我们调用reflect.TypeOf(x)时,x被存储在一个空接口变量中被传递过去,然后reflect.TypeOf对空接口变量进行拆解,恢复其类型信息。
函数reflect.ValueOf也会对底层的值进行恢复:

1
2
var x float64 = 3.4
fmt.Println("value:", reflect.ValueOf(x))

上面这段代码会打印出:

1
value: <float64 Value>

类型 reflect.Type 和 reflect.Value 都有很多方法,我们可以检查和使用它们。这里我们举几个例子。类型 reflect.Value 有一个方法 Type(),它会返回一个 reflect.Type 类型的对象。Type和 Value都有一个名为 Kind 的方法,它会返回一个常量,表示底层数据的类型,常见值有:Uint、Float64、Slice等。Value类型也有一些类似于Int、Float的方法,用来提取底层的数据。Int方法用来提取 int64, Float方法用来提取 float64,参考下面的代码:

1
2
3
4
5
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())

上面这段代码会打印出:

1
2
3
type: float64
kind is float64: true
value: 3.4

反射库提供了很多值得列出来单独讨论的属性。首先是介绍下Value 的 getter 和 setter 方法。为了保证API 的精简,这两个方法操作的是某一组类型范围最大的那个。比如,处理任何含符号整型数,都使用 int64。也就是说 Value 类型的Int 方法返回值为 int64类型,SetInt 方法接收的参数类型也是 int64 类型。实际使用时,可能需要转化为实际的类型。
第二个属性是反射类型变量(reflection object)的 Kind 方法 会返回底层数据的类型,而不是静态类型。如果一个反射类型对象包含一个用户定义的整型数,看代码:

1
2
3
type MyInt int
var x MyInt = 7
v := relect.ValueOf(x)

上面的代码中,虽然变量 v 的静态类型是MyInt,不是 int,Kind 方法仍然返回 reflect.Int。换句话说, Kind 方法不会像 Type 方法一样区分 MyInt 和 int。

反射可以将“反射类型对象”转换为“接口类型变量”

根据一个 reflect.Value 类型的变量,我们可以使用 Interface 方法恢复其接口类型的值。事实上,这个方法会把 type 和 value 信息打包并填充到一个接口变量中,然后返回。其函数声明如下:

1
2
// Interface returns v's value as an interface{}.
func (v Value) Interface() interface{}

然后我们可以通过断言,恢复底层的具体值:

1
2
y := v.Interface().(float64)
fmt.Println(y)

事实上,我们可以更好地利用这一特性。标准库中的 fmt.Println 和 fmt.Printf 等函数都接收空接口变量作为参数,fmt 包内部会对接口变量进行拆包(前面的例子中,我们也做过类似的操作)。因此,fmt 包的打印函数在打印 reflect.Value 类型变量的数据时,只需要把 Interface 方法的结果传给 格式化打印程序:

1
fmt.Println(v.Interface())

如果要修改“反射类型对象”,其值必须是“可写的”(settable)

“可写性”有些类似于寻址能力,但是更严格。它是反射类型变量的一种属性,赋予该变量修改底层存储数据的能力。“可写性”最终是由一个事实决定的:反射对象是否存储了原始值。举个代码例子:

1
2
3
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFLoat(7.1)

如果这行代码能够成功执行,它不会更新 x ,虽然看起来变量 v 是根据 x 创建的。相反,它会更新 x 存在于 反射对象 v 内部的一个拷贝,而变量 x 本身完全不受影响。这会造成迷惑,并且没有任何意义,所以是不合法的。“可写性”就是为了避免这个问题而设计的。
上面的代码中,我们把变量 x 的一个拷贝传递给函数,因此不期望它会改变 x 的值。如果期望函数 f 能够修改变量 x,我们必须传递 x 的地址(即指向 x 的指针)给函数 f,如下:

1
f(&x)

反射的工作机制是一样的。如果你想通过反射修改变量x,就要把想要修改的变量的指针传递给反射库。
首先,像通常一样初始化变量x,然后创建一个指向它的反射对象,名字为p:

1
2
3
4
var x float64 = 3.4
p := reflect.ValueOf(&x)
fmt.Println("type of p:", p.Type())
fmt.Println("settability of p:", p.CanSet())

这段代码的输出是:

1
2
type of p: * float64
settability of p: false

反射对象p是不可写的,但是我们也无意修改p,事实上我们要修改的是 * p。为了得到p指向的数据,可以调用Value类型的Elem方法。Elem方法能够对指针进行“解引用”,然后将结果存储到Value类型对象v中:

1
2
v := p.Elem()
fmt.Printlen("settability of v:", v.CanSet())

上面这段代码的输出:

1
settability of v: true

由于变量v代表x,因此我们可以使用v.SetFloat修改x的值:

1
2
3
v.SetFloat(7.1)
fmt.Println(v.Interface())
fmt.Println(x)

上面的代码输出如下:

1
2
7.1
7.1

反射不太容易理解,reflect.Type 和 reflect.Value 会混淆正在执行的程序,但是它做的事情正是编程语言做的事情。记住:只要反射对象要修改它们表示的对象,就必须获取它们表示的对象的地址。

结构体

把反射应用到结构体时,常用的方式是 使用反射修改一个结构体的某些字段。只要拥有结构体的地址,我们就可以修改它的字段。
下面是一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
type T struct {
A int
B string
}
t := T{23, "skidoo"}
s := reflect.ValueOf(&t).Elem()
typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
f := s.Field(i)
fmt.Printf("%d: %s %s = %v\n", i,
typeOfT.Field(i).Name, f.Type(), f.Interface())
}

上面这段代码的输出如下:

1
2
0: A int = 23
1: B string = skidoo

这里有一点需要指出:变量 T 的字段都是首字母大写的(暴露到外部),因为struct中只有暴露到外部的字段才是“可写的”。
由于变量 s 包含一个“可写的”反射对象,我们可以修改结构体的字段:

1
2
3
s.Field(0).SetInt(77)
s.Field(1).SetString("Sunset Strip")
fmt.Println("t is now", t)

上面的代码输出如下:

1
t is now {77 Sunset Strip}

如果变量s是通过t,而不是&t创建的,调用SetInt和SetString将会失败,因为t的字段是不可写的。
顺便一提,struct中的tag可以通过反射获得:

1
tag := s.Field(0).Tag.Get("testtag")