Skip to main content

29 posts tagged with "golang"

View All Tags

· One min read
package main

import (
"os"
"os/signal"
"syscall"

"github.com/scott-x/slimx_backup/engine"
)

func main() {
go engine.Run() //MAIN PROCESS
quit := make(chan os.Signal)
signal.Notify(quit, syscall.SIGKILL, syscall.SIGQUIT, syscall.SIGINT, syscall.SIGTERM)
<-quit
}

· 5 min read

对于一门编程语言来说,代码格式化是最容易引起争议的一个问题,不同的开发者可能会有不同的编码风格和习惯,但是如果所有开发者都能使用同一种格式来编写代码,开发者就可以将宝贵的时间专注在语言要解决的问题上。

gofmt介绍 Golang的开发团队制定了统一的官方代码风格,并且推出了gofmt工具(gofmt或go fmt)来帮助开发者格式化他们的代码到统一的风格。gofmt是一个cli程序,会优先读取标准输入,如果传入了文件路径的话,会格式化这个文件,如果传入一个目录,会格式化目录中所有.go文件,如果不传参数,会格式化当前目录下的所有.go文件。

gofmt默认不对代码进行简化,使用-s参数可以开启简化代码功能,具体来说会进行如下的转换:

去除数组、切片、Map初始化时不必要的类型声明:

//如下形式的切片表达式:
[]T{T{}, T{}}
//将被简化为:
[]T{{}, {}}

去除数组切片操作时不必要的索引指定

//如下形式的切片表达式:
s[a:len(s)]
//将被简化为:
s[a:]

去除迭代时非必要的变量赋值

//如下形式的迭代:
for x, _ = range v {...}
//将被简化为:
for x = range v {...}
//如下形式的迭代:
for _ = range v {...}
//将被简化为:
for range v {...}

gofmt命令参数列表如下:

usage: gofmt [flags] [path ...]
-cpuprofile string
write cpu profile to this file
-d display diffs instead of rewriting files
-e report all errors (not just the first 10 on different lines)
-l list files whose formatting differs from gofmt's
-r string
rewrite rule (e.g., 'a[b:len(a)] -> a[b:]')
-s simplify code
-w write result to (source) file instead of stdout

可以看到,gofmt命令还支持自定义的重写规则,使用-r参数,按照pattern -> replacement的格式传入规则。

有如下内容的Golang程序,存储在main.go文件中。

package main

import "fmt"

func main() {
a := 1
b := 2
c := a + b
fmt.Println(c)
}

用以下规则来格式化上面的代码。

gofmt -r "a + b -> b + a"

格式化的结果如下。

package main

import "fmt"

func main() {
a := 1
b := 2
c := b + a
fmt.Println(c)
}

注意:Gofmt使用tab来表示缩进,并且对行宽度无限制,如果手动对代码进行了换行,gofmt也不会强制把代码格式化回一行。

go fmt和gofmt

gofmt是一个独立的cli程序,而go中还有一个go fmt命令,go fmt命令是gofmt的简单封装。

usage: go fmt [-n] [-x] [packages]

Fmt runs the command 'gofmt -l -w' on the packages named
by the import paths. It prints the names of the files that are modified.
For more about gofmt, see 'go doc cmd/gofmt'.
For more about specifying packages, see 'go help packages'.
The -n flag prints commands that would be executed.
The -x flag prints commands as they are executed.
To run gofmt with specific options, run gofmt itself.

See also: go fix, go vet.

go fmt命令本身只有两个可选参数-n和-x,-n仅打印出内部要执行的go fmt的命令,-x命令既打印出go fmt命令又执行它,如果需要更细化的配置,需要直接执行gofmt命令。

go fmt在调用gofmt时添加了-l -w参数,相当于执行了gofmt -l -w

· 2 min read

首先,我们要理解时间戳的概念

然而前端获取的时间戳却是毫秒级的(13位):

不管是s级、ms级、还是us级 时间戳都是int64位的

所以问题转化为如何把 int64转化为 time.Time

time包提供了下面3个api,分别针对不同的时间单位:

  • func Unix(sec int64, nsec int64) Time: s
  • func UnixMilli(msec int64) Time:ms
  • func UnixMicro(usec int64) Time:us

我们要做的就是选择正确的API,那么如何选择呢?

我们可以根据时间戳的位数来判断,假设n为时间戳:

s => 10位 time.Unix(n,0)

ms => 13位 time.UnixMilli(n)

