mirror of
https://github.com/zhwei820/learn.lianglianglee.com.git
synced 2025-11-16 14:13:47 +08:00
fix img
This commit is contained in:
@@ -195,7 +195,7 @@ Hello, 世界
|
||||
<h3>Go 语言环境搭建</h3>
|
||||
<p>要想搭建 Go 语言开发环境,需要先下载 Go 语言开发包。你可以从官网 <a href="https://golang.org/dl/">https://golang.org/dl/</a> 和 <a href="https://golang.google.cn/dl/">https://golang.google.cn/dl/</a> 下载(第一个链接是国外的官网,第二个是国内的官网,如果第一个访问不了,可以从第二个下载)。</p>
|
||||
<p>下载时可以根据自己的操作系统选择相应的开发包,比如 Window、MacOS 或是 Linux 等,如下图所示:</p>
|
||||
<p><img src="assets/CgqCHl-ikW2AdldmAABgiiXVyCo654.png" alt="go_sdk_download.png" /></p>
|
||||
<p><img src="assets/CgqCHl-ikW2AdldmAABgiiXVyCo654.png" alt="png" /></p>
|
||||
<h4>Windows MSI 下安装</h4>
|
||||
<p>MSI 安装的方式比较简单,在 Windows 系统上推荐使用这种方式。现在的操作系统基本上都是 64 位的,所以选择 64 位的 go1.15.windows-amd64.msi 下载即可,如果操作系统是 32 位的,选择 go1.15.windows-386.msi 进行下载。</p>
|
||||
<p>下载后双击该 MSI 安装文件,按照提示一步步地安装即可。在默认情况下,Go 语言开发工具包会被安装到 c:\Go 目录,你也可以在安装过程中选择自己想要安装的目录。</p>
|
||||
|
||||
@@ -188,7 +188,7 @@ fmt.Println("the sum is",sum)
|
||||
<pre><code>array:=[5]string{"a","b","c","d","e"}
|
||||
</code></pre>
|
||||
<p>数组在内存中都是连续存放的,下面通过一幅图片形象地展示数组在内存中如何存放:</p>
|
||||
<p><img src="assets/Ciqc1F-pBzmAWUQ0AAAttSjgTjQ158.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/Ciqc1F-pBzmAWUQ0AAAttSjgTjQ158.png" alt="png" /></p>
|
||||
<p>可以看到,数组的每个元素都是连续存放的,每一个元素都有一个下标(Index)。下标从 0 开始,比如第一个元素 a 对应的下标是 0,第二个元素 b 对应的下标是 1。以此类推,通过 array+[下标] 的方式,我们可以快速地定位元素。</p>
|
||||
<p>如下面代码所示,运行它,可以看到输出打印的结果是 c,也就是数组 array 的第三个元素:</p>
|
||||
<p><em><strong>ch04/main.go</strong></em></p>
|
||||
|
||||
@@ -348,7 +348,7 @@ type address struct {
|
||||
</code></pre>
|
||||
<p>意思就是类型 person 没有实现 Stringer 接口。这就证明了<strong>以指针类型接收者实现接口的时候,只有对应的指针类型才被认为实现了该接口。</strong></p>
|
||||
<p>我用如下表格为你总结这两种接收者类型的接口实现规则:</p>
|
||||
<p><img src="assets/Ciqc1F-yPMSAZ4k7AABU_GW4VxE080.png" alt="Drawing 0.png" /></p>
|
||||
<p><img src="assets/Ciqc1F-yPMSAZ4k7AABU_GW4VxE080.png" alt="png" /></p>
|
||||
<p>可以这样解读:</p>
|
||||
<ul>
|
||||
<li>当值类型作为接收者时,person 类型和*person类型都实现了该接口。</li>
|
||||
|
||||
@@ -362,7 +362,7 @@ if errors.As(err,&cm){
|
||||
<p>小提示:interface{} 是空接口的意思,在 Go 语言中代表任意类型。</p>
|
||||
</blockquote>
|
||||
<p>panic 异常是一种非常严重的情况,会让程序中断运行,使程序崩溃,所以<strong>如果是不影响程序运行的错误,不要使用 panic,使用普通错误 error 即可。</strong></p>
|
||||
<p><img src="assets/CgqCHl-15ZSAAsw5AAUnpsfN34w061.png" alt="pDE7ppQNyfRSIn1Q__thumbnail.png" /></p>
|
||||
<p><img src="assets/CgqCHl-15ZSAAsw5AAUnpsfN34w061.png" alt="png" /></p>
|
||||
<h3>Recover 捕获 Panic 异常</h3>
|
||||
<p>通常情况下,我们不对 panic 异常做任何处理,因为既然它是影响程序运行的异常,就让它直接崩溃即可。但是也的确有一些特例,比如在程序崩溃前做一些资源释放的处理,这时候就需要从 panic 异常中恢复,才能完成处理。</p>
|
||||
<p>在 Go 语言中,可以通过内置的 recover 函数恢复 panic 异常。因为在程序 panic 异常崩溃的时候,只有被 defer 修饰的函数才能被执行,所以 recover 函数要结合 defer 关键字使用才能生效。</p>
|
||||
|
||||
@@ -195,7 +195,7 @@ First defer
|
||||
<p>讲并发就绕不开线程,不过在介绍线程之前,我先为你介绍什么是进程。</p>
|
||||
<h4>进程</h4>
|
||||
<p>在操作系统中,进程是一个非常重要的概念。当你启动一个软件(比如浏览器)的时候,操作系统会为这个软件创建一个进程,这个进程是该软件的工作空间,它包含了软件运行所需的所有资源,比如内存空间、文件句柄,还有下面要讲的线程等。下面的图片就是我的电脑上运行的进程:</p>
|
||||
<p><img src="assets/CgqCHl-7fwyAdSu_AADl16erQwg589.png" alt="Drawing 0.png" /></p>
|
||||
<p><img src="assets/CgqCHl-7fwyAdSu_AADl16erQwg589.png" alt="png" /></p>
|
||||
<p>(电脑运行的进程)</p>
|
||||
<p>那么线程是什么呢?</p>
|
||||
<h4>线程</h4>
|
||||
@@ -267,7 +267,7 @@ First defer
|
||||
<pre><code>cacheCh:=make(chan int,5)
|
||||
</code></pre>
|
||||
<p>我创建了一个容量为 5 的 channel,内部的元素类型是 int,也就是说这个 channel 内部最多可以存放 5 个类型为 int 的元素,如下图所示:</p>
|
||||
<p><img src="assets/CgqCHl-7fzmAVLu0AACSjW-neAE188.png" alt="Drawing 2.png" /></p>
|
||||
<p><img src="assets/CgqCHl-7fzmAVLu0AACSjW-neAE188.png" alt="png" /></p>
|
||||
<p>(有缓冲 channel)</p>
|
||||
<p>一个有缓冲 channel 具备以下特点:</p>
|
||||
<ol>
|
||||
|
||||
@@ -322,7 +322,7 @@ func watchDog(ctx context.Context,name string) {
|
||||
<li><strong>值 Context</strong>:用于存储一个 key-value 键值对。</li>
|
||||
</ol>
|
||||
<p>从下图 Context 的衍生树可以看到,最顶部的是空 Context,它作为整棵 Context 树的根节点,在 Go 语言中,可以通过 context.Background() 获取一个根节点 Context。</p>
|
||||
<p><img src="assets/CgqCHl_EyHOARbBqAAKzKmhclWo807.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/CgqCHl_EyHOARbBqAAKzKmhclWo807.png" alt="png" /></p>
|
||||
<p>(四种 Context 的衍生树)</p>
|
||||
<p>有了根节点 Context 后,这颗 Context 树要怎么生成呢?需要使用 Go 语言提供的四个函数。</p>
|
||||
<ol>
|
||||
@@ -347,7 +347,7 @@ go func() {
|
||||
</code></pre>
|
||||
<p>示例中增加了两个监控狗,也就是增加了两个协程,这样一个 Context 就同时控制了三个协程,一旦 Context 发出取消信号,这三个协程都会取消退出。</p>
|
||||
<p>以上示例中的 Context 没有子 Context,如果一个 Context 有子 Context,在该 Context 取消时会发生什么呢?下面通过一幅图说明:</p>
|
||||
<p><img src="assets/Ciqc1F_EyIyAAO_TAADuPjzGt5U321.png" alt="Drawing 3.png" /></p>
|
||||
<p><img src="assets/Ciqc1F_EyIyAAO_TAADuPjzGt5U321.png" alt="png" /></p>
|
||||
<p>(Context 取消)</p>
|
||||
<p>可以看到,当节点 Ctx2 取消时,它的子节点 Ctx4、Ctx5 都会被取消,如果还有子节点的子节点,也会被取消。也就是说根节点为 Ctx2 的所有节点都会被取消,其他节点如 Ctx1、Ctx3 和 Ctx6 则不会。</p>
|
||||
<h3>Context 传值</h3>
|
||||
|
||||
@@ -194,7 +194,7 @@ name变量的内存地址为: 0xc000010200
|
||||
</blockquote>
|
||||
<p>以上示例中 nameP 指针的类型是 *string,用于指向 string 类型的数据。在 Go 语言中使用类型名称前加 * 的方式,即可表示一个对应的指针类型。比如 int 类型的指针类型是 *int,float64 类型的指针类型是 *float64,自定义结构体 A 的指针类型是 *A。总之,指针类型就是在对应的类型前加 * 号。</p>
|
||||
<p>下面我通过一个图让你更好地理解普通类型变量、指针类型变量、内存地址、内存等之间的关系。</p>
|
||||
<p><img src="assets/Ciqc1F_OA06AI435AADN1ZPvtvs400.png" alt="Drawing 1.png" /></p>
|
||||
<p><img src="assets/Ciqc1F_OA06AI435AADN1ZPvtvs400.png" alt="png" /></p>
|
||||
<p>(指针变量、内存地址指向示意图)</p>
|
||||
<p>上图就是我刚举的例子所对应的示意图,从图中可以看到普通变量 name 的值“飞雪无情”被放到内存地址为 0xc000010200 的内存块中。指针类型变量也是变量,它也需要一块内存用来存储值,这块内存对应的地址就是 0xc00000e028,存储的值是 0xc000010200。相信你已经看到关键点了,指针变量 nameP 的值正好是普通变量 name 的内存地址,所以就建立指向关系。</p>
|
||||
<blockquote>
|
||||
@@ -272,7 +272,7 @@ func modifyAge(age *int) {
|
||||
<li>可以修改指向数据的值;</li>
|
||||
<li>在变量赋值,参数传值的时候可以节省内存。</li>
|
||||
</ol>
|
||||
<p><img src="assets/CgqCHl_OA2eANW2SAAU88P9foow113.png" alt="Drawing 2.png" /></p>
|
||||
<p><img src="assets/CgqCHl_OA2eANW2SAAU88P9foow113.png" alt="png" /></p>
|
||||
<p>不过 Go 语言作为一种高级语言,在指针的使用上还是比较克制的。它在设计的时候就对指针进行了诸多限制,比如指针不能进行运行,也不能获取常量的指针。所以在思考是否使用时,我们也要保持克制的心态。</p>
|
||||
<p>我根据实战经验总结了以下几点使用指针的建议,供你参考:</p>
|
||||
<ol>
|
||||
|
||||
@@ -349,11 +349,11 @@ modifyMap函数:p的内存地址为0xc000060180
|
||||
<h3>类型的零值</h3>
|
||||
<p>在 Go 语言中,定义变量要么通过声明、要么通过 make 和 new 函数,不一样的是 make 和 new 函数属于显式声明并初始化。如果我们声明的变量没有显式声明初始化,那么该变量的默认值就是对应类型的零值。</p>
|
||||
<p>从下面的表格可以看到,可以称为引用类型的零值都是 nil。</p>
|
||||
<p><img src="assets/Ciqc1F_QqlyAItQJAABQMWd6pSU650.png" alt="112.png" /></p>
|
||||
<p><img src="assets/Ciqc1F_QqlyAItQJAABQMWd6pSU650.png" alt="png" /></p>
|
||||
<p>(各种类型的零值)</p>
|
||||
<h3>总结</h3>
|
||||
<p>在 Go 语言中,<strong>函数的参数传递只有值传递</strong>,而且传递的实参都是原始数据的一份拷贝。如果拷贝的内容是值类型的,那么在函数中就无法修改原始数据;如果拷贝的内容是指针(或者可以理解为引用类型 map、chan 等),那么就可以在函数中修改原始数据。</p>
|
||||
<p><img src="assets/CgqCHl_QqryAEqYQAAVkYmbnDIM013.png" alt="Lark20201209-184447.png" /></p>
|
||||
<p><img src="assets/CgqCHl_QqryAEqYQAAVkYmbnDIM013.png" alt="png" /></p>
|
||||
<p>所以我们在创建一个函数的时候,要根据自己的真实需求决定参数的类型,以便更好地服务于我们的业务。</p>
|
||||
<p>这节课中,我讲解 chan 的时候没有举例,你自己可以自定义一个有 chan 参数的函数,作为练习题。</p>
|
||||
<p>下节课我将介绍“内存分配:new 还是 make?什么情况下该用谁?”记得来听课!</p>
|
||||
|
||||
@@ -270,7 +270,7 @@ ok gotour/ch18 0.367s coverage: 85.7% of statements
|
||||
<pre><code>➜ go tool cover -html=ch18.cover -o=ch18.html
|
||||
</code></pre>
|
||||
<p>命令运行后,会在当前目录下生成一个 ch18.html 文件,使用浏览器打开它,可以看到图中的内容:</p>
|
||||
<p><img src="assets/CgpVE1_i7P2ALPmDAACtzdHE7Jo110.png" alt="image.png" /></p>
|
||||
<p><img src="assets/CgpVE1_i7P2ALPmDAACtzdHE7Jo110.png" alt="png" /></p>
|
||||
<p>单元测试覆盖率报告</p>
|
||||
<p>红色标记的部分是没有测试到的,绿色标记的部分是已经测试到的。这就是单元测试覆盖率报告的好处,通过它你可以很容易地检测自己写的单元测试是否完全覆盖。</p>
|
||||
<p>根据报告,我再修改一下单元测试,把没有覆盖的代码逻辑覆盖到,代码如下:</p>
|
||||
|
||||
@@ -361,7 +361,7 @@ func main() {
|
||||
<p>从以上代码可以看到,只需要把建立链接的方法从 Dial 换成 DialHTTP 即可。</p>
|
||||
<p>现在分别运行服务端和客户端代码,就可以看到输出的结果了,和上面使用TCP 链接时是一样的。</p>
|
||||
<p>此外,Go 语言 net/rpc 包提供的 HTTP 协议的 RPC 还有一个调试的 URL,运行服务端代码后,在浏览器中输入 http://localhost:1234/debug/rpc 回车,即可看到服务端注册的RPC 服务,以及每个服务的方法,如下图所示:</p>
|
||||
<p><img src="assets/Ciqc1F_7zbWAb5PXAAA7zm9tcRE148.png" alt="image" /></p>
|
||||
<p><img src="assets/Ciqc1F_7zbWAb5PXAAA7zm9tcRE148.png" alt="png" /></p>
|
||||
<p>如上图所示,<strong>注册的 RPC 服务</strong>、<strong>方法的签名</strong>、<strong>已经被调用的次数</strong>都可以看到。</p>
|
||||
<h3>JSON RPC 跨平台通信</h3>
|
||||
<p>以上我实现的RPC 服务是基于 gob 编码的,这种编码在跨语言调用的时候比较困难,而当前在微服务架构中,RPC 服务的实现者和调用者都可能是不同的编程语言,因此我们实现的 RPC 服务要支持多语言的调用。</p>
|
||||
|
||||
Reference in New Issue
Block a user