1.go语言入门深入解析
目录
- 1. 关键5大问题解读
- 2. Go语言简介
- 3. Go语言能做什么
- 4. 实现原理
- 5. 使用Go的步骤
- 6. 最佳实践及注意事项
- 7. 代码示例
关键5大问题解读
问题1:Go到底是静态类型语言还是动态类型语言?
静态类型语言是指在编译阶段就确定变量的类型,如果将不同类型的数据赋值给变量,就会在编译阶段报错。这种类型的语言能提供更强的类型检查,预防了许多运行错误,同时也能提供更好的性能。
动态类型语言则是在运行阶段才确定变量的类型,通常具备更高的灵活性,但可能在运行过程中发生类型错误。
Go语言虽然是静态类型语言,但是也提供了一些动态语言的特性,比如接口(interface)类型提供了对动态类型的支持,可以接收任何实现了特定接口的类型实例。这让Go语言在提供静态类型的性能和安全性的同时,也具有一定的灵活性
问题2:Go编译后的是什么文件?
因为Go语言是编译型语言,源代码在运行前会先编译成机器码,然后操作系统直接运行这个机器码。这个机器码组成的就是那个可执行文件
同时,Go语言编译出的二进制文件是静态链接的,这意味着所有的依赖库都会被包含在最后生成的二进制文件中。因此你可以直接运行这个二进制文件,而不需要关心在目标机器上安装依赖库等问题。这也是Go语言得以在Docker等容器化环境中流行的原因之一
问题3: Go语言的并发模型是什么?
Go语言的并发模型是goroutine和channel。goroutine是Go语言并发的核心,它是一种轻量级的线程,可以在Go语言运行时调度器上并发执行。channel是goroutine之间的通信方式,可以用来在goroutine之间传递数据,实现数据共享和同步
问题4:Go语言的垃圾回收机制是什么?
Go语言的垃圾回收机制是自动的,开发者无需关心内存管理。Go语言的垃圾回收器会自动检测不再使用的内存,并回收这些内存。Go语言的垃圾回收器是基于标记-清除算法实现的,它会在程序运行时自动进行垃圾回收,释放不再使用的内存,避免内存泄漏
问题5:Go语言的接口和继承是怎么实现的?
Go语言没有传统的面向对象语言中的继承的概念,但是有接口这一概念。Go语言的接口是一种抽象类型,它定义了一组方法的集合,只要一个类型实现了这些方法,就可以看作是这个接口的实现类型。这种方式实现了接口的多态性,使得Go语言可以实现类似继承的效果
Go语言简介
Go是一门静态类型、编译型的开源编程语言。Go的设计者和主要的实现者都是Google,并且Go依靠Google的强有力支持稳步发展。它的设计目标是“解决开发人员在处理大型代码项目时常常遇到的一些问题”。而Go语言的出现正是为了将这一目标具体化,它通过在语言级别提供并发支持、垃圾回收、强大的标准库、强类型等特性,使开发者可以更高效,更舒适地编写代码
Go语言能做什么
- • 性能优化:Go语言在语言核心层面支持并行计算,能有效利用多处理器、多核心的计算能力,使得CPU密集型或者并发性强的系统获得更好的性能。
- • 部署简单:编译后的Go应用是静态的二进制文件,无需任何外部依赖,可以直接运行,使得部署极其简便。
- • 内建网络库:Go内建了丰富的网络库,对于网络编程非常友好,因此很适合后端开发。
- • 内容丰富、便捷的标准库:Go语言的标准库丰富又实用,可以解决你日常大部分的问题。
实现原理
与传统的编译型语言如C,C++等不同,Go是以平台无关的中间代码分发的,当安装程序或者运行程序时,编译成特定平台的机器代码,这也是Go执行速度快的主要原因。
- • 编译速度:Go采用了包级别的编译方式,编译速度极快。
- • 并发:goroutine和channel:Golang原生支持并发,goroutine是Go并发的核心,相比于线程,它的初始栈内存非常小,且运行时可动态增长,创建和销毁的成本低廉。channel是goroutine之间的通信方式,使用它可以很方便地处理并发之间的数据共享和同步问题。
- • 面向接口编程:Go语言没有传统的面向对象中的继承的概念,但是有接口这一概念,而且数据类型(不论是结构体还是别的类型)只要实现了该接口的所有方法,就可以看作是这个接口的实现类型。
- • 垃圾回收:Go语言自带垃圾回收机制,开发者无需关心内存管理,这与C,C++不同,降低了开发门槛
使用Go的步骤
- • 安装:包括下载Go语言安装包,配置GOPATH环境变量等。
-
- • 配置go env -w GO111MODULE=on用来开启Go Modules,这是Go语言的依赖管理工具,可以更好地管理项目依赖。
- • 配置go env -w GOPROXY=mirrors.aliyun.com/goproxy/用来设…
- • 编程:开发工具可以使用VSCode、Goland等,编写Go语言程序。
- • 编译:通过go build命令编译Go程序,生成可执行文件;通过go run命令直接运行Go程序。
- • 测试:Go语言内建提供了testing包,非常方便编写测试,并且提供了一个轻量级的测试框架和功能。
- • 部署:Go的部署非常简单,编译生成的程序不依赖其他文件,只需要将生成的可执行文件部署到服务器并执行即可。
最佳实践及注意事项
最佳实践
- • Go是静态强类型的语言,使用var声明变量,:=进行简短声明,它要求明确每个变量的类型。
- • Go的控制结构中不需要在条件或循环结构后添加括号,其语句块必须用花括号切需要在开头和结尾的花括号中。
- • 函数的返回值可以为多个,并且可以将返回值明确命名。
- • Go语言使用defer注册延迟调用,这些调用直到return前才被执。你可以在deferm后面一系列的函数注册,它们会在函数结束时按注册顺序的相反顺序被执行。
注意事项
- • 并发控制:尽管Go语言对并发进行了内建的支持,会使用并发,并能够控制并发,编写并发控制良好的程序,是编程人员需要掌握的技能。
- • 使用纯函数:由于函数在Go中属于一等公民,可以作为其它函数的参数或者返回值,闭包的存在提供了很多“潜力”,所以,我们应该尽量使用和编写纯函数。
- • 资源清理:Go提供了defer关键字实现“延迟调用”,主要用于进行资源清理。
- • 错误处理:Go语言的错误处理机制是通过返回值来实现的,函数返回值中的最后一个值通常是error类型,如果函数执行成功,error的值为nil,否则为错误信息。
代码示例
简单运行
下面是一个简单的Go语言程序,它输出了"Hello, World!"。
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
直接通过go run命令运行这个程序,就可以看到输出结果了,也可以通过go build命令编译成可执行文件,然后运行。
接口与继承
下面是使用Go语言编写的一个简单的示例,主要涉及到interface(接口)和embedding(嵌入),这在go中模拟实现了继承的效果。
package main
import "fmt"
// 定义一个数据的操作接口DataOperation
type DataOperation interface {
Add()
Subtract()
}
// 定义基本的共享结构体BaseData
type BaseData struct {
value int
}
// 基本结构体的Add方法
func (data *BaseData) Add() {
data.value += 1
}
// 基本结构体的Subtract方法
func (data *BaseData) Subtract() {
data.value -= 1
}
// 定义一个继承自BaseData的结构体AData
type AData struct {
// 使用内嵌字段,实现对BaseData的继承
BaseData
}
// 定义一个继承自BaseData的结构体BData
type BData struct {
// 使用内嵌字段,实现对BaseData的继承
BaseData
}
func main() {
// 创建一个AData对象
a := AData{BaseData{value: 10}}
// 创建一个BData对象
b := BData{BaseData{value: 5}}
// 将对象赋值给对应的接口,这样我们就可以把多种不同的结构体以相同的方式去处理
var i DataOperation
i = &a
fmt.Println(i.Add())
i = &b
fmt.Println(i.Subtract())
}
首先定义了一个接口DataOperation,它规定了所有实现该接口的结构体都需要有Add()和Subtract()两个方法。然后定义了一个基础结构体BaseData,这个结构体实现了DataOperation接口,它有一个int型值value和两个方法,一个用于增加value的值,另一个用于减少value的值。
接下来定义了两个新的结构体AData和BData,它们都内嵌了BaseData,因此也都继承了BaseData的方法和属性,这就是Go语言中的继承。最后,在main函数中,我们创建了AData和BData的实例,并将这些实例赋值给了DataOperation接口。这样,我们就可以使用接口来操作这些对象,这是Go语言中接口的使用。
goroutine协程的使用
Go的协程(goroutine)是Go并发执行机制的核心。它们是用户级线程,由Go运行时进行管理,可以让函数并发执行。特点是创建与运行成本低,数以万计的goroutine可以同时存在
package main
import (
"fmt"
"time"
)
// 函数f就是一个goroutine运行的主体
func f(from string) {
for i := 0; i < 3; i++ {
// 打印出引入的消息
fmt.Println(from, ":", i)
// 此处的Sleep,使得进程挂起,切换到其他goroutine
time.Sleep(time.Second)
}
}
func main() {
// 使用常规的同步方式调用函数f
f("direct")
// 使用go关键字来创建并启动一个goroutine
go f("goroutine")
// 也可以为匿名函数启动一个goroutine
go func(msg string) {
fmt.Println(msg)
}("going")
// 我们需要等待两个goroutine运行结束
// 这里我们为了等待,故意Sleep了4秒,实际开发中我们可以使用channel或者sync.WaitGroup来同步goroutine
time.Sleep(time.Second * 4)
//程序运行结果可能不同,因为goroutines的调度可能以不同的顺序进行
fmt.Println("done")
}
示例输出:
direct : 0
direct : 1
direct : 2
going
goroutine : 0
goroutine : 1
goroutine : 2
done
"going" 和 "goroutine" 的打印顺序可能会变,因为 Go 的 goroutine 调度是非确定性的。其他部分的打印顺序则是固定的,始终按照代码顺序执行。
转载自:https://juejin.cn/post/7374683456716914727