西维蜀黍

【Golang】go-redis 连接Redis

go-redis

client := redis.NewClient(&redis.Options{
	Addr:     "localhost:6379",
	Password: "", // password
	DB:       0,  // use default DB
})

pong, err := client.Ping().Result()
fmt.Println(pong, err)
var id uint64 = 64
rule := &Rule{
	Id: &id,
}
b, err := json.Marshal(rule)
if err != nil {
	panic(err)
}

fmt.Println(client.Set("cache1", b, 0).Result())

a, err := client.Get("cache1").Result()

var item myProto.CompositeLogisticPromotionRule
err = json.Unmarshal([]byte(a), &item)
if err != nil {
	panic(err)
}
fmt.Println(item.Id)
  ...


【Network】通过反向代理实现内网穿透

Background

树莓派通常会(通过LAN 或者 WIFI)连接到一个路由器上,如果我们不方便在路由器上配置端口映射(或者这个路由器之外还有另外的 NAT),当我们不在这个内网时,就无法管理我们的树莓派。

这时候,我们可以考虑使用用于内网穿透的反向代理应用。

如果你有一个拥有公网 IP 的机器,比如云主机、VPS 等。那么最稳定的方式就是在这个机器上自己架设中转节点。

  ...


【Golang】内置函数

当我们想要在 Go 语言中初始化一个结构时,可能会用到两个不同的关键字 — makenew。因为它们的功能相似,所以初学者可能会对这两个关键字的作用感到困惑,但是它们两者能够初始化的却有较大的不同。

  • make 的作用是初始化内置的数据结构,包括且仅包括 slice、map 和 Channel
  • new 的作用是根据传入的类型分配一片内存空间,并返回指向这片内存空间的指针

make()

slice := make([]int, 0, 100)
hash := make(map[int]bool, 10)
ch := make(chan int, 5)
  1. slice 是一个包含 datacaplen 的结构体 reflect.SliceHeader
  2. hash 是一个指向 runtime.hmap 结构体的指针;
  3. ch 是一个指向 runtime.hchan 结构体的指针;
  ...


【Golang】数据类型

类型

序号 类型 描述
1 布尔型(boolean type) 布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。
2 数字类型(numeric type) 整型(integer values) int 和浮点型(floating-point values) float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。
3 字符串类型(string type) 字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。
4 派生类型: 包括:(a) 指针类型(Pointer)(b) 数组类型(c) 结构化类型(struct)(d) Channel 类型(e) 函数类型(f) 切片类型(g) 接口类型(interface)(h) Map 类型

布尔型(boolean type)

布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。

数字类型(numeric type)

整型(Integer types)

The predeclared architecture-independent numeric types are:

序号 类型和描述
1 uint8 无符号(unsigned) 8 位整型 (0 到 255)
2 uint16 无符号 16 位整型 (0 到 65535)
3 uint32 无符号 32 位整型 (0 到 4294967295)
4 uint64 无符号 64 位整型 (0 到 18446744073709551615)
5 int8 有符号(signed) 8 位整型 (-128 到 127)
6 int16 有符号 16 位整型 (-32768 到 32767)
7 int32 有符号 32 位整型 (-2147483648 到 2147483647)
8 int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807)

There is also a set of predeclared numeric types with implementation-specific sizes:

序号 类型和描述
3 uint 32 或 64 位,由 architecture 决定。如果是32位CPU就是4个字节,如果是64位就是8个字节
4 int 与 uint 一样大小
5 uintptr 无符号整型,用于存放一个指针
uint either 32 or 64 bits
int  same size as uint

Demo

package main

import (
	"fmt"
	"runtime"
	"unsafe"
)

func main() {
	fmt.Println("arch:", runtime.GOARCH)

	var i1 int = 1
	var i2 int8 = 2
	var i3 int16 = 3
	var i4 int32 = 4
	var i5 int64 = 5
	fmt.Println("int:", unsafe.Sizeof(i1))
	fmt.Println("int8:", unsafe.Sizeof(i2))
	fmt.Println("int16:", unsafe.Sizeof(i3))
	fmt.Println("int32:", unsafe.Sizeof(i4))
	fmt.Println("int64:", unsafe.Sizeof(i5))
}

