惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Troy Hunt's Blog
Scott Helme
Scott Helme
T
Threat Research - Cisco Blogs
T
Tenable Blog
L
LINUX DO - 热门话题
V
Visual Studio Blog
I
Intezer
Blog — PlanetScale
Blog — PlanetScale
Cisco Talos Blog
Cisco Talos Blog
A
Arctic Wolf
C
Cyber Attacks, Cyber Crime and Cyber Security
F
Fortinet All Blogs
aimingoo的专栏
aimingoo的专栏
Know Your Adversary
Know Your Adversary
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
N
Netflix TechBlog - Medium
SecWiki News
SecWiki News
I
InfoQ
Microsoft Security Blog
Microsoft Security Blog
Project Zero
Project Zero
W
WeLiveSecurity
Microsoft Azure Blog
Microsoft Azure Blog
A
About on SuperTechFans
Recorded Future
Recorded Future
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Vercel News
Vercel News
S
Securelist
Spread Privacy
Spread Privacy
L
LangChain Blog
云风的 BLOG
云风的 BLOG
G
Google Developers Blog
MongoDB | Blog
MongoDB | Blog
Google DeepMind News
Google DeepMind News
Recent Commits to openclaw:main
Recent Commits to openclaw:main
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
CERT Recently Published Vulnerability Notes
罗磊的独立博客
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
The Last Watchdog
The Last Watchdog
Attack and Defense Labs
Attack and Defense Labs
博客园 - 司徒正美
Help Net Security
Help Net Security
L
Lohrmann on Cybersecurity
人人都是产品经理
人人都是产品经理
Forbes - Security
Forbes - Security
Hacker News - Newest:
Hacker News - Newest: "LLM"
PCI Perspectives
PCI Perspectives
博客园 - 【当耐特】
T
Tor Project blog

PixelNest

会议是效率的杀手 国庆出游记录 对居家办公的一些想法 2022 春节 《搏击俱乐部》的结局 纪念 PAI 头一回达到 100 卡片笔记法 《刑法学讲义》的部分摘录 迁移博客 《UNIX 传奇》摘录 就地过年 一点记录:docker-compose 编排不同项目间的网络 近期读的几本书 办新身份证 git 获取其他分支的单个文件或目录 我的高考成绩 推荐 2020 年上半年的 8 部影视作品 ChromeOS 开发者模式 终于下单了《守望者》 联通来电管家服务 一些实用的 bash 代码片段 关于 鸟枪换炮 《架构整洁之道》摘录 惨淡的星球大战 旧笔记本电脑 if...then... 命令行检测 SSL 证书过期时间 《程序员的职业素养》读书笔记 2019 国庆假期 在 Ubuntu 里按空格键预览 《基本穿搭》笔记 我在 Ubuntu 里安装的一些软件 让人郁闷的英语学习过程 色彩理论基础 向百度主动推送网站链接 两部电视剧 《历史的教训》摘录 新工具 Koa 获取客户端 IP 备案了 apiDoc 基础语法 《代码整洁之道》摘录 BBC 纪录片 《逻辑的乐趣》 读不下去的《原则》 蓝牙鼠标唤醒休眠的 Ubuntu Google 发布会 2018 国庆假期 开发者的时间追踪和统计 使用 curl 发送 POST 请求的几种方式 《重来 2》读书笔记 Linux 和 MacOS 命令行转换图片用法整理 Win10 微软拼音添加小鹤双拼以及其他配置 针对不同场景临时禁用 eslint 规则的方法 《好妈妈胜过好老师》读书笔记 配置 Win10 Linux 子系统作为开发环境 又读完一本尤·奈斯博的小说 近期写 Go 的一点语法碎片整理 ffmpeg 合并 MP4 M4A 文件 睡前看手机的大问题 《局外人》读书笔记 菊次郎与佐纪 读书笔记 3 菊次郎与佐纪 读书笔记 2 菊次郎与佐纪 读书笔记 1 豆瓣图片服务器防盗链 《如何有效阅读一本书》读书笔记 - 3 《如何有效阅读一本书》读书笔记 - 2 《如何有效阅读一本书》读书笔记 - 1 Vim iskeyword 济南 图个乐 开始看不懂《西部世界》了 剧透 听到几个好故事 过周末 相亲 让人失望的 Apple 悬疑故事 AI 记忆 世纪三部曲 上帝的梦
《Go 语言实战》笔记
2018-09-03 · via PixelNest