us => 16位 time.UnixMicro(n)

EXAMPLE:

package main

import (
"time"
"fmt"
)

func main() {
var n int64 = 1631504561000;
var t = time.Unix(n,0) //处理秒级的n
fmt.Println(t.Format("2006-01-02 15:04:05")) //53670-04-27 23:23:20 发现结果不对


var t1 = time.UnixMilli(n) //处理ms级的 go version go1.17 才有这个api
fmt.Println(t1.Format("2006-01-02 15:04:05")) //2021-09-13 11:42:41
}

· One min read
  • os.Getwd(): 为动态路径,你终端cd到哪里,它就取当前的dir(等价于./),用于做小工具。
  • os.Getenv(): 路径是写死的,唯一的,用于指定配置文件的位置

· One min read

API

example:

package main

import (
"fmt"
"reflect"
)

type B struct {
}

func main() {
a := "123"
fmt.Println(reflect.TypeOf(a)) //string
var b B
fmt.Println(reflect.TypeOf(b)) //main.B
}

为什么要检测变量类型?

  1. 前端传过来的数据,比如说bool,int你不知道会转化成什么类型
  2. 使用别人的库,你也不知道实例是什么类型

gin拿到的都是string

· 9 min read

struct

Go 语言 为我们提供了几种基本的内置 数据类型,同时,我们可以通过对着几种内置的数据类型的任意组合组装出新的数据类型,这就是 Golang 中的结构体类型。

Golang 中结构体类型的 关键字 是 struct

struct组成

结构体成员是由一系列的成员 变量 构成的,这些成员变量也被称为 “字段”。字段具有以下特性:

  1. 字段拥有自己的类型和值。
  2. 字段名必须唯一。
  3. 字段的类型可以是任意的内置数据类型,也可以是结构体类型。

定义

Go 语言 中 结构体 的定义使用 type 关键字类型名加 struct 关键字的形式。结构体中的每一个成员被称为字段,结构体中每一个字段的 类型 可以是 Golang 中的任意类型也可以是结构体类型。

//定义了一个名为 StructName 的结构体,结构体有三个字段。
type StructName struct {
Field1 FieldType1 [Tag1]
Field2 FieldType2 [Tag2]
Field3 FieldType3 [Tag3]
...
}

说明:

定义了一个名为 StructName 的结构体,结构体有三个字段,结构体的字段分别为 Field1、Field2 和 Field3,他们的类型分别为 FieldType1、FieldType2 和 FieldType3。

结构体每个字段后面,都可以有一个可选的 Tag,该 Tag 可以用于说明该字段的意义,比如可以通过 Tag 指定结构体序列化为 Json 时,所使用的字段名。

3种方式实例化struct

Go 语言 中 结构体 定义完之后,并不能直接使用,因此此时还没有分配内存,因此,我们必须在定义完结构体,并实例话之后才可以使用结构体。

Golang 中结构体的实例化主要有三种方式,分别为:使用 变量 定义的方式、使用 new 分配内存和使用 & 符号定义结构体。

1, 变量定义方式

使用 var 关键字定义一个名为 StructName 的结构体变量 varName,后续我们就可以直接使用该变量

//语法
var varName StructName

例子:

//定义一个 Person 结构体,并给结构体赋值
package main

import (
"fmt"
)

func main() {
//定义一个 Person 结构体,并给结构体赋值
type Person struct {
Name string
Age int
}
var p Person
p.Name = "scott"
p.Age = 18
fmt.Println("Pserson =", p)
}

2, new分配内存方式

使用 new 关键字为变量 varName 分配内存。

//语法
var varName = new(StructName)

例子:

package main

import (
"fmt"
)

func main() {
//使用new分配内存的方式,实例化一个名为 Student 的结构体
type Student struct {
Name string
Age int
Score float64
}
var stu = new(Student)
stu.Name = "SCOTT"
stu.Age = 1998
stu.Score = 100.0
fmt.Println("Student =", stu)
}

3, &符号定义结构体

使用 & 定义一个名为 varName 的结构体,使用 & 符号定义时,结构体名后面必须要加 {}

//语法
var varName = &StructName{}

例子:

package main

import (
"fmt"
)

func main() {
//使用 & 符实例化结构体 Student
type Student struct {
Name string
Age int
Score float64
}
var stu = &Student{}
stu.Name = "SCOTT"
stu.Age = 1998
stu.Score = 100.0
fmt.Println("Student =", stu)
}

