网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要体系化学习资料的朋友,可以加我V获取:vip204888 (备注网络安全)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
随着编程语言的发展,Go 还很年轻。它于 2009 年 11 月 10 日首次发布。其创建者 Robert Griesemer Rob Pike 和 Ken Thompson 在 Google 工作,在那里大规模扩展的挑战激励他们将 Go 设计为一种快速有效的编程解决方案,用于具有大型代码库、管理由多个开发人员,具有严格的性能要求,并跨越多个网络和处理核心。
Go 的创始人在创建他们的新语言时也借此机会学习了其他编程语言的优点、缺点和漏洞。结果是一种干净、清晰和实用的语言,具有相对较少的命令和功能集。
在本文中,今天这篇文章将给大家介绍一下 Go 与其他语言不同的 9 个特性。
1. Go 总是在构建中包含二进制文件
Go 运行时提供内存分配、垃圾收集、并发支持和网络等服务。它被编译到每个 Go 二进制文件中。这与许多其他语言不同,其中许多语言使用需要与程序一起安装才能正常工作的虚拟机。
将运行时直接包含在二进制文件中使得分发和运行 Go 程序变得非常容易,并避免了运行时和程序之间的不兼容问题。Python、Ruby 和 JavaScript 等语言的虚拟机也没有针对垃圾收集和内存分配进行优化,这解释了 Go 相对于其他类似语言的优越速度。例如,Go 将尽可能多的存储在堆栈中,其中数据按顺序排列以便比堆更快地访问。稍后会详细介绍。
关于 Go 的静态二进制文件的最后一件事是,因为不需要运行外部依赖项,所以它们启动得非常快。如果您使用Google App Engine 之类的服务,这是一种在 Google Cloud 上运行的平台即服务,它可以将您的应用程序缩减到零实例以节省云成本,这将非常有用。当收到新请求时,App Engine 可以在眨眼间启动 Go 程序的一个实例。在 Python 或 Node 中的相同体验通常会导致 3-5 秒(或更长时间)的等待,因为所需的虚拟环境也会与新实例一起启动。
2. Go 没有针对程序依赖的集中托管服务
为了访问已发布的 Go 程序,开发人员不依赖集中托管的服务,例如Java 的Maven Central或JavaScript的NPM注册表。相反,项目通过其源代码存储库(最常见的是 Github)共享。在go install命令行允许以这种方式下载库。
为什么我喜欢这个功能?我一直认为像 Maven Central、PIP 和 NPM 这样的集中托管的依赖服务有点令人生畏的黑盒子,也许可以抽象出下载和安装依赖项的麻烦,但不可避免地会在依赖项错误时引发可怕的心跳停止发生。
此外,将的的模块提供给其他人就像将其放入版本控制系统一样简单,这是分发程序的一种非常简单的方式。
3. Go 是按值调用的
在 Go 中,当你提供一个原始值(数字、布尔值或字符串)或一个结构体(类对象的粗略等价物)作为函数的参数时,Go 总是会复制变量的值。
在 Java、Python 和 JavaScript 等许多其他语言中,原语是按值传递的,但对象(类实例)是按引用传递的,这意味着接收函数实际上接收的是指向原始对象的指针,而不是其副本。在接收函数中对对象所做的任何更改都会反映在原始对象中。
在 Go 中,结构体和原语默认按值传递,可以选择传递指针,通过使用星号运算符:
// 按值传递
func MakeNewFoo(f Foo ) (Foo, error) {
f.Field1 = "New val"
f.Field2 = f.Field2 + 1
return f, nil
}
上述函数接收 Foo 的副本并返回一个新的 Foo 对象。
// 通过引用传递
func MutateFoo(f \*Foo ) error {
f.Field1 = "New val"
f.Field2 = 2
return nil
}
上面的函数接收一个指向 Foo 的指针并改变原始对象。
按值调用与按引用调用的这种明显区别使您的意图显而易见,并降低了调用函数无意中改变传入对象的可能性(当它不应该发生时(许多初学者开发人员很难做到这一点)握紧)。
正如麻省理工总结的:“可变性使得理解你的程序在做什么变得更加困难,并且更难以执行契约”
此外,按值调用显着减少了垃圾收集器的工作,这意味着应用程序更快、内存效率更高。这篇文章得出的结论是,指针追踪(从堆中检索指针值)比从连续堆栈中检索值慢 10 到 20 倍。要记住的一个很好的经验法则是:从内存中读取的最快方法是顺序读取,这意味着将随机存储在 RAM 中的指针数量减少到最少。
4. ‘defer’ 关键字
在NodeJS 中,在我开始使用knex.js之前,我会通过创建一个数据库池来手动管理我的代码中的数据库连接,然后在每个函数中从池中打开一个新连接,一旦所需的数据库 CRUD 功能已完成。
这有点像维护噩梦,因为如果我没有在每个函数结束时释放连接,未释放的数据库连接的数量会慢慢增长,直到池中没有更多可用连接,然后中断应用程序。
现实情况是,程序经常需要释放、清理和拆除资源、文件、连接等,因此 Go 引入了defer关键字作为管理这些的有效方式。
任何以defer开头的语句都会延迟对它的调用,直到周围的函数退出。这意味着您可以将清理/拆卸代码放在函数的顶部(很明显),知道一旦函数完成它就会如此。
func main() {
if len(os.Args) < 2 {
log.Fatal("no file specified")
}
f, err := os.Open(os.Args[1])
if err != nil {
log.Fatal(err)
}
defer f.Close()
data := make([]byte, 2048)
for {
count, err := f.Read(data)
os.Stdout.Write(data[:count])
if err != nil {
if err != io.EOF {
log.Fatal(err)
}
break
}
}
}
在上面的例子中,文件关闭方法被推迟了。我喜欢这种在函数顶部声明你的内务处理意图的模式,然后忘记它,知道一旦函数退出它就会完成它的工作。
5. Go 采用了函数式编程的最佳特性
函数式编程是一种高效且富有创造性的范式,幸运的是 Go 采用了函数式编程的最佳特性。在Go中:
- 函数是值,这意味着它们可以作为值添加到映射中,作为参数传递给其他函数,设置为变量,并从函数返回(称为“高阶函数”,在 Go 中经常使用装饰器创建中间件图案)。
- 可以创建和自动调用匿名函数。
- 在其他函数内声明的函数允许闭包(在函数内声明的函数能够访问和修改在外部函数中声明的变量)。在惯用的 Go 中,闭包被广泛使用来限制函数的范围,并设置函数然后在其逻辑中使用的状态。
func StartTimer (name string) func(){
t := time.Now()
log.Println(name, "started")
return func() {
d := time.Now().Sub(t)
log.Println(name, "took", d)
}
}
func RunTimer() {
stop := StartTimer("My timer")
defer stop()
time.Sleep(1 \* time.Second)
}
上面是一个闭包的例子。‘StartTimer’ 函数返回一个新函数,它通过闭包可以访问在其出生范围内设置的 ‘t’ 值。然后,此函数可以将当前时间与“t”的值进行比较,从而创建一个有用的计时器。感谢Mat Ryer的这个例子。
6. Go 有隐式接口
任何阅读过有关SOLID编码和设计模式的文献的人都可能听说过“优先组合胜过继承”的口头禅。简而言之,这表明您应该将业务逻辑分解为不同的接口,而不是依赖于来自父类的属性和逻辑的分层继承。
另一个流行的方法是“为接口编程,而不是实现”: API 应该只发布其预期行为的契约(其方法签名),而不是有关如何实现该行为的详细信息。
这两者都表明接口在现代编程中的重要性。
因此,Go 支持接口也就不足为奇了。事实上,接口是 Go 中唯一的抽象类型。
然而,与其他语言不同,Go 中的接口不是显式实现的,而是隐式实现的。具体类型不声明它实现了接口。相反,如果为该具体类型设置的方法集包含底层接口的所有方法集,则Go 认为该对象实现了 interface。
这种隐式接口实现(正式称为结构类型)允许 Go 强制执行类型安全和解耦,保持动态语言中表现出的大部分灵活性。
相比之下,显式接口将客户端和实现绑定在一起,例如,在 Java 中替换依赖项比在 Go 中困难得多。
// 这是一个接口声明(称为Logic)
type Logic interface {
Process (data string) string
}
type LogicProvider struct {}
// 这是 LogicProvider 上名为“Process”的方法 struct
func (lp LogicProvider) Process (data string) string {
// 业务逻辑
}
// 这是具有 Logic 接口作为属性的客户端结构
type Client struct {
L Logic
}
func(c Client) Program() {
// 从某处获取数据
cLProcess(data)
}
func main() {
c := Client {
L: LogicProvider{},
}
c.Program()
}
LogicProvider 中没有任何声明表示它符合Logic接口。这意味着客户端将来可以轻松替换其逻辑提供程序,只要该逻辑提供程序包含底层接口 ( Logic ) 的所有方法集。
7.错误处理
Go 中的错误处理方式与其他语言大不相同。简而言之,Go 通过返回一个 error 类型的值作为函数的最后一个返回值来处理错误。
学习路线:
这个方向初期比较容易入门一些,掌握一些基本技术,拿起各种现成的工具就可以开黑了。不过,要想从脚本小子变成黑客大神,这个方向越往后,需要学习和掌握的东西就会越来越多以下是网络渗透需要学习的内容:
需要体系化学习资料的朋友,可以加我V获取:vip204888 (备注网络安全)
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!