
重学 Go 语言:基础篇
文章平均质量分 93
79 章节内容解析 Go 语言基础知识点;使用反汇编、编程范式推导 Go 核心原理;从架构思想层面解析 Go 语言每个细节;与 C 语言对比学习 Go 基础核心;从 Go 语言背景知识到灵活使用,知其然知其所以然。
优惠券已抵扣
余额抵扣
还需支付
¥39.00
¥99.00
购买须知?
本专栏为图文内容,最终完结不会低于15篇文章。
订阅专栏,享有专栏所有文章阅读权限。
本专栏为虚拟商品,基于网络商品和虚拟商品的性质和特征,专栏一经购买无正当理由不予退款,不支持升级,敬请谅解。
YJingLee
我的博客:http://lyj.cnblogs.com
展开
-
专栏导读
Go 语言,你真的学会了吗?为什么是 Go?传统的编程语言都存在一些通用的缺点:学习成本高:诸如 C++ 要理解作者思想,Java 要学习大量库;编译速度慢:代码的编写、预处理、编译与运行流程花费时间太长;无法使用现代计算机发展:现代计算机发展迅速,C、C++ 生态项目迭代周期短:996 已经成为常态,任务压的又压,在如何提高团队生产效率,又能保证项目质量两者之前权衡成了一个比较重要...原创 2020-10-28 16:08:47 · 1190 阅读 · 0 评论 -
类型:变量的定义和初始化
变量是什么?抛开云山雾罩的官腔,变量的本质是什么?一块或多块用于存储可变数据的内存。确切的类型决定了内存长度和数据存储格式。用于引用该内存的符号名。 通过unsafe包提供的指针运算,可以将多个变量指向同一块内存,比如整数可以当做字节序列读出来,但这并不能改变原变量的类型。变量本身的确是一个相对来说比较抽象的概念,变量首先要引用一块内存,需要存储数据并且可修改数据,那这块内存...原创 2020-10-28 16:08:48 · 342 阅读 · 0 评论 -
类型:变量的语法规则
简短定义x := 123符号不同(:=)。必须显式提供初始化值。不能提供数据类型。只能用于函数内部。 这种简短定义必须有初始化值,同时不能提供类型,必须通过初始化值进行推导,只能用函数内部。在变量定义时候,Go 语言有个很特殊的习惯是在函数内部用简短定义方式,即有初始化值的时候可以省略类型,编译器通过初始化值推断出它的类型,相当于补全类型,因为有类型的变量编译器才知道分配...原创 2020-10-28 16:08:48 · 276 阅读 · 0 评论 -
类型:多变量赋值和命名建议
多变量赋值//go:noinline//go:nosplitfunc test() (int, int) { a, b := 1, 2 a, b = b+1, a+2 return a, b}func main() { a, b := test() println(a, b)}多变量赋值涉及计算规则问题,很多语言对于多变量赋值其实是个坑。Go...原创 2020-10-28 16:08:49 · 296 阅读 · 0 评论 -
类型:常量的定义 · 魔法数字
常量定义const x, s = 1, "abc"func main() { const a = 1 { const a = "abc" println(a) } println(a)}常量的定义跟变量差不多,我们可以进行类型推断,可以同时定义多个,可以在函数内部定义在包块内定义。const x int32 = 100...原创 2020-10-28 16:08:49 · 428 阅读 · 0 评论 -
类型:常量的本质 · 常量展开
常量展开const a = 100func main() { const b = 200 println(a, b)}$ go tool objdump -s "main\.main" testTEXT main.main(SB) main.go:7 CALL runtime.printlock(SB) main.go:7 MOVQ $0x64, 0(SP) m...原创 2020-10-28 16:08:50 · 264 阅读 · 0 评论 -
类型:枚举与 iota · 计数器
枚举是什么固定且有限的类别,比如春夏秋冬,亦或者KB/MB/GB/TB等。没有明确意义上的enum定义。借助iota实现常量组自增值。可使用多个iota,各自单独计数。中断须显式恢复。枚举是非常常见的类型,通常情况下指的是一种一连串或者连续性的定义,它的总数是固定的,比如星期、月份、容量、颜色。它有一定的规律并且可以用一连串顺序数字代替。枚举在其他语言里用的比较多,Go 语言里没...原创 2020-10-28 16:08:51 · 226 阅读 · 0 评论 -
类型:基本类型与空值
基本类型与空值相比 C 语言,Go 语言有明确的基本类型。C语言大多数类型不确定长度,int类型固定是 32 位的,long可能是4字节也可能是8字节,所以这样定义long long64位整数。Go 语言里面类型定义非常明确,官方文档中有张表格。基本类型除了很明确的类型以外,其中比较特殊的有几个,其中uintptr用来存储地址的整数,rune用来存储编码的码点,int32有点类似 UCS-2 ...原创 2020-10-28 16:08:51 · 195 阅读 · 0 评论 -
类型:引用类型 · new 和 make 的区别
所谓引用类型,是指其内部结构,而非分配于托管堆。slice、map、channel使用 make 或初始化语句创建实例。使用 new 无法有效初始化。 从实现角度看,除 slice、interface是结构体外,map、channel、function 都是指针。引用类型对初学者来说很容易造成误解。在 Go 语言里 slice、map、channel 三种类型称之为引用类型,J...原创 2020-10-28 16:08:52 · 275 阅读 · 0 评论 -
类型:引用类型 · 尽可能在栈分配内存
尽可能在栈分配内存func test() []int { s := make([]int, 3) s[0] = 0x100 s[1] = 0x200 s[2] = 0x300 return s}func main() { _ = test()}$ go build && go tool objdump -s "main...原创 2020-10-28 16:08:53 · 127 阅读 · 0 评论 -
类型:语法歧义与类型转换的坑
语法歧义func main() { x := 100 p := *int(&x) // cannot convert &x (type *int) to type int 正确写成(*int)(&x) println(p)}(*int)(p) --- 如果没有括号 --> *(int(p))(<-chan int)(c) ...原创 2020-10-28 16:08:53 · 167 阅读 · 0 评论 -
类型:自定义类型与别名
自定义类型type X intfunc main() { var a X = 1 var b int = a // cannot use a (type X) as type int in assignment _ = b} 以基础类型(underlying type)内存布局为模板,创建新类型。与基础类型属完全不同的两个类型。允许显式转换。除了...原创 2020-10-28 16:08:54 · 173 阅读 · 0 评论 -
表达式:位运算和 if 里的初始化语句
保留字保留字是语法上的一些关键字,和内置函数不同,大部分语言使用保留字作为符号名可能会出问题,所以不允许在编码中使用保留字。Go 语言内置函数在builtin包中,它不需要导入包就可以直接使用,编译器会把它翻译成特殊的函数调用。一些语言允许定义和内置函数相同的函数,我们建议内置函数、内置类型、语法上的保留字不要使用,因为很容易引起误解。运算符(Operators)每种语言都会有特殊的运算符,...原创 2020-10-28 16:08:55 · 257 阅读 · 0 评论 -
表达式:死代码与代码覆盖率
死代码//go:noinlinefunc count() int { return 3}func main() { if x := count(); x > 5 { println("a") } else if x > 7 { // dead code println("b") }}反汇编查看$ go bu...原创 2020-10-28 16:08:55 · 729 阅读 · 0 评论 -
表达式:使用 switch 改善 if 代码
switch 执行顺序switch语句case支持变量,有些语言必须是常量,不用显式的写break,很多语言不写break的话顺序往后执行,Go语言默认情况下自动终止。使用 switch 改善 if 代码在很多语言里面都有 if、switch,除了某些语言有限制看上去差不多,比如 C 语言要求 case 里面必须是常量。那么这两种分支有什么样的差异,什么时候该用,什么时候用 if,什么时候...原创 2020-10-28 16:08:56 · 468 阅读 · 0 评论 -
表达式:for range 复制行为
for range 复制行为复制要么复制指针,要么复制完整的对象,Go 语言影响最大的是数组。func rangeCopy() { x := [4]int{0x11, 0x22, 0x33, 0x44} for _, n := range x { // 遍历输出 println(n) } for i, n := range x...原创 2020-10-28 16:08:56 · 282 阅读 · 0 评论 -
函数:函数是第一类对象
概况函数是结构化编程的最小模块单元。不支持匿名嵌套。不支持重载(overload)。不支持默认参数。支持变参。支持多返回值和命名返回值。支持匿名函数和闭包。函数是结构化编程的最小单位。类似搭积木、函数是最小的积木块。函数不关心细节,我们不需要介入到函数内部,所以函数对我们来说名字(签名)很重要。流程控制的表现方式也是函数。所有的代码都是基于函数来封装的,方法也可以看做一种变相...原创 2020-10-28 16:08:57 · 210 阅读 · 0 评论 -
函数:函数使用性能
利用匿名函数重构作用域 以便 defer 能在合适时机执行// 错误的例子func example() { var m sync.Mutex for i := 0; i < 5; i++ { m.Lock() defer m.Unlock() println(i) }}这地方有个问题,example ...原创 2020-10-28 16:08:57 · 180 阅读 · 0 评论 -
函数:函数的参数
函数:参数func test(x int, y ...byte) { fmt.Printf("%T\n", y)}func main() { test(100, 1, 2, 3)} 参数内存由调用者分配,但被当作局部变量。 变参的本质只是个 slice 对象。函数有很多细节可能和你想象不太一样。比如一个函数调用,定义一个test函数,y称之为变参,不管语法怎...原创 2020-10-28 16:08:58 · 121 阅读 · 0 评论 -
函数:函数参数使用注意的地方
什么时候应该传递指针?函数传递参数传指针进去,拷贝指针,对值修改。func test(x *int) { *x += 100}func main() { x := 1 test(&x) println(x)}函数传递默认都是值拷贝,如果传的是x,把x拷贝一份,接下来对x的修改和外面没有关系。第二种是x有个指针p指向它,然后把p传递进去,复制...原创 2020-10-28 16:08:58 · 189 阅读 · 0 评论 -
函数:函数的多返回值
函数:多返回值//go:noinlinefunc test(x int) (int, int) { return x + 1, x + 2}func main() { a, b := test(100) println(a, b)}$ go build && go tool objdump -s "main\.main" test函数提供多...原创 2020-10-28 16:08:59 · 905 阅读 · 0 评论 -
函数:函数调用约定
调用堆栈和堆栈帧一个进程启动后,由线程来执行所有的代码,线程启动的时候首先分配一段内存,这段内存用来存储。分配内存有两种方式,第一种:分配所有线程的内存,每个线程都会有一段内存。不同的操作系统默认给线程分配的内存大小会不一样,有1MB或者10MB,另外有些程序比如Go语言会自主控制一个线程分配多少内存。我们把为线程分配的内存称之为栈,就是说所有线程带的内存通常称之为执行栈。栈基本的结构是先进...原创 2020-10-28 16:08:59 · 129 阅读 · 0 评论 -
函数:C 语言和 Go 语言函数汇编对比
参数传递 C 参数复制,返回值先看下 C 语言代码例子。#include <stdio.h>#include <stdlib.h>__attribute__((noinline)) void info(int x){ printf("info %d\n", x);}__attribute__((noinline)) int add(int x, i...原创 2020-10-28 16:09:00 · 221 阅读 · 0 评论 -
函数:优化模式对参数传递的影响
优化模式对参数传递的影响我们刚刚是完全把优化开关关掉的,所以我们画内存图的时候和源码可以一一对上。但是如果启动优化状态的时候,优化方式的时候未必对上号了。我们先以 C 为例,再以 Go 为例。$ gcc -g -O2 -o test test.c #代码优化模式$ objdump -d -M intel test | grep -A30 "<main>:"0000000400...原创 2020-10-28 16:09:00 · 219 阅读 · 0 评论 -
函数:函数调用汇编
用 GDB 查看调用堆栈,输出堆栈桢信息#include <stdio.h>#include <stdlib.h>__attribute__((noinline)) void info(int x){ printf("info %d\n", x);}__attribute__((noinline)) int add(int x, int y){ ...原创 2020-10-28 16:09:00 · 320 阅读 · 0 评论 -
函数:匿名函数的定义和原理
匿名函数在 JS 中我们在大量使用匿名函数,Java 或者 C# 类似 Lambda 表达式也是匿名函数,匿名函数已经成为现代语言很重点的标志。为什么会出现匿名函数呢?匿名函数自从 JS 流行以后使用有点泛滥的趋势,很多人都习惯使用匿名函数,因为写出来代码比较酷。简单与看上去好看不是一回事,简单是阅读代码没有太大干扰,同时可以把被调用的函数搬移到另外文件或者包中,甚至很容易替换,最关键对编译器...原创 2020-10-28 16:09:01 · 2130 阅读 · 0 评论 -
函数:匿名函数调用方式
匿名函数调用方式匿名函数调用方式有两种,第一种是作为返回值的匿名函数,第二种是直接调用匿名函数。上面的例子是作为返回值的匿名函数。作为返回值的匿名函数我们首先使用 GBD 调试上面的例子。$ gdb test$ l$ l$ b 16 #test()返回匿名函数f,f可以看作一个指针指向main.test.func1函数$ r #执行到断点位置$ info locals #查看局部...原创 2020-10-28 16:09:02 · 1438 阅读 · 0 评论 -
函数:闭包以及实现
何为闭包一个匿名函数引用了它的上下文对象,我们把这种状态称之为闭包。func test(x int) func() { println("test.x :", &x) return func() { println("closure.x :", &x, x) }}func main() { f := test(100) ...原创 2020-10-28 16:09:02 · 313 阅读 · 0 评论 -
函数:闭包的实现机制
闭包的实现机制我们对上面的代码进行 GBD 和反汇编看看究竟:$ gdb test$ l$ l$ b 13 #打断点$ r #执行$ set disassembly-flavor intel #设置intel样式$ disass #反汇编Dump of assembler code for function main.main: 0x0000000000450ac0 &l...原创 2020-10-28 16:09:03 · 193 阅读 · 0 评论 -
函数:递归调用
什么是递归调用函数的时候,比如 main 函数调用 add 时候,需要为 add 分配内存,我们管这个这个叫 Frame,如果 add 函数自己调用自己叫做递归,它调用的时候它会分配新的栈桢么?就是说自己调用自己的时候栈帧的状态是什么样子的?所谓的自己调用自己实质是什么,只不过执行了相同的代码,但是它依然会分配新的栈帧,一直往上面分配,只不过栈帧的内存状态未必是一样的,数据可能会不一样。.te...原创 2020-10-28 16:09:03 · 204 阅读 · 0 评论 -
函数:尾递归优化
什么是尾递归优化我们看 C 语言的一个例子:#include <stdio.h>#include <stdlib.h>int sum(int x){ if (x == 0) return 0; return x + sum(x - 1);}int tailsum(int x, int total){ if (x == 0) ret...原创 2020-10-28 16:09:04 · 220 阅读 · 0 评论 -
函数:延迟调用的用途
error vs exception在设计层面错误和异常代表了两种概念,实现上没有抽象概念,异常和错误实际上是抽象层面的事情。假设错误代表了意外,那么异常可以理解一种可控的意外。从 CPU 什么或者从汇编层面上来说,不存在错误和异常。因为哪怕就是一个浮点计算错误或许被零除 CPU 实际上操作系统对这个事情都是有预案的,它并没有导致计算机崩溃。那也就是不管 CPU,还是操作系统对这个事都有一...原创 2020-10-28 16:09:04 · 331 阅读 · 0 评论 -
函数:延迟调用实现方式
延迟函数到底怎么实现呢?每个defer语句都会形成一条deferproc注册调用。注册调用会把函数名称,函数参数,调用函数的宿主函数SP寄存器地址打包。调用函数的宿主函数SP寄存器地址是什么?func test(){ defer println("xxx")}func main(){ defer println(1) defer println(2) ...原创 2020-10-28 16:09:05 · 891 阅读 · 0 评论 -
函数:延迟调用的使用和性能损耗
3-18 延迟调用的使用和性能损耗18 延迟调用(defer)执行机制和时机18 如何在调用堆栈的任意环节终止执行18 不能确保延迟调用一定被执行18 延迟调用性能损耗3-18 延迟调用的使用和性能损耗18 延迟调用(defer)执行机制和时机还有一点所谓延迟调用的问题,我们知道返回一个匿名函数只是返回一个包装,但是并没有调用这个函数,这就存在延迟调用问题。fu...原创 2020-10-28 16:09:06 · 250 阅读 · 0 评论 -
函数:正确认识错误
3-19 正确认识错误19 错误分类19 错误处理19 使用实例或类型判断错误类别19 检查错误值19 检查错误类型19 常量错误值3-19 正确认识错误什么是error?和exception有什么区别?错误(异常)是一种“值”,属于正常逻辑返回(exception,error)Stuck in 70'sErrors just valueserror...原创 2020-10-28 16:09:06 · 268 阅读 · 0 评论 -
函数:panic 和 recover
3-20 panic和recover20 确保defer得以执行20 仅最后一个panic会被捕获20 必须在延迟函数中直接调用recover20 使用匿名函数保护代码片段20 error vs panic20 在defer/recover内再次panic的意义(log,rethrow)3-20 panic和recover相比error,panic、recove...原创 2020-10-28 16:09:07 · 217 阅读 · 0 评论 -
函数:使用 defer 改善错误处理
利用匿名函数重构作用域 以便defer能在合适时机执行// 错误的例子func example() { var m sync.Mutex for i := 0; i < 5; i++ { m.Lock() defer m.Unlock() println(i) }}这地方有个问题,example函数是...原创 2020-10-28 16:09:08 · 192 阅读 · 0 评论 -
数据:数组解析
数组我们站在汇编的角度基本上没有数据结构一说,只有纯数字。要么传地址,地址也是一些整数,要么就是把内存中一些字节从寄存器搬到内存或者从内存搬到寄存器或者做一些简单的数学运算。所以站在汇编的角度事情很简单很直接。这样的好处就是我们可以抛开很复杂的抽象理论去研究计算机本质是怎样工作的。那么带来的问题是在我们编程时不可能全部用数字来表达所有的东西,需要用抽象概念来表达这些数字。从本质上来说,不管是字...原创 2020-10-28 16:09:08 · 193 阅读 · 0 评论 -
数据:False Sharing 的问题
4-2 False Sharing 的问题02 数组指针 vs 指针数组01 是不是数组就一定能分配在栈上?02 False Sharing的问题4-2 False Sharing 的问题02 数组指针 vs 指针数组func main() { var a [3]int var b [3]*int var p *[3]int = &a...原创 2020-10-28 16:09:09 · 200 阅读 · 0 评论 -
数据:切片定义
4-3 切片定义03 切片定义03 切片为什么不是动态数组或数组指针4-3 切片定义很多语言都有切片,切片严格来说是一种概念,并不是一种明确的数据结构。树、哈希表是一种明确的数据结构。切片不管怎么实现,实际上是引用数组一个片段,要么是整个数组,要么是局部数组。引用局部的话必须有长度、容量,切片需要模拟动态数组特征必然存在扩容问题,扩容涉及到底层数组重新分配,重新分配是...原创 2020-10-28 16:09:09 · 6566 阅读 · 0 评论