struct使用实例

1, 普通struct

//定义一个 Person 结构体,并给结构体赋值
package main

import (
"fmt"
)

func main() {
//定义一个 Person 结构体,并给结构体赋值
type Person struct {
Name string
Age int
}
var p Person
p.Name = "scott"
p.Age = 18
fmt.Println("Pserson =", p)
}

解析:我们定义了一个结构体 Person,该结构体有两个字段,一个是 string 类型的 Name,一个是 int 类型的 Age。 接着我们定义了一个 Person 结构体类型的变量 p,并给结构体变量 p 的两个字段赋值,最后使用 print 打印结构体变量。

运行

2, struct嵌套struct

结构体的类型名还可以是一个结构体类型

注意

内部结构体字段会直接暴露给外面的结构体 比如下面的例子:Student.Name其实访问的就是Person.Name

package main

import (
"fmt"
)

func main() {
type Person struct {
Name string
Age int
}
// 结构体的类型名还可以是一个结构体类型
type Student struct {
P Person
Score float64
}
var stu Student
stu.P.Name = "scott"
stu.P.Age = 1998
stu.Score = 100.0
fmt.Println("Student =", stu)
}

3, 内嵌的struct是可以简写的

package main

import (
"fmt"
)

func main() {
type Person struct {
Name string
Age int
}
// 结构体的类型名还可以是一个结构体类型
type Student struct {
Person
Score float64
}
var stu Student
stu.Name = "scott"
stu.Age = 1998
stu.Score = 100.0
fmt.Println("Student =", stu)
}

运行:

下面的写法和上面的等效:

package main

import (
"fmt"
)

func main() {
type Person struct {
Name string
Age int
}
// 结构体的类型名还可以是一个结构体类型
type Student struct {
Person
Score float64
}
var stu Student
stu.Person.Name = "scott"
stu.Person.Age = 1998
stu.Score = 100.0
fmt.Println("Student =", stu)
}

4,另一种初始化方式

package main

import (
"fmt"
)

func main() {
type Person struct {
Name string
Age int
}
// 结构体的类型名还可以是一个结构体类型
type Student struct {
Person
Score float64
}

stu := Student{
Person: Person{
Name: "scott",
Age: 1998,
},
Score: 100.0,
}

fmt.Println("stu.Name:", stu.Name)
fmt.Println("stu.Age:", stu.Age)
fmt.Println("stu.Score:", stu.Score)
fmt.Println("Student =", stu)
}

运行:

5,当字段名重复时,会根据表达式优先赋值

比如Person.Name=1,首先去Person里面找看有没有有Name字段,如果有直接赋值;如果没有会去其他结构体成员找Name字段,找到后就赋值

package main

import (
"fmt"
)

func main() {
type Person struct {
Name string
Age int
}
// 结构体的类型名还可以是一个结构体类型
type Student struct {
Person
Name string
Score float64
}
stu := Student{
Person: Person{
Name: "scott",
Age: 1998,
},
Score: 100.0,
Name: "test",
}

fmt.Println("stu.Name", stu.Name)

stu.Name = "赵娘子"
fmt.Println("stu.Name", stu.Name)
fmt.Println("stu.Person.Name", stu.Person.Name)

stu.Person.Name = "宋娘子"
fmt.Println("stu.Name", stu.Name)
fmt.Println("stu.Person.Name", stu.Person.Name)
}

运行:

总结

我们可以通过对着几种内置的数据类型的任意组合组装出新的数据类型,这就是 Golang 中的结构体类型。Golang 中结构体类型的关键字是 struct。

补充

结构体是对现实世界的描述,接口是对某一类行为的规范和抽象;通过它们我们可以实现代码的抽象和复用,同时可以面向接口编程,把具体细节隐藏起来,让写出的代码更灵活,适应能力也更强!

· 6 min read

1. 匿名结构体

Go 语言 中 结构体 支持匿名结构体,匿名结构体没有 类型 名称,无须通过 type 关键字定义就可以直接使用。

语法
s := struct {
// 匿名结构体字段定义
Field1 Field1Type
Field2 Field2Type

}{
// 字段值初始化
Field1: Value1,
Field2: Value2,

}

说明:定义一个匿名结构体,该结构体有两个字段,字段名分别为 Field1 和 Field2,字段类型为 Field1Type 和 Field2Type。

接着,我们直接初始化字段,将 Filed1 字段的值设置为 Value1,将 Field2 的字段的值设置为 Value2。