// output:
// arch: amd64
// int: 8
// int8: 1
// int16: 2
// int32: 4
// int64: 8

浮点型(Floating-point Numbers)

序号 类型 描述
1 float32 IEEE-754 32位浮点型数
2 float64 IEEE-754 64位浮点型数
3 complex64 32 位实数(real part)和虚数(imaginary part)
4 complex128 64 位实数和虚数

其他数字类型

以下列出了其他更多的数字类型:

序号 类型和描述
1 byte 类似 uint8
2 rune 类似 int32
  ...


【Golang】循环

For

package main

import "fmt"

func main() {
	sum := 0
	for i := 0; i < 10; i++ {
		sum += i
	}
	fmt.Println(sum)
}

无限循环

如果循环中条件语句永远不为 false 则会进行无限循环,我们可以通过 for 循环语句中只设置一个条件表达式来执行无限循环:

实例

package main

import "fmt"

func main() {
  for true {
    fmt.Printf("这是无限循环。\n");
  }
}

Or

package main

func main() {
	for {
	}
}

For-each range 循环

for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:

package main
import "fmt"

func main() {
        strings := []string{"google", "runoob"}
        for i, s := range strings {
                fmt.Println(i, s)
        }


        numbers := [6]int{1, 2, 3, 5}
        for i,x:= range numbers {
                fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
        }  
}

while

package main

import "fmt"

func main() {
	sum := 1
	for sum < 1000 {
		sum += sum
	}
	fmt.Println(sum)
}
  ...


【Golang】异常处理

异常处理

Go语言追求简洁优雅,所以,Go语言不支持传统的 try…catch…finally 的这种异常处理方式。

因为Go语言的设计者们认为,将异常处理逻辑与业务逻辑混在一起会很容易使得代码变得混乱。因为开发者很容易滥用异常,甚至一个小小的错误都抛出一个异常。