上周末翻完了《Go 语言实战》这本书,还不错,篇幅不大,内容实用。书中有很多内容是这样写的:先给出一大段代码,然后一点一点拆解分析,稍微有些啰嗦。我先把 A Tour of Go 过了一遍,再看这本书算是更深入了一点。

看书过程中,标注了一些之前忽略或者生僻或者需要刻意重复练习的——或者是有趣的——知识点(自然是不全面的),整理在这里。

init 函数

程序中每个代码文件里的 init 函数都会在 main 函数执行前调用。

一个常见的用途:在 init 函数里配置日志参数,让程序一开始就能用 log 包按格式进行输出。

强制风格

为了让程序的可读性更强,Go 编译器不允许声明导入某个包却不使用。下划线 _ 让编译器接受这类导入,并且调用对应包内的所有代码文件里定义的 init 函数。

不仅不允许导入包却不使用,也不允许声明了变量后不使用。

Go 在语法格式上也有一些区别于其他语言的强制规定

  • 行尾不能加分号
  • 左大括号 { 不能另起一行
  • if 判断式 和 for 循环不用小括号括起来

与其让编译器告警,不如直接失败更有意义。

违法了上面提到的各项规则,都会让代码编译失败。

命名导入

如果导入的包有重名的,或者说是要导入两个一样名字的包(我觉得这通常不是什么好事),可以给导入的包另起一个名字,来解决命名冲突。

package main

import (
    "fmt"
    myfmt "mylib/fmt"
)

func main() {
    fmt.Println("Standard Library")
    myfmt.Println("mylib/fmt")
}

Go

数组, slice, map

数组和 slice

一旦声明,数组里存储的数据类型和数组长度就不能改变了。

同样类型的数组可以赋值给另一个数组。

这里说的同样类型,是指数组长度和每个元素的类型都相同。例如下面例子中的数组 a,它的类型是“拥有 4 个 int 元素的数组”。

根据内存和性能来看,在函数间传递数组是一个开销很大的操作。

如果函数的参数是数组,在函数间传递数组时,整个数组都会被复制一遍。

slice 作为函数参数时,不用担心像数组那样的开销。

由于与 slice 关联的数据包含在底层数组里,不属于 slice 本身,所以将 slice 复制到任意函数的时候,对底层数组大小都不会有影响。

一个例子

package main

import "fmt"

func main() {
    a := [4]int{1, 2, 3, 4}
    fmt.Printf("type of a, %T\n", a)
    fmt.Println("a", a)

    b := [4]int{}
    fmt.Printf("type of b, %T\n", b)
    fmt.Println("b", b)

    var c [4]int
    fmt.Printf("type of c, %T\n", c)
    fmt.Println("c", c)

    d := []int{1, 2, 3, 4}
    fmt.Printf("type of d, %T\n", d)
    fmt.Println("d", d)

    x := c
    x[2] = 100
    fmt.Printf("address of c, %p\n", &c)
    fmt.Printf("address of x, %p\n", &x)
    fmt.Println("c", c)
    fmt.Println("x", x)

    y := d
    y[2] = 100
    fmt.Printf("address of d, %p\n", &d)
    fmt.Printf("address of y, %p\n", &y)
    fmt.Printf("address of d[0], %p\n", &d[0])
    fmt.Printf("address of y[0], %p\n", &y[0])
    fmt.Println("d", d)
    fmt.Println("y", y)
}

a, bc 类型相同,都是“包含 4 个 int 元素的数组”。其中 a 中的四个元素被初始化了四个不同的值,bc 中的元素都是零值。

d 是一个 []int 类型的 slice

x := c
x[2] = 100
fmt.Printf("address of c, %p\n", &c)
fmt.Printf("address of x, %p\n", &x)
fmt.Println("c", c)
fmt.Println("x", x)

这一段代码的输出是

address of c, 0xc4200a80a0
address of x, 0xc4200a8120
c [0 0 0 0]
x [0 0 100 0]

可以看出把数组 c 复制给 x 时,整个数组都被复制了一遍,之后 xc 就没关联了,修改 x 中元素的值,不影响 c

y := d
y[2] = 100
fmt.Printf("address of d, %p\n", &d)
fmt.Printf("address of y, %p\n", &y)
fmt.Printf("address of d[0], %p\n", &d[0])
fmt.Printf("address of y[0], %p\n", &y[0])
fmt.Println("d", d)
fmt.Println("y", y)

这段代码的输出是

address of d, 0xc4200a6020
address of y, 0xc4200a6080
address of d[0], 0xc4200a8100
address of y[0], 0xc4200a8100
d [1 2 100 4]
y [1 2 100 4]

dy 的地址不同,不是同一个 slice,但是它们的底层数据是一样的,所以 d[0]y[0] 的地址相同,修改 y 中元素的值,也修改了 b 中元素的值,反之亦然。

map

map 的键可以是内置类型,也可以是结构体,只要这个值能用 == 运算符做比较。slice、函数以及包含 slice 的结构体,具有引用语义,不能作为 map 的键。

map 的值可以是任意类型,任意的值。

range 返回的不是对元素的引用

range 迭代(遍历)slice 时,range 创建了每个元素的副本,而不是直接返回对该元素的引用。

package main

import "fmt"

func main() {
    x := make(map[string]string)

    x["hello1"] = "world1"
    x["hello2"] = "world2"
    x["hello3"] = "world3"

    var y []*string

    for _, v := range x {
        y = append(y, &v)
    }

    fmt.Println(y)

    var sy []*string
    sx := []string{"world1", "world2", "world3"}
    for _, v := range sx {
        sy = append(sy, &v)
    }
    fmt.Println(sy)
}

下面是我电脑上的一次输出结果,可以看到每次迭代的 v 的地址是一样的。

[0xc42009a030 0xc42009a030 0xc42009a030]
[0xc42009a050 0xc42009a050 0xc42009a050]

golang 面试题这里的第二个题目与这个点相关。

嵌入类型

嵌入类型是将已有的类型直接声明在新的结构类型里

package main

import "fmt"

type writer interface {
        write()
}

type author struct {
        name  string
        email string
}

type admin struct {
        // 如果写成下面一行这样,就不是“嵌入类型”了,代码执行结果跟预期必然不一致
        // author author
        author
        role string
}

func (a *author) write() {
        fmt.Printf("Author %s write an article\n", a.name, a.email)
}

func Post(w writer) {
        w.write()
}

func main() {
        larry := admin{author: author{name: "Larry", email: "[email protected]"}, role: "admin"}
        fmt.Println(larry)
        Post(&larry)
}

输出结果是

{{Larry [email protected]} admin}
Author Larry write an article

由于内部类型的提升,内部类型实现的接口会自动提升到外部类型。这意味着由于内部类型的实现,外部类型也同样实现了这个接口。

具体到上面代码中,外部类型是 admin,内部类型是 useradmin 并没有实现 write 方法,调用了 usewrite 方法。如果外部类型实现了 write 方法,就不会去调用内部类型的 write 方法了。

func (a *admin) write() {
   fmt.Printf("Admin %s write an article\n", a.name, a.email)
}

输出就变成了

{{Larry [email protected]} admin}
Admin Larry write an article

关键字 iota

这是个有点古怪的关键字。乍一看我以为是 itoaatoi 之类的函数,其实它是在生命 const 变量时用的,itoa 起到自增的作用。

In some programming languages (e.g., A+, APL, C++, Go[6]) iota (either as a lowercase symbol ι or as a keyword iota) is used to represent and generate an array of consecutive integers. For example, in APL ι4 gives 1 2 3 4.

Ultimate Visual Guide to Go Enums 这篇文章有很详细的讲述。

Golang WiKi 上提到的两个例子:

type ByteSize float64

const (
    _           = iota // ignore first value by assigning to blank identifier
    KB ByteSize = 1 << (10 * iota)
    MB
    GB
    TB
    PB
    EB
    ZB
    YB
)

Weekday enum example - How iota is calculated


每种编程语法都可能有些坑,但是通常来说,刻意去制造出现“坑”的上下文,刻意纠结语法细节, 是没有必要的,甚至是有害的。在实际工作中,这是种要尽量避免的情况。

对于编程语言的学习,最好的教程还是官方的各种文档,不是要一气看完,边写边查也未尝不可。写得多了,自然水到渠成。