实例

定义一个匿名结构体,并初始化

package main

import (
"fmt"
)

func main() {
//定义一个匿名结构体,并初始化
stu := struct{
Name string
Age int
Score float64
}{
"SCOTT",
18,
99.5,
}
fmt.Println("Student =", stu)
}

运行:

2. 结构体嵌套

Go 语言 中 结构体 支持结构体嵌套,即一个结构体里可以存放另一个结构体。嵌套结构初始化时,也支持列表初始化的方式和键值对初始化的方式。

语法
s := struct {
// 结构体嵌套字段定义
Field1 Field1Type
Field2 StructType

}{
// 字段值初始化
Field1: Value1,
Field2: Value2,

}

说明:定义一个结构体嵌套,该结构体有两个字段,字段名分别为 Field1 和 Field2,字段类型为 Field1Type 和 StructType。

接着,我们直接初始化字段,将 Filed1 字段的值设置为 Value1,将 Field2 的字段的值设置为 Value2。其中,Field2 的字段类型为 StructType,即是一个结构体类型。

package main

import (
"fmt"
)

func main() {

type Person struct {
Name string
Age int
}
type Student struct {
P Person
Score float64
}
//定义一个结构体嵌套,使用列表初始化
stu := Student{
Person{
"scott",
20,
},
60,
}
fmt.Println("Student =", stu)
}

说明:首先,我们定义了一个结构体 Person,该结构体有两个字段,接着再次定义了一个结构体 Student,该结构体有两个字段。

Student 结构体第一个字段类型为 Person,也是一个结构体类型,第二个字段是一个 float64 类型的字段。

接着,我们初始化结构体时,首先初始化 Person 类型的字段,Person 字段有两个字段,分别为 Name 和 Age,接着初始化结构体的 Score 字段。

package main

import (
"fmt"
)

func main() {
type Person struct {
Name string
Age int
}
type Student struct {
P Person
Score float64
}
//定义一个结构体嵌套,使用键值对初始化
stu := Student{
P: Person{
Name: "scott",
Age: 20,
},
Score: 60,
}
fmt.Println("Student =", stu)
}

说明:首先,我们定义了一个结构体 Person,该结构体有两个字段,接着再次定义了一个结构体 Student,该结构体有两个字段,接着,我们使用键值对的方式初始化结构体。

package main

import (
"fmt"
)

func main() {
type Person struct {
Name string
Age int
}
type Student struct {
P Person
Score float64
}
//定义一个结构体嵌套,使用键值初始化指定字段
stu := Student{
P: Person{
Name: "scott",
Age: 20,
},
}
fmt.Println("Student =", stu)
}

说明:首先,我们定义了一个结构体 Person,该结构体有两个字段,接着再次定义了一个结构体 Student,该结构体有两个字段,接着,我们使用键值对的方式初始化结构体 Student 中的 P 字段。

总结

1, Go 语言中结构体支持匿名结构体,匿名结构体没有类型名称,无须通过 type 关键字定义就可以直接使用。匿名结构体定义初始化语法:

s := struct {
// 匿名结构体字段定义
Field1 Field1Type
Field2 Field2Type

}{
// 字段值初始化
Field1: Value1,
Field2: Value2,

}

2, Go 语言中结构体支持结构体嵌套,即一个结构体里可以存放另一个结构体。嵌套结构初始化时,也支持列表初始化的方式和键值对初始化的方式。结构体嵌套定义语法:

s := struct {
// 结构体嵌套字段定义
Field1 Field1Type
Field2 StructType

}{
// 字段值初始化
Field1: Value1,
Field2: Value2,

}

· 6 min read

1. 列表初始化

Go 语言 中 结构体 变量的初始化的方式有两种,分别为:

  • 使用列表对字段挨个赋值
  • 使用键值对赋值 的方式。

如果我们使用列表对字段挨个赋值的方式初始化结构体,那么结构体的每个字段都必须要要赋值,否则程序报错。并且使用列表初始化的方式定义结构体时,最后一个字段也需要加逗号结尾符。

//语法
varName := StructName{
Field1Value,
Field2Value,
Field3Value,
...
}

说明:我们只需要定义一个该结构体类型的 变量 名,接着在大括号里面对结构体的每个字段挨个设置值。这里的每个字段都必须设置值,如果不设置,则程序报错。

举例:

package main

import (
"fmt"
)

