Go Slice 底层实现


在 Go 中,与 C 数组变量隐式作为指针使用不同,Go 数组是值类型,赋值和函数传参操作都会复制整个数组数据。

func main() {
    arrayA := [2]int{100, 200}
    var arrayB [2]int

    arrayB = arrayA

    fmt.Printf("arrayA : %p , %v\n", &arrayA, arrayA)
    fmt.Printf("arrayB : %p , %v\n", &arrayB, arrayB)

    testArray(arrayA)
}

func testArray(x [2]int) {
    fmt.Printf("func Array : %p , %v\n", &x, x)
}

打印结果:

arrayA : 0xc4200bebf0 , [100 200]  
arrayB : 0xc4200bec00 , [100 200]  
func Array : 0xc4200bec30 , [100 200]

阅读全文

Go 单元测试


Go 语言中自带有一个轻量级的测试框架 testing 和自带的 go test 命令来实现单元测试和性能测试。
实际操作就是建立一个 gotest 目录。在该目录下面创建两个文件:gotest.go 和 gotest_test.go。
gotest.go 这个文件里面我们是创建了一个包,里面有一个函数实现了除法运算:

package gotest

import (
    "errors"
)
// 除法函数
func Division(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为0")
    }
    return a / b, nil
}

gotest_test.go 这是我们的单元测试文件,但是记住下面的这些原则:

  • 文件名必须是 _test.go 结尾的,这样在执行 go test 的时候才会执行到相应的代码。
  • 你必须 import testing 这个包。
  • 所有的测试用例函数必须是 Test 开头。
  • 测试用例会按照源代码中写的顺序依次执行。
  • 测试函数 TestXxx() 的参数是 testing.T,我们可以使用该类型来记录错误或者是测试状态。

测试格式:
func TestXxx (t *testing.T),Xxx 部分可以为任意的字母数字的组合,但是首字母不能是小写字母 [a-z],例如
Testintdiv 是错误的函数名。函数中通过调用 testing.T 的 Error,Errorf,FailNow,Fatal,FatalIf 方法,说明测试不通过,调用 Log 方法用来记录测试的信息。





阅读全文

Go 比较两个 Slice 是否相等


开发中经常会遇到需要比较两个 slice 包含的元素是否完全相等的情况,一般来说有两个思路:

  • reflect 比较的方法
  • 循环遍历比较的方法

reflect 比较的方法

func StringSliceReflectEqual(a, b []string) bool {
    return reflect.DeepEqual(a, b)
}

这个写法很简单,就是直接使用 reflect 包的 reflect.DeepEqual 方法来比较 a 和 b 是否相等。

阅读全文

Go Redis 库 Redigo


https://github.com/garyburd/redigo/

引入库

import (
    "github.com/garyburd/redigo/redis"
)

查询数据

cli, cerr := redis.Dial("tcp", ip:port, redis.DialPassword(redisPasswd), redis.DialDatabase(1))
if cerr != nil {
    fmt.Println(cerr)
}
defer cli.Close()
// 查询 key
val, err := redis.String(cli.Do("GET", key))
if err != nil {
    fmt.Println(err)
}
return val

写入数据

cli, cerr := redis.Dial("tcp", ip:port, redis.DialPassword(redisPasswd), redis.DialDatabase(1))
if cerr != nil {
    fmt.Println(cerr)
}
defer cli.Close()
_, err := cli.Do("SET", key, value)
if err != nil {
    fmt.Println(err)
}
阅读全文

Go MySQL 库 go-sql-driver


https://github.com/go-sql-driver/mysql

引入库

import (
    "database/sql"

    _ "github.com/go-sql-driver/mysql"
)

查询数据

// 建立 MySQL 链接
db, err := sql.Open("mysql", "user:passwd@tcp(ip:port)/db")
if err != nil {
    fmt.Println(err)
}
defer db.Close()
// 执行 SQL
rows, rerr := db.Query(sqlInfo)
if rerr != nil {
    fmt.Println(rerr)
}
defer rows.Close()
// 获取 SQL 数据
for rows.Next() {
    var returnData string
    err := rows.Scan(&returnData)
    if err != nil {
        fmt.Println(err)
    }
    return returnData
}
return returnData

写入/更新数据

// 建立 MySQL 链接
db, err := sql.Open("mysql", "user:passwd@tcp(ip:port)/db")
if err != nil {
    fmt.Println(err)
}
defer db.Close()
// 执行 SQL
db.Exec(sqlInfo)
阅读全文

Go 实现简单的 Set


需求
对于 Set 类型的数据结构,其实本质上跟 List 没什么多大的区别。无非是 Set 不能含有重复的 Item 的特性,Set 有初始化、Add、Clear、Remove、Contains 等操作。

Go中Map的数据结构,Key是不允许重复的:

m := map[string]string{
    "1": "one",
    "2": "two",
    "1": "one",
    "3": "three",
}
fmt.Println(m)

程序会直接报错,提示重复 Key 值,这样就非常符合 Set 的特性需求了。

定义
前面分析出 Set 的 Value 为固定的值,用一个常量替代即可。但是笔者分析的实现源码,用的是一个空结构体来实现的,如下所示:

// 空结构体
var Exists = struct{}{}
// Set is the main interface
type Set struct {
    // struct 为结构体类型的变量
    m map[interface{}]struct{}
}


阅读全文

Go 语言小细节(四)


map 的容量
你可以在 map 创建时指定它的容量,但你无法在 map 上使用 cap() 函数。

package main
func main() {  
    m := make(map[string]int,99)
    cap(m)
}

匿名函数作用域陷阱

import (
    "fmt"
)

func main(){
    var msgs []func()
    array := []string{
        "1", "2", "3", "4",
    }
    for _, e := range array{
        msgs = append(msgs, func(){
            fmt.Println(e)
        })
    }
    for _, v := range msgs{
        v()
    }
}

阅读全文

Go 语言小细节(三)


在 for 语句的闭包中使用迭代变量会有问题
在 for 迭代过程中,迭代变量会一直保留,只是每次迭代值不一样。因此在 for 循环中在闭包里直接引用迭代变量,在执行时直接取迭代变量的值,而不是闭包所在迭代的变量值。如果闭包要取所在迭代变量的值,就需要 for 中定义一个变量来保存所在迭代的值,或者通过闭包函数传参。

package main

import (  
    "fmt"
    "time"
)

func forState1(){
    data := []string{"one","two","three"}

    for _,v := range data {
        go func() {
            fmt.Println(v)
        }()
    }
    time.Sleep(3 * time.Second)
    // three, three, three

    for _,v := range data {
        vcopy := v
        // 使用临时变量
        go func() {
            fmt.Println(vcopy)
        }()
    }
    time.Sleep(3 * time.Second)
    // one, two, three

    for _,v := range data {
        go func(in string) {
            fmt.Println(in)
        }(v)
    }
    time.Sleep(3 * time.Second)
    // one, two, three
}

func main() {  
    forState1()
}

阅读全文

Go 语言小细节(二)


数组用于函数传参时是值复制
方法或函数调用时,传入参数都是值复制,跟赋值一致,除非是 map、slice、channel、指针类型这些特殊类型是引用传递。

x := [3]int{1,2,3}

数组在函数中传参是值复制

func(arr [3]int) {
    arr[0] = 7
    fmt.Println(arr)
    // [7 2 3]
}(x)
fmt.Println(x)
// [1 2 3]

使用数组指针实现引用传参

func(arr *[3]int) {
    (*arr)[0] = 7
    fmt.Println(arr)
    // &[7 2 3]
}(&x)
fmt.Println(x)
// [7 2 3]

阅读全文