Skip to main content

· 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

演示:

· 2 min read
#( 强制用A覆盖B)
sudo ln -sf A B

#查看eth0的MAC地址 DHCP静态ip设置会用到
cat /sys/class/net/eth0/address

# 查看当前目录下的所有文件列表
ls -al
ls -alt # 按时间排序: 时间最近的在前面
ls -altr # 按时间反向排序: 时间最近的在后面

# 查看盘符
sudo fdisk -l

# 查看磁盘使用情况
df -h

# 把/xxx/xxx 挂载到 /some/dir
sudo mount /xxx/xxx /some/dir

# 查看关于a的进程
ps -ef |grep a
ps -p pid -v

# kill process
kill pid

# 检测流量
sudo iftop

# 查看当前主机状态
top

# 添加用户xxx
sudo adduser xxx

# 把xxx用户添加到sudo group中
sudo usermod -aG sudo xxx

sudo chown -R xxx:xxx /some/dir

# update passwd
sudo passwd username

nc -z host port # 如果有返回则表示此端口有服务

# tcp port: 查看tcp service
netstat -tuna | grep LISTEN

#使用ps命令找出占用内存资源最多的20个进程(数量可以任意设置)
ps aux | head -1;ps aux |grep -v PID |sort -rn -k +4 | head -20

eg:

· One min read

\S* vs .*

  • *表数目
  • .表任意字符(包含空白字符,不含\n)范围更大
  • \S表非空字符,相对来说范围更小更精准
package main

import (
"fmt"
"io/ioutil"
"regexp"
)

var re = regexp.MustCompile(`"(\S*).github-scott-bear.workers.dev"`)

func main() {
bs, _ := ioutil.ReadFile("config.json")
arr := re.FindAllStringSubmatch(string(bs), -1)
fmt.Println(arr[0][1]) //young-leaf-8425
}

· One min read

为什么你的shell脚本在shell里可以正常执行,但是到了crontab就gg了?

正如上面所说,使用crontab时一定要注意:

  • 程序中不能出现任何相对路径
  • 程序中不能出现任何环境变量
  • 补充: 如果有block的程序结尾需要加 &, 否则后面的配置将会被block住,如@reboot /home/store/go/src/github.com/scott-x/export_ipv6/export_ipv6 &

crontab 定时任务各字段意义

· 2 min read

夏日炎炎,是不是该给树莓派降降温了?

关于降温,在温控开关的加持下,我有2套方案:

  • 1, 风扇降温:优点操作简单;缺点噪音大,运转占比时间长
  • 2, 水冷降温:优点能狠狠地把温度压到某个范围(我用的是这个);缺点:需要自己动手组装,要点成本
温馨提示

如果你想给树莓派组装一套水冷系统,可以点击页面最底部的email私密我。

如果你通过pin脚来控制风扇的话,那么获取树莓派温度是个绕不开的问题

原理很简单,CPU会时时把温度写入/sys/class/thermal/thermal_zone0/temp这个文件中,我们要做的就是读取并解析

$ cat /sys/class/thermal/thermal_zone0/temp                    
41868

下面就是示例:

package main

import (
"fmt"
"io/ioutil"
"os"
"strconv"
"strings"
"time"
)

func main() {
for {
time.Sleep(time.Second)
fmt.Printf("%s: CPU 温度 %.2f\n", time.Now().Format("2006-01-02 15:03:04"), GetTemperatureFromPi())
}
}

func isExist(file string) bool {
_, err := os.Stat(file)
if err != nil {
return false
}
return true
}

//get temperature of raspberry pi
func GetTemperatureFromPi() float64 {
var result float64
var file = "/sys/class/thermal/thermal_zone0/temp"
if isExist(file) {
bs, err := ioutil.ReadFile(file)
if err != nil {
return 0
}
data := strings.TrimSpace(string(bs))
result, _ = strconv.ParseFloat(data, 64)
result, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", result/1000), 64)
}
return result
}

· 3 min read

定义

在HTML5中,新加入了一个localStorage特性,这个特性主要是用来作为本地存储来使用的,解决了cookie存储空间不足的问题(cookie中每条cookie的存储空间为4k),localStorage中一般浏览器支持的是5M大小,这个在不同的浏览器中localStorage会有所不同。

localStorage的优势与局限

localStorage的优势

1、localStorage拓展了cookie的4K限制

2、localStorage会可以将第一次请求的数据直接存储到本地,这个相当于一个5M大小的针对于前端页面的数据库,相比于cookie可以节约带宽,但是这个却是只有在高版本的浏览器中才支持的

localStorage的局限

1、浏览器的大小不统一,并且在IE8以上的IE版本才支持localStorage这个属性

2、目前所有的浏览器中都会把localStorage的值类型限定为string类型,这个在对我们日常比较常见的JSON对象类型需要一些转换

3、localStorage在浏览器的隐私模式下面是不可读取的

4、localStorage本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡

5、localStorage不能被爬虫抓取到

localStorage与sessionStorage的唯一一点区别就是localStorage属于永久性存储,而sessionStorage属于当会话结束的时候,sessionStorage中的键值对会被清空

关于runoob对的localStorage理解

获取对象

var storage=localStorage;

完整格式为:var storage = window.localStorage,由于window是全局对象,so window可以省略。

三种写入方式

   //写入a字段     
storage["a"]=1;
//写入b字段
storage.b=1;
//写入c字段
storage.setItem("c",3);

针对不同情况,大家可以自行选择使用。

清除所有

storage.clear();

清除一对

1, 写入我们接口返回的数据(json格式)

var storage=localStorage; 
var data={
name:'xiecanyong',
sex:'man',
hobby:'program'
};
var d=JSON.stringify(data);
storage.setItem("data",d);
//将JSON字符串转换成为JSON对象输出
var json=storage.getItem("data");
var jsonObj=JSON.parse(json);
console.log(typeof jsonObj);

注意: 在localStorage中只能以字符串的形式进行保存,所以在存入localStorage之前,我们需要将json格式的数据进行转化成string格式。

2, 遍历localStorage中的所有数据

for(var i=0; i<localStorage.length;i++){          
console.log('第'+i+'条数据key为:'+localStorage.key(i)+',value为:'+localStorage.getItem(localStorage.key(i)));
}

其中: localStorage.key(i),表示在lcoalStorage中第i下标的key值。

· 2 min read

下图是单体架构模拟用户登录的过程

  • 1,用户在客户端输入用户名和密码
  • 2,web服务器去数据库中查询用户
  • 3,失败,则返回401;成功则返回用户
  • 4, 生成一条session
  • 5,设置sessionid到cookie中
  • 6,下次用户再请求的时候,浏览器的header就会带上这个cookie
  • 7,组件(也就是中间件)会检查sessionid的合法性,从而判断是否要放行/拦截

微服务是独立的

但是微服务就不一样的,因为微服务是独立的,这就意味着单体服务和数据库是一起的

如上图,用户成功登录后,拿着sessionid来访问商品服务,由于商品服务是没有这个sessionid的则会出问题

解决办法

可以用redis共享session