直接回答:go env -w 写进的是 GOENV 文件,优先于 export 设置的环境变量;格式化字符串里 %d、%g、%p、%0d、%n.mg 各有用途,记不住就查一遍比猜快。

go换源

go env -w

在格式化字符串里,

%d 用于格式化整数(%x 和 %X 用于格式化 16 进制表示的数字),

%g 用于格式化浮点型(%f 输出浮点数,%e 输出科学计数表示法),

%0d 用于规定输出定长的整数,其中开头的数字 0 是必须的。

%n.mg 用于表示数字 n 并精确到小数点后 m 位,除了使用 g 之外,还可以使用 e 或者 f,例如:使用格式化字符串 %5.2e 来输出 3.4 的结果为 3.40e+00。

%p 指针的格式化字符

数组去重

go env -w 通过这种方式设置的参数是直接修改的GOENV路径下的env文件,再使用export的方式就无效

移除依赖,项目初始化

当前代码中不需要了某些包,删除相关代码片段后并没有在 go.mod 文件中自动移出。

运行下面命令可以移出所有代码中不需要的包:

1
go mod tidy

如果仅仅修改 go.mod 配置文件的内容,那么可以运行 go mod edit --droprequire=path,比如要移出 golang.org/x/crypto 包

1
go mod edit --droprequire=golang.org/x/crypto

结构体不同的初始化的方式new的使用

new返回的是指针,下面三种初始化方式是一样的

1
2
3
4
5
6
7
type User struct{
age int
}
var a=User{age:12} // 声明变量a为User类型,并且赋值初始值为12
var b=new(User) // 声明变量b为User类型的指针,也就是User类型的实例
var c User // 声明变量c为User类型,且值为空 这里的值是系统自己给的
var d =User{} // 声明变量d为User类型,且值为空 这里的值声明为空,也是系统自己给的

几种方式的区别就是 acd是值对象 ,b是值对象的**指针**,平时没什么区别,但是在当成参数传入函数的时候,只有b可以修改属性值,其他的无法修改

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
43
44
45
46
47

```指针```属于**引用类型**,还有```slices``` ```maps``` ```channel```都属于**引用类型**

结构体类似于js中的对象,不同的是结构体是值类型,预估平时使用应该是用结构体的指针进行操作

通过结构体可以实现继承,重载

## 一个关于接口断言的例子

```go
package main

import "fmt"

func main() {
var a Bb = new(Aa)
if _, ok := a.(*Aa); ok { // 这里原来一直报红,是因为之前我用a.(Aa),提示我Aa没有Spean方法,这里可以静态检查到,因为a里面现在存到是new(Aa),也就是一个指针,然后调用a.(Aa),放入到类型就是一个值类型,值类型和指针类型

}
/////////////////// type-switch 类型检测
var y Bb = new(Cc)
switch y.(type) {
case *Cc:
fmt.Println("wawawa")
case *Aa:
fmt.Println("ieieie")
case nil:
fmt.Println("baocuol")
default:
fmt.Println("default")
}
}

type Aa struct {
}
type Cc struct{}

func (a *Aa) Spean() {
fmt.Println("haha")
}
func (a *Cc) Spean() {
fmt.Println("haha")
}

type Bb interface {
Spean()
}

接口变量的本质是指针

Linux下编译Mac, Windows平台的64位可执行程序:

CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build test.go CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build test.go

获取当前操作系统

runtime.GOOS=="windows"

关键字 defer 允许我们进行一些函数执行完成后的收尾工作

new与make

new 和 make 均是用于分配内存:new 用于值类型和用户定义的类型,如自定义结构,

make用于内置引用类型(切片、map 和管道)。它们的用法就像是函数,但是将类型作为参数: new(type)、make(type)。

new(T) 分配类型 T 的零值并返回其地址,也就是指向类型 T的指针(详见第 10.1 节)。它也可以被用于基本类型: v := new(int) 。make(T) 返回类型T 的初始化之后的值,因此它比 new 进行更多的工作(详见第 7.2.3/4 节、第 8.1.1 节和 第 14.2.1 节)

new() 是一个函数,不要忘记它的括号

map类型的切片

假设我们想获取一个 map 类型的切片,我们必须使用两次 make() 函数,第一次分配切片,第二次分配 切片中 每个 map 元素第八行很重要要通过下标的方式获取值本身,在range中的item实际是值的拷贝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main
import "fmt"

func main() {
// Version A:
items := make([]map[int]int, 5)
for i:= range items {
items[i] = make(map[int]int, 1)
items[i][1] = 2
}
fmt.Printf("Version A: Value of items: %v\n", items)

// Version B: NOT GOOD!
items2 := make([]map[int]int, 5)
for _, item := range items2 {
item = make(map[int]int, 1) // item is only a copy of the slice element.
item[1] = 2 // This 'item' will be lost on the next iteration.
}
fmt.Printf("Version B: Value of items: %v\n", items2)
}

输出结果:

1
2
Version A: Value of items: [map[1:2] map[1:2] map[1:2] map[1:2] map[1:2]]
Version B: Value of items: [map[] map[] map[] map[] map[]]

需要注意的是,应当像 A 版本那样通过索引使用切片的 map 元素。在 B 版本中获得的项只是 map 值的一个拷贝 而已,所以真正的 map 元素没有得到初始化。

通道要先有接收者,也可以反过来,只要有一个在协程里

无缓冲通道要先有接收者(协程中),然后才能进行发送(其他进程),

无缓冲通道如果在主线程中先写入数据,就会阻塞主线程,使接收数据的程序没发运行,所以要先在协程里接收,接收的时候也是阻塞,等待有数据写入,但是协程的阻塞不会影响主线程的运行

无缓冲通道,要有其他人接收数据,然后才能写入数据,

关于打印println函数

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

```go
println() // 输出一个换行符
println('a') // 这是一个char类型,输出为转位int32后的数字
println("ahah") // 输出ahah
println(12) // 输出12
println(12.12) // 输出12.12
trype SS struct{}
println(SS) // 运行报错 illegal types for operand: print 不能打印结构体
aa := SS{}
println(aa) // 运行报错 illegal types for operand: print 不能打印结构体
bb := new(SS)
println(bb) // 打印出变量的地址,这个时候bb是个指针类型
cc := &SS{}
println(cc) // 打印出变量的地址,这个时候cc是个指针类型
score := map[string]int{
"chinese": 117,
"math": 141,
"english": 104,
}