func main() {
//使用列表初始化的形式定义结构体变量
type Student struct {
Name string
Age int
Score float64
}
var stu = Student{
"scott",
1998,
100.0,
}
fmt.Println("Student =", stu)
}

运行:

说明:我们定义了一个结构体 Student,该结构体初始化有三个字段,一个是 string 类型的 Name,一个是 int 类型的 Age,还有一个 float64 类型的 Score。

接着我们使用列表初始化的形式定义结构体变量,并为结构体变量的每个字段赋值。

使用列表初始化的形式定义结构体变量,必须每个字段都赋值

举例:

package main

import (
"fmt"
)

func main() {
//使用列表初始化的形式定义结构体变量,必须每个字段都赋值
type Student struct {
Name string
Age int
Score float64
}
var stu = Student{
"scott",
1998,
}
fmt.Println("Student =", stu)
}

运行:

说明:我们定义了一个结构体 Student,该结构体初始化有三个字段,但我们初始化赋值的时候,只给了两个字段赋值,因此我们的程序报错。

2. 键值对初始化

举例:

package main

import (
"fmt"
)

func main() {
//使用键值对的形式初始化结构体变量
type Student struct {
Name string
Age int
Score float64
}
var stu = Student{
Name: "SCOTT",
Age: 1998,
Score: 100.0,
}
fmt.Println("Student =", stu)
}

运行:

说明:我们使用键值对的形式初始化结构体时,结构体里面的键值对的顺序不需要跟结构体里面的顺序一致。我们定义结构体的顺序为 Name、Age 和 Score,但我们赋值的顺序为 Age,Name 和 Score。

使用键值对的形式初始化结构体变量,可以省略不必要的值的赋值

package main

import (
"fmt"
)

func main() {
//使用键值对的形式初始化结构体变量,可以省略不必要的值的赋值
type Student struct {
Name string
Age int
Score float64
}
var stu = Student{
Name: "scott",
}
fmt.Println("Student =", stu)
}

我们定义了一个结构体 Student,该结构体初始化有三个字段,一个是 Name,一个是 Age,还有一个 Score,接着,我们显式使用字段名的方式给结构体的字段赋值,但我们只给字段 Name 赋值。

这时,程序没有报错,没有被赋值的字段名使用该字段名变量类型的默认值进行初始化。

3. 总结

1, 我们使用列表对字段挨个赋值的方式初始化结构体,那么结构体的每个字段都必须要要赋值,否则程序报错。并且使用列表初始化的方式定义结构体时,最后一个字段也需要加逗号结尾符。列表初始化结构体语法:

varName := StructName{
Field1Value,
Field2Value,
Field3Value,
...
}

2, 如果使用键值对赋值的方式初始化结构体,没有被赋值的字段将使用该字段 类型 的默认值,如果使用键值对赋值的方式初始化结构体,那么我们可以有选择的指定赋值的字段。键值对初始化结构体语法:

varName := StructName{
Field1:Field1Value,
Field2:Field2Value,
...
}

· 3 min read

myfmt简介

myfmt是我自己在github上开源的一款代码格式化工具,格式化golang

why myfmt

golang代码通过格式化可以使风格统一, 这样即使是不同的程序员看到同样一份代码头也不会变得那么大

你可以通过下面的途径来格式化你的 go code

  • 手动使用 go fmt
  • IDE保存的时候,自动格式化

既然IDE都能搞定的事,为何又要折腾?

因为我的工作环境仅支持vim 和 sublime 2种环境,goland会很卡

sublime之前配的有golang代码保存自动格式化,但是如果你不小心开了PS or AI,那么一保存

你会发现即使一个小小的修改你都会卡半天,很浪费时间,所以我就把sublime的格式化配置删掉了;

这样就导致我必须经常切换到不同的目录去go fmt,这显得很机械,显然把宝贵时间浪费在fmt上是很不理智的,于是myfmt应运而生,你可以把它看做是go fmt的升级版

myfmt特性

  • 第一次运行,当前文件夹及其子文件夹的所有go文件都会被格式化
  • 再次运行,只会格式化被修改后的文件

这样你在github上提交代码的时候就不会混乱,既美化了代码,又只会在对应的文件上打上你想要的commit,完美!

安装和使用

# download
go get https://github.com/scott-x/myfmt.git
cd myfmt
# init database
go run db/bin/init.go
# install
go install github.com/scott-x/myfmt
# use
myfmt

演示: