问道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: 内置函数Copy
returns 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 ...
随机推荐
- 目标检测yolov5检测火龙果
一.github官方网址 https://github.com/ultralytics/yolov5/tree/v6.1 二.labelme标记数据集: (1).进入虚拟环境 (2).pip inst ...
- Java 多线程 术语
并行和并发:并发偏重于多个任务交替执行.并行是真正意义上的"同时执行".但两者的最终效果是一样的: 同步和异步:同步是指在同一时间里,一个同步方法调用后需要返回后,才能继续后续的行 ...
- S家lic
1,用1patch里对应的工具patch 2019的s家的工具2,用ocad里的lmgrd和snpslmd替换s家的scl里的3,用scl_keygen产生license,再用fix.bat,添加si ...
- Git配置新学
Git中的AutoCRLF与SafeCRLF换行符问题 https://zhuanlan.zhihu.com/p/380574688 https://xiaozhuanlan.com/topic/40 ...
- ssh scp 相关
1. 设置ssh 的免密登录 1> 将 ~/.ssh/id_rsa.pub文件中的内容拷贝到 远程host的 ~/.ssh/authorized_keys文件中 2> ssh-copy-i ...
- 在mysql中正常查询的句子,在C#中出错,原因是定义了变量。
在C#中 查询一样. 运行报错 Fatal error encountered during command execution." 命令执行过程中碰到的致命错误." MySqlE ...
- 使用idea2021.1.3新建一个Web项目教程
使用idea2021.1.3新建一个Web项目教程 文章目录 一.新建项目 二.在WEB-INF下创建classes,lib文件夹 三.配置WEB容器(tomcat Server) 一.新建项目 点击 ...
- ④ 版本② axios 封装
HttpRequestBase 类 1 构造函数 constructor(baseUrl) { const basePort = getUrlPort(baseUrl); this.baseUrl = ...
- 创建sqlSession对象操作数据库
1.加载核心配置文件 //加载mybatis核心配置文件,获取SqlSessionFactory String resource = "mybatis-config.xml"; I ...
- 社团管理系统(AMS)个人总结
一.展示 所在小组:13组 源代码链接:https://github.com/xupppp/ams 博文链接: https://www.cnblogs.com/xupppp/p/11795218.ht ...