问道Golang,6月龄必知必会(二)
在我看来,golnag有许多反直观的设计,而且这些设计通常不能自圆其说,导致gohper一而再再而三的调入陷阱。
网上也有很多gohper总结了一些笔记,我再提炼精简一下,挂在脑图树上便于记忆。
值类型包括:所有integer、所有float、bool、string、数组和structure
引用类型包括:指针、slice、map、chan、interface
1. map需要先先初始化,才能使用
issue: 定义、不初始化使用,运行时panic;
solution:make关键字初始化或者使用map字面量
m := make(map[string]float64, 5)
m["pi"] = 3.14
mm := map[string]float64{"pi": 3.14, "pi1": 4.14} // map字面量
slice不需要初始化就可以被append.
var ss []int
ss = append(ss, 1, 2, 3)
fmt.Println(ss) // [1,2,3]
2. map中取得不存在的键,会返回零值,
issue:
func main() {
x := map[string]string{"one":"a","three":"c"}
if v := x["two"]; v == "" { // 不存在该键值对,也能返回零值“”
fmt.Println("true")
}
}
solution: 使用map取值的参数2 bool值来判断
func main() {
x := map[string]string{"one":"a","two":"","three":"c"}
if _,ok := x["two"]; !ok {
fmt.Println("no entry")
}
}
3. array作为函数实参, array内容不受函数内影响
issue: golang array 是值类型,形参是实参的内存拷贝
package main
import "fmt"
func changeFunc(arr [3]int) {
arr[0] = 222
}
func main() {
var arr [3]int = [3]int{1, 2, 3}
changeFunc(arr)
for i, item := range arr {
fmt.Printf("index : %d, item: %d \n\r", i, item)
}
}
solution: 实参从array改成引用类型的 slice。
slice切片的实质是SliceHeader 结构体,值传递slice时,正好将底层数组指针拷贝。
type SliceHeader struct {
Data uintptr // 底层数组的指针
Len int
Cap int
}
4. 变量遮蔽
issue: 块内声明的变量会遮蔽上层的同名变量n
func main() {
n := 0
if true {
n := 1
n++
}
fmt.Println(n) // 0
}
solution: 不要使用同名遮蔽变量,使用 n=1
5. 不可修改的字符串
issue:同大多数语言一样,golang的string是不可变的
func main() {
s := "hello"
s[0] = 'H'
}
# command-line-arguments
.\main.go:8:2: cannot assign to s[0] (value of type byte)
solution: 尝试通过byte/rune 中转
s := "hello"
buf := []rune(s)
buf[0] = 'H'
ss := string(buf)
fmt.Println(ss) // Hello
6. strings.TrimRight 并非剔除右侧后缀
issue: strings.TrimRight实际是将cutset字符串拆成字符,然后原字符串从右向左,直到遇到没有在cutset中国出现的字符。
fmt.Println(strings.TrimRight("ABBA", "BA")) // ""
fmt.Println(strings.TrimRight("ABBAABABCABAB", "BA")) // "ABBAABABC"
solution: 常规的移除后缀使用 strings.TrimSuffix
7. copy函数:最小化拷贝
issue: 内置函数Copyreturns the number of elements copied, which will be the minimum of len(src) and len(dst).
src := []int{1, 2, 3}
var dst []int // 此时len(dst) =0
copy(dst, src)
fmt.Println(dst) // []
solution: 初始化足够空间的dst
src := []int{1, 2, 3}
var dst []int = make([]int, 3)
copy(dst, src)
fmt.Println(dst) // [1,2,3]
或者使用append方法
src := []int{1, 2, 3}
var dst1 []int
dst1 = append(dst1, src...)
fmt.Println(dst1) // [1,2,3]
或者使用append方法
src := []int{1, 2, 3}
var dst1 []int
dst1 = append(dst1, src...)
fmt.Println(dst1) // [1,2,3]
append方法既可以加元素,也可以加切片,真是活见久。
// slice = append(slice, elem1, elem2)
// slice = append(slice, anotherSlice...)
8. range slice不是随机,range map是随机的
issue:
func main() {
ss := []int{11, 4, 5, 2, 7}
for i, item := range ss {
fmt.Printf(" %d : %d \r\n", i, item)
}
mm := map[string]int{"11": 11, "4": 4, "5": 5, "2": 2, "7": 7}
for i, m := range mm {
fmt.Printf(" %s : %d \r\n", i, m)
}
}
输出:
0 : 11
1 : 4
2 : 5
3 : 2
4 : 7 // slice
4 : 4 // map
5 : 5
2 : 2
7 : 7
11 : 11
9. 让人困惑的for-range 循环
golang中除了经典的三段式for循环外,还有帮助快速遍历 slice array map channel的 for range循环。
issue1:for range中操作迭代变量,原切片竟然没影响。
func main() {
ss := []int{1, 1, 1}
for _, x := range ss {
x = x + 1
}
fmt.Println(ss) // [1,1,1]
}
solution:操作索引值
func main() {
ss := []int{1, 1, 1}
for i,_ := range ss {
ss[i] += 1
}
fmt.Println(ss) // [2,2,2]
}
issue2: 这也是一个有意思的case, 迭代体内对于[修改array元素值]无意识, 对于[修改slice元素值]有意识, 活见久。
func main() {
aa := [2]int{0, 0}
for _, x := range aa {
fmt.Println(x) // print 0,0
aa[1] = 8
}
fmt.Println(aa) // print [0,8]
}
solution: 将array换成slice
ss := []int{0, 0}
for _, x := range ss {
fmt.Println(x) // print 0,8
ss[1] = 8
}
fmt.Println(ss) // print [0,8]
以上问题的关键是:
所有的 range 循环,Go 语言都会在编译期将原切片或者数组赋值给一个新变量 ha,在赋值的过程中就发生了拷贝,而我们又通过 len 关键字预先获取了切片的长度,所以在循环中追加新的元素也不会改变循环执行的次数,这也就解释了上面提到的现象。
而遇到这种同时遍历索引和元素的 range 循环时,Go 语言会额外创建一个新的 v2 变量存储切片中的元素,循环中使用的这个变量 v2 会在每一次迭代被重新赋值而覆盖,赋值时也会触发拷贝。
ha := a
hv1 := 0
hn := len(ha)
v1 := hv1
v2 := nil
for ; hv1 < hn; hv1++ {
tmp := ha[hv1]
v1, v2 = hv1, tmp
...
}
C#中没有这么多诡异的情况。
C#数组是定长数组,一旦被创建,数组大小就无法改变;
span 带有底层数组指针和长度,但是长度也是只读,是类型安全、内存安全的滑块。
10. nil值比较
issue: golang中:一个接口等于另一个接口,前提是它们的类型和动态值相同。这同样适用于nil值。
func Foo() error {
var err *os.PathError = nil
return err
}
func main() {
err := Foo()
fmt.Println(err) // print: <nil>
fmt.Println(err == nil) // print: false
}
solution: 强转为同一类型
fmt.Println(err == (*os.PathError)(nil)) // print: true
或者显式返回nil error
func returnsError() error {
if bad() {
return ErrBad
}
return nil
}
在底层,接口被实现为两个元素,一个类型T和一个值V,V是一个具体的值,比如int、结构体或指针,而不是接口本身,它的类型是T, 上面的错误示例中: err 具备了T=*MyError, V=nil 的实现,故与nil不等。
只要记住,如果接口中存储了任何具体的值,该接口将不会为nil.
最后再提供几张图,供大家参考,也许上面的坑位能柳暗花明。
(1)