println("score:", score) // 打印出变量的地址,score是个指针类型
numbers := [3]int{300, 301, 302}

println(numbers) // 运行报错 illegal types for operand: print 不能打印数组
demo := []int{3, 6, 9}
println(demo)
demo2 := new([2]int)
println(demo2) //打印出变量的地址,数组demo2是个指针类型
slice := make([]string, 23)
println(slice) // 打印出变量的地址,切片slice是个指针类型
demo := make(chan int, 5)
demo <- 3
println(demo) // 打印出变量的地址, demo是个指针类型
println(strings.Split) // 打印出变量的地址,函数是个指针类型

new或者make出来的和普通的区别就是,普通的是值类型,如果赋值给另外一个变量的话是进行了一次值的拷贝,

new或者make出来的是指针类型,如果赋值给另外一个变量是对指针的一个拷贝,对原数据修改,另外一个变量的也会改变,普通的不会改变

string转byte数组默认是[]unint8

1
2
hex.EncodeToString(byte_data) // byte 数组转字符串
hex.DecodeString(hex_string_data) // 字符串转16进制byte数组

需要在函数中返回的数组不用new 因为new出来的是指针类型的 // 个人见解

MD5计算的两种方式

第一种

1
2
3
4
5
6
data := []byte("hello")
hash := md5.New()
n, _ := hash.Write(data) // 这里返回的n是字符串hello的长度
bytes := hash.Sum(nil)
md5str := fmt.Sprintf("%x", bytes) // 将计算出来的md5的byte数组转成字符串
fmt.Printf(md5str, n)

第二种

1
2
v := md5.Sum([]byte("hello"))
vSrt := hex.EncodeToString(v[:])

上面例子中bytes直接用strings(bytes)得出的结果是乱码,因为里面不是unicode编码,而是长度为16的bytes数组,每个里面的值为0~255之间的一个uint8类型的数字,每一个byte可能是1位数,也可能是2位或者三位数,将最终的位数固定,所以通过hex.EncodeToString()进行一次编码,将每一个byte用两位十六进制显示,因为ff刚好就是255,不足两位补0,最终长度16位的butes数组可以用32位的16进制表示出来,然后转成字符串,

比如:

1
2
3
a:=[]byte("a") //此时a=[97],用16进制表示就是61,也就是10进制转16进制

b:=Hex.EncodeToString(a)// 此时b就等于“61”,也就是将16进制的数字转成了字符串,这个里面首先十进制转16进制,然后格式化成了字符串

而平常用的strings([]byte)其本质是将byte切片中每1-3个0~255的数字按照unicode编码方式解码成一个字符,本质是切片里必须得存的是unicode编码,不然解码出来也是乱码,

97这个数字,按照unicode编码来看,这个是字符a,按照int来看,这是97,如果这是个16进制的数,那么转成10进制就是151,先要定义他是什么,才能觉得能不能转成什么

任何数据在计算机内部都是以二进制保存的,所以进制与数据的存储无关,只与输入输出有关。所以,对于进制转换,我们只关心字符串中的结果。

在计算机里存的是0110 0001,可以输出的是97(十进制),也可以输出成为a(unicode),也可以输出61(十六进制),也可以输出141(八进制),对比上面的例子,a:=[]byte("a")是将计算机里存的用10进制的方式输出,b:=Hex.EncodeToString(a)是用16进制的方式输出

三种难度加密算法的实现

1. Sha256 sha1 md5

1
2
3
4
5
6
7
8
9
10
11
12
//import "crypto/sha256"
h := sha256.New()
io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.")
fmt.Printf("% x", h.Sum(nil))
//import "crypto/sha1"
h := sha1.New()
io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.")
fmt.Printf("% x", h.Sum(nil))
//import "crypto/md5"
h := md5.New()
io.WriteString(h, "需要加密的密码")
fmt.Printf("%x", h.Sum(nil))

2. 加盐处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

//import "crypto/md5"
//假设用户名abc,密码123456
h := md5.New()
io.WriteString(h, "需要加密的密码")
//pwmd5等于e10adc3949ba59abbe56e057f20f883e
pwmd5 :=fmt.Sprintf("%x", h.Sum(nil))
//指定两个 salt: salt1 = @#$% salt2 = ^&*()
salt1 := "@#$%"
salt2 := "^&*()"
//salt1+用户名+salt2+MD5拼接
io.WriteString(h, salt1)
io.WriteString(h, "abc")
io.WriteString(h, salt2)
io.WriteString(h, pwmd5)
last :=fmt.Sprintf("%x", h.Sum(nil))

3. 专家方案

这里推荐scrypt方案,scrypt是由著名的FreeBSD黑客Colin Percival为他的备份服务Tarsnap开发的。

目前Go语言里面支持的库http://code.google.com/p/go/source/browse?repo=crypto#hg%2Fscrypt

1
2

dk := scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32)

关闭协程

方法一:

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
43
44
45
46
47
48
package main

import (
"context"
"log"
"os"
"time"
)

var (
logg *log.Logger
)

func work(ctx context.Context, ch chan bool) {
for {
select {
case <-ctx.Done():
logg.Println(`下班!`)
ch <- true
return
default:
logg.Println(`上班!`)
time.Sleep(2 * time.Second)
}
}
}

func main() {
ch := make(chan bool)
logg = log.New(os.Stdout, "", log.Ltime)
ctx, cancel := context.WithCancel(context.Background())
go work(ctx, ch)
time.Sleep(10 * time.Second)
//取消函数:当cancel被调用时,WithCancel遍历Done以执行关闭;
cancel()
// 这个chan是为了保证子的goroutine执行完,当然也可以不用chan用time.Sleep停止几秒
<-ch
logg.Println(`无脑发呆中!`)
}
/* outfile:
17:27:52 上班!
17:27:54 上班!
17:27:56 上班!
17:27:58 上班!
17:28:00 上班!
17:28:02 下班!
17:28:02 无脑发呆中!
*/

方法二

手动结束

1
runtime.Goexit()

编译

在编译的时候,如果是多个文件,使用命令

1
go build

后面不要加文件名,容易出现underfind

时间格式化

1
2006-01-02 15:04:05   //now.Format()方式 其字符串的格式是特定的"2006-01-02 15:04:05",必须是这个时间

go里的定时器

使用方法还没研究,看到了了就先记录下来,随后研究

1
2
3
4
5

ticker := time.NewTicker(time.Minute * 1)
for range ticker.C {
fmt.Println('每分钟执行一次')
}

goland如何编译出exe,在 运行/调试配置中,将环境设置为下面的,就是生成window下64位运行程序

1
CGO_ENABLED=0;GOOS=windows;GOARCH=amd64

初始化一个程序,或者运行别人的程序

1
2
3
4
5
6
#初始化一个模块,如果项目里已经有了mod文件就不需要这样了
go mod init 包名字
# 将项目中用到的包写进mod文件里
go mod tidy
# 接下来就可以愉快的运行了,需要打包exe的时候可以
go build

error类型转为字符串

1
2
3
4
// 对于error类型,可以直接使用
err.Error() // 这里输出的就是字符串
// 对于panic的时候recover到的是一个空接口,可以使用
fmt.Errorf("panicing %s",e).Error()

二进制文件压缩工具upx

  1. go生成的二进制文件,可能非常大,上传服务器什么非常吗
  2. upx可以有效压缩二进制文件
  3. 安装方法
    • Mac brew install upx
    • centos yum install upx -y
    • Debian apt install upx
  4. 使用方法
    • upx 二进制文件
    • 压缩后的文件会直接替换原文件,一般都能减小40%

关于gorm中需要字段唯一的方式

gorm:"unique"


这篇笔记整理自我自己的实践记录,如果做法有出入,或者你踩过别的坑,欢迎到留言板一起聊聊。

站内搜索

没有找到内容!