在Go语言中,通过利用多值返回来返回错误。因此,不要用异常代替错误(在 Go 的世界中,我们只能使用 error,而不是 exception),更不要用是否有抛出异常来改变控制程序执行流程(在 Java、C#中我们往往都是这样做的)。在极个别的情况下,也就是说,遇到真正的异常的情况下(比如除数为0了)。才使用Go中引入的Exception处理:defer, panic, recover。

这几个异常的使用场景可以这么简单描述:Go中可以通过调用 panic() 来抛出一个异常(这意味着你可以在任何地方调用 panic() 来表示一个自定义异常),然后通过 defer 关键字声明一个异常处理函数,最后在这个异常处理函数中通过 recover() 捕获任何之前未被捕获的异常,并进行相应的异常处理。

  ...


【Golang】静态数组(Array)和切片(slices)

数组(Array)

Define Array

An array type definition specifies a length and an element type,语法格式如下:

var variable_name [SIZE]variable_type

The type [n]T is an array of n values of type T.

The expression

var a [10]int

declares a variable a as an array of ten integers.

Arrays can be indexed in the usual way, so the expression s[n] accesses the nth element, starting from zero.

var a [4]int
a[0] = 1
i := a[0]
// i == 1

Arrays do not need to be initialized explicitly; the zero value of an array is a ready-to-use array whose elements are themselves zeroed:

// a[2] == 0, the zero value of the int type

The in-memory representation of [4]int is just four integer values laid out sequentially:

  ...


【Golang】函数

函数定义

A function type denotes the set of all functions with the same parameter and result types. The value of an uninitialized variable of function type is nil.

Go 语言函数定义格式如下:

func function_name( [parameter list] ) [return_types] {
   函数体
}

函数定义解析:

  • func:函数由 func 开始声明
  • function_name:函数名称,函数名和参数列表一起构成了函数签名。
  • parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
  • 函数体:函数定义的代码集合。

比如:

package main

import "fmt"

func add(x int, y int) int {
	return x + y
}

func main() {
	fmt.Println(add(42, 13))
}

Example:

func()
func(x int) int
func(a, _ int, z float32) bool
func(a, b int, z float32) (bool)
func(prefix string, values ...int)
func(a, b int, z float64, opt ...interface{}) (success bool)
func(int, int, float64) (float64, *[]int)
func(n int) func(p *T)

函数命名规范

由于Golang的特殊性(用大小写来控制函数的可见性),除特殊的性能测试与单元测试函数之外,都应该遵循如下原则:

  1. 使用驼峰命名
  2. 如果包外不需要访问请用小写开头的函数
  3. 如果需要暴露出去给包外访问需要使用大写开头的函数名称

一个典型的函数命名方法如下:

// 注释一律使用双斜线, 对象暴露的方法
func (*fileDao) AddFile(file *model.File) bool {
   result := db.NewRecord(*file)
   if result {
      db.Create(file)
   }
   return result
}
 
// 不需要给包外访问的函数如下
func removeCommaAndQuote(content string) string {
   re, _ := regexp.Compile("[\\`\\,]+")
   return strings.TrimSpace(re.ReplaceAllString(content, ""))
}
  ...


【Golang】模块管理与引用

包声明 - package

在go源文件的开头必须申明文件所属的package,如下所示:

package name
......

一些实验发现

实验 1

注意,位于同一个文件夹下的所有 .go 文件,它们的 package name 一定要完全一致。比如,由于 ccc.go 和 ddd.go 两个文件都位于 bbb 文件夹下,因此这两个文件的 package name 必需要完全一致(但这个 package name 不要求一定为 bbb,即不一定要和所在文件夹的文件夹名称相同)。

如果不一致,则会提示以下错误:

  ...


【Golang】变量

变量声明

可以一次声明多个变量:

var identifier1, identifier2 type

实例2

package main
import "fmt"

var c, python, java bool

func main() {
  var a string = "test"
  fmt.Println(a)

  var b, c int = 1, 2
  fmt.Println(b, c)
  
  var i int
	fmt.Println(i, c, python, java)
}

以上实例输出结果为:

test
1 2
  • 数值类型(包括complex64/128)为 0

  • 布尔类型为 false

  • 字符串为 ""(空字符串)

  • 以下几种类型为 nil

    var a *int
    var a []int
    var a map[string] int
    var a chan int
    var a func(string) int
    var a error // error 是接口
    

实例

package main

import "fmt"

func main() {
  var i int
  var f float64
  var b bool
  var s string
  fmt.Printf("%v %v %v %q\n", i, f, b, s)
}

输出结果是:

0 0 false ""

根据值自行判定变量类型

var v_name = value

实例

package main
import "fmt"
func main() {
  var d = true
  fmt.Println(d)
}

输出结果是:

true

省略 var

Inside a function, the := short assignment statement can be used in place of a var declaration with implicit type.

Outside a function, every statement begins with a keyword (var, func, and so on) and so the := construct is not available.

注意 *:=* 左侧如果没有声明新的变量,就产生编译错误,格式:

v_name := value

例如:

var intVal int 

intVal :=1 // 这时候会产生编译错误

intVal,intVal1 := 1,2 // 此时不会产生编译错误,因为有声明新的变量,因为 := 是一个声明语句

可以将 var f string = “Runoob” 简写为 f := “Runoob”:

实例

package main
import "fmt"
func main() {
  f := "Runoob" // var f string = "Runoob"

  fmt.Println(f)
}

输出结果是:

Runoob

多变量声明

//类型相同多个变量, 非全局变量
var vname1, vname2, vname3 type
vname1, vname2, vname3 = v1, v2, v3

var vname1, vname2, vname3 = v1, v2, v3 // 和 python 很像,不需要显示声明类型,自动推断

vname1, vname2, vname3 := v1, v2, v3 // 出现在 := 左侧的变量不应该是已经被声明过的,否则会导致编译错误


// 这种因式分解关键字的写法一般用于声明全局变量
var (
    vname1 v_type1
    vname2 v_type2
)

实例

package main

var x, y int
var ( *// 这种因式分解关键字的写法一般用于声明全局变量*
  a int
  b bool
)

var c, d int = 1, 2
var e, f = 123, "hello"

*//这种不带声明格式的只能在函数体中出现*
*//g, h := 123, "hello"*

func main(){
  g, h := 123, "hello"
  println(x, y, a, b, c, d, e, f, g, h)
}

以上实例执行结果为:

0 0 0 false 1 2 123 hello 123 hello

常量声明(const)

const identifier [type] = value

你可以省略类型说明符 [type],因为编译器可以根据变量的值来推断其类型。

  • 显式类型定义: const b string = "abc"
  • 隐式类型定义: const b = "abc"

多个相同类型的声明可以简写为:

const c_name1, c_name2 = value1, value2

以下实例演示了常量的应用:

实例

package main

import "fmt"

func main() {
  const LENGTH int = 10
  const WIDTH int = 5 
  var area int
  const a, b, c = 1, false, "str" *//多重赋值*

  area = LENGTH * WIDTH
  fmt.Printf("面积为 : %d", area)
  println()
  println(a, b, c) 
}

以上实例运行结果为:

面积为 : 50
1 false str

Default Values

Variables declared without an explicit initial value are given their zero value.

The zero value is:

  • 0 for numeric types,
  • false for the boolean type, and
  • "" (the empty string) for strings.
package main

import "fmt"

func main() {
	var i int
	var f float64
	var b bool
	var s string
	fmt.Printf("%v %v %v %q\n", i, f, b, s)
}
//output 0 0 false ""

变量作用域

局部变量

在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。

以下实例中 main() 函数使用了局部变量 a, b, c:

实例

package main

import "fmt"

func main() {
  */\* 声明局部变量 \*/*
  var a, b, c int

  */\* 初始化参数 \*/*
  a = 10
  b = 20
  c = a + b

  fmt.Printf ("结果: a = %d, b = %d and c = %d\n", a, b, c)
}

以上实例执行输出结果为:

结果: a = 10, b = 20 and c = 30

全局变量

在函数体外声明的变量称之为全局变量,全局变量可以在整个包甚至外部包(被导出后)使用。

全局变量可以在任何函数中使用,以下实例演示了如何使用全局变量:

实例

package main

import "fmt"

*/\* 声明全局变量 \*/*
var g int

func main() {

  */\* 声明局部变量 \*/*
  var a, b int

  */\* 初始化参数 \*/*
  a = 10
  b = 20
  g = a + b

  fmt.Printf("结果: a = %d, b = %d and g = %d\n", a, b, g)
}

以上实例执行输出结果为:

结果: a = 10, b = 20 and g = 30

Go 语言程序中全局变量与局部变量名称可以相同,但是函数内的局部变量会被优先考虑。实例如下:

实例

package main

import "fmt"

*/\* 声明全局变量 \*/*
var g int = 20

func main() {
  */\* 声明局部变量 \*/*
  var g int = 10

  fmt.Printf ("结果: g = %d\n", g)
}

以上实例执行输出结果为:

结果: g = 10

形式参数

形式参数会作为函数的局部变量来使用。实例如下:

实例

package main

import "fmt"

*/\* 声明全局变量 \*/*
var a int = 20;

func main() {
  */\* main 函数中声明局部变量 \*/*
  var a int = 10
  var b int = 20
  var c int = 0

  fmt.Printf("main()函数中 a = %d\n", a);
  c = sum( a, b);
  fmt.Printf("main()函数中 c = %d\n", c);
}

*/\* 函数定义-两数相加 \*/*
func sum(a, b int) int {
  fmt.Printf("sum() 函数中 a = %d\n", a);
  fmt.Printf("sum() 函数中 b = %d\n", b);

  return a + b;
}

以上实例执行输出结果为:

main()函数中 a = 10
sum() 函数中 a = 10
sum() 函数中 b = 20
main()函数中 c = 30

初始化局部和全局变量

不同类型的局部和全局变量默认值为:

数据类型 初始化默认值
int 0
float32 0
pointer nil
  ...