[4]int在内存中的表示形式只是按顺序排列的四个整数值:
(2)

s = make([]byte,5)
切片是数组片段的描述符,它由指向数组的指针、片段的长度和它的容量(片段的最大长度)组成。
当我们对s进一步切片: s =s[2,4]

C# span是指向一段连续内存的类型安全的、内存安全的视图,也有数组指针和长度length,不过他的length是只读定长的,也不会有扩容的动作。
(3)

m := make(map[string]string)
m是指向Map Header数据结构的指针,Map Header包含了关于map的所有元信息:
- map中当前的条目数
- map中的桶数总是等于2的幂,因此存储log(桶)以保持较小的值
- 指向连续内存位置的桶数组的指针
- 创建不同的map的哈希种子是随机
https://phati-sawant.medium.com/internals-of-map-in-golang-33db6e25b3f8
问道Golang,6月龄必知必会(二)的更多相关文章
- 闻道Go语言,6月龄必知必会
大家好,我是马甲哥, 学习新知识, 我的策略是模仿-->归纳--->举一反三, 在同程倒腾Go语言一年有余,本次记录<闻道Go语言,6月龄必知必会>,形式是同我的主力语言C#做 ...
- 读书笔记汇总 - SQL必知必会(第4版)
本系列记录并分享学习SQL的过程,主要内容为SQL的基础概念及练习过程. 书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL i ...
- 读书笔记--SQL必知必会--建立练习环境
书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL in 10 Minutes - Fourth Edition> MyS ...
- 读书笔记--SQL必知必会12--联结表
12.1 联结 联结(join),利用SQL的SELECT在数据查询的执行中联结表. 12.1.1 关系表 关系数据库中,关系表的设计是把信息分解成多个表,一类数据一个表,各表通过某些共同的值互相关联 ...
- 读书笔记--SQL必知必会18--视图
读书笔记--SQL必知必会18--视图 18.1 视图 视图是虚拟的表,只包含使用时动态检索数据的查询. 也就是说作为视图,它不包含任何列和数据,包含的是一个查询. 18.1.1 为什么使用视图 重用 ...
- 《MySQL 必知必会》读书总结
这是 <MySQL 必知必会> 的读书总结.也是自己整理的常用操作的参考手册. 使用 MySQL 连接到 MySQL shell>mysql -u root -p Enter pas ...
- 《SQL必知必会》学习笔记(一)
这两天看了<SQL必知必会>第四版这本书,并照着书上做了不少实验,也对以前的概念有得新的认识,也发现以前自己有得地方理解错了.我采用的数据库是SQL Server2012.数据库中有一张比 ...
- SQL 必知必会
本文介绍基本的 SQL 语句,包括查询.过滤.排序.分组.联结.视图.插入数据.创建操纵表等.入门系列,不足颇多,望诸君指点. 注意本文某些例子只能在特定的DBMS中实现(有的已标明,有的未标明),不 ...
- .NET程序员项目开发必知必会—Dev环境中的集成测试用例执行时上下文环境检查(实战)
Microsoft.NET 解决方案,项目开发必知必会. 从这篇文章开始我将分享一系列我认为在实际工作中很有必要的一些.NET项目开发的核心技术点,所以我称为必知必会.尽管这一系列是使用.NET/C# ...
- 0005 《SQL必知必会》笔记01-SELECT语句
1.SELECT基本语句: SELECT 字段名1,···,字段名n FROM 表名 2.检索所有字段,用"*"替换字段名,这会导致效率低下 SELECT * FROM 表名; 3 ...
随机推荐
- python_列表(list)
列表用中括号表示,列表中的数据可以存储不同类型的数据,在实际开发中,列表中都是定义相同类型数据,可以对列表中的数据用相同的方法进行处理. 1, 通过index获取到对应的值. num_list = [ ...
- node.js请求css、js静态资源页面不生效
产生原因:文件响应头内容类型错误 解决方案:设置对应的响应头内容类型 const http = require('http'); const fs = require('fs'); const pat ...
- 什么是跨域及如何解决、json和jsonp
1.跨域: 出于浏览器的同源策略限制,同源策略会阻止一个域的javascript脚本和另外一个域的内容进行交互. 同源:即指在同一个域中,就是两个页面具有相同的协议(protocol),主机(host ...
- Ajax同步和异步的区别,如何解决跨域的问题
同步的概念应该是来自于OS中关于同步的概念:不同进程为协同完成某项工作而在先后次序上调整(通过阻塞,唤醒等方式),同步强调的是顺序性,谁先谁后,异步则不存在这种顺序性. 同步:浏览器访问服务器请求,用 ...
- ZSTUOJ刷题④:Problem B.--输出双层金字塔
Problem B: 输出双层金字塔 Time Limit: 1 Sec Memory Limit: 64 MBSubmit: 7860 Solved: 5834 Description 输出双层 ...
- Jmeter添加Plugins Manager插件管理器后增加常用base类函数
路径为/lib/ext/jmeter-plugins-manager-1.7.jar 放置即可打开插件管理器: 搜索Custom JMeter Functions后自动下载安装即可:
- bzoj 4176
题意:求$\sum_{i=1}^{n}\sum_{j=1}^{n}d(ij)$ 首先推一发式子: $\sum_{i=1}^{n}\sum_{j=1}^{n}d(ij)$ 有一个结论:$d(nm)=\s ...
- Spring之IOC(控制反转)入门理解
在面向对象编程中,我们经常处理处理的问题就是解耦,程序的耦合性越低表明这个程序的可读性以及可维护性越高(假如程序耦合性过高,改一处代码通常要对其他地方也要做大量修改,难以维护).控制反转(Invers ...
- IP协议数据包
Header Length:头部长度固定20字节,永远为5(4bit为单位) Total Length:头部+包, 抓包结果 Identification.Fragment Flags.Fragmen ...
- 监控室NTP/GPS同步时钟解决方案
深圳市立显电子有限公司,专业LED时钟生产厂家!--------[点击进入] 车站.机场.学校等场所监控室布置要求: 1.宜选择建筑物中环境噪声较小的声场所.如车站票务中心后台.机场保安值班室. ...