go语言特性

对比其他编程语言,Go有何突出的呢?

一:Go语言的语言特性

很久以前,有一个IT公司,这公司有个传统,允许员工拥有20%自由时间来开发实验性项目。在2007的某一天,公司的几个大牛,正在用c++开发一些比较繁琐但是核心的工作,主要包括庞大的分布式集群,大牛觉得很闹心,后来c++委员会来他们公司演讲,说c++将要添加大概35种新特性。这几个大牛的其中一个人,名为:Rob Pike,听后心中一万个xxx飘过,“c++特性还不够多吗?简化c++应该更有成就感吧”。于是乎,Rob Pike和其他几个大牛讨论了一下,怎么解决这个问题,过了一会,Rob Pike说要不我们自己搞个语言吧,名字叫“go”,非常简短,容易拼写。其他几位大牛就说好啊,然后他们找了块白板,在上面写下希望能有哪些功能。接下来的时间里,大牛们开心的讨论设计这门语言的特性,经过漫长的岁月,他们决定,以c语言为原型,以及借鉴其他语言的一些特性,来解放程序员,解放自己,然后在2009年,go语言诞生。

以下就是这些大牛所罗列出的Go要有的语言特性:

  • 规范的语法(不需要符号表来解析)
  • 垃圾回收(独有)
  • 无头文件
  • 明确的依赖
  • 无循环依赖
  • 常量只能是数字
  • int和int32是两种类型
  • 字母大小写设置可见性(letter case sets visibility)
  • 任何类型(type)都有方法(不是类型)
  • 没有子类型继承(不是子类)
  • 包级别初始化以及明确的初始化顺序
  • 文件被编译到一个包里
  • 包package-level globals presented in any order
  • 没有数值类型转换(常量起辅助作用)
  • 接口隐式实现(没有“implement”声明)
  • 嵌入(不会提升到超类)
  • 方法按照函数声明(没有特别的位置要求)
  • 方法即函数
  • 接口只有方法(没有数据)
  • 方法通过名字匹配(而非类型)
  • 没有构造函数和析构函数
  • postincrement(如++i)是状态,不是表达式
  • 没有preincrement(i++)和predecrement
  • 赋值不是表达式
  • 明确赋值和函数调用中的计算顺序(没有“sequence point”)
  • 没有指针运算
  • 内存一直以零值初始化
  • 局部变量取值合法
  • 方法中没有“this”
  • 分段的堆栈
  • 没有静态和其它类型的注释
  • 没有模板
  • 内建string、slice和map
  • 数组边界检查

二:Go语言的突出亮点

Go语言在编译速度、执行速度、开发效率之间做了权衡,力求在不损失应用程序性能的情况下尽力降低代码的复杂性,达到快速编译、高效执行、易于开发的效果

// 1、关于Go的编译速度
    编译表示的是将你所写的源代码转换为低层次的语言或其他中间格式。
    例如
        go语言编写的程序----编译成低级语言-->汇编语言
        Java、C#编写的程序--编译成中间格式-->字节码

    通常情况下大多数编译性语言的编译速度都会很慢,一个程序光是编译过程就需要花费几分钟甚至几十分钟,这给程序的更新迭代带来了极大的困扰。
    但是Go在编译速度很快,Go程序的初次编译速度媲美C/C++,甚至二次编译的速度明显快于C/C++,速度快到近乎感知不到编译过程的存在,即便是习惯使用解释型语言如python的人来说也会不由地惊呼:你丫的不会是解释型的吧!!!

    编译涉及到两个问题:编译速度和依赖管理

    目前Golang具有两种编译器,一种是建立在GCC基础上的Gccgo,另外一种是分别针对64位x64和32位x86计算机的一套编译器(6g和8g)。

    依赖管理方面,由于golang绝大多数第三方开源库都在github上,在代码的import中加上对应的github路径就可以使用了,库会默认下载到工程的pkg目录下。

    另外,编译时会默认检查代码中所有实体的使用情况,凡是没使用到的package或变量,都会编译不通过。这是golang挺严谨的一面。

// 2、关于Go的执行速度与开发效率
    开发效率:目前看对比所有编程语言,python属于最简洁优美的了,但Go语言有接近Python的简洁与开发效率。
    执行效率:
            I: 动态语言如Python,由于没有强类型的约束,很多错误需要在运行时发现,而这种低级错误更应该交给编译器来发现,这就是Go采用静态类型的出发点,毫无疑问,静态类型+编译型的go语言执行效率自然要远高于动态类型+解释型的python,此外go的执行效率已经过了C++/Java,但目前看还没有超过C,

            II: Go语言还具备极强的抗并发能力
            Go语言从根本上将一切都并发化,运行时用 Goroutine 运行所有的一切,包括main.main入口函数。
            可以说,Goroutine 是 Go 最显著的特征。它用类协程的方式来处理并发单元,却又在运行时层面做了
            更深度的优化处理。这使得语法上的并发编程变得极为容易,无须处理回调,无须关注线程切换,仅一个
            关键字go,简单而自然。搭配 channel,实现CSP模型。将并发单元间的数据耦合拆解开来,各司其职,                       这对所有纠结于内存共享、锁粒度的开发人员都是一个可期盼的解脱。若说有所不足,那就是应该有个更               大的计划,将通信从进程内拓展到进程外,实现真正意义上的分布式。

// 3、管于Go自带内存管理机制
// 3.1 内存分配
将一切并发化固然是好,但带来的问题同样很多。如何实现高并发下的内存分配和管理就是个难题。好在 Go 选择了 tcmalloc,它本就是为并发而设计的高性能内存分配组件。

可以说,内存分配器是运行时三大组件里变化最少的部分。刨去因配合垃圾回收器而修改的内容,内存分配器完整保留了 tcmalloc 的原始架构。使用 cache 为当前执行线程提供无锁分配,多个 central 在不同线程间平衡内存单元复用。在更高层次里,heap 则管理着大块内存,用以切分成不同等级的复用内存块。快速分配和二级内存平衡机制,让内存分配器能优秀地完成高压力下的内存管理任务。

在最近几个版本中,编译器优化卓有成效。它会竭力将对象分配在栈上,以降低垃圾回收压力,减少管理消耗,提升执行性能。可以说,除偶尔因性能问题而被迫采用对象池和自主内存管理外,我们基本无须参与内存管理操作。

同时允许开发人员干预回收操作(ps:在最近几个版本中,编译器优化卓有成效。它会竭力将对象分配在栈上,以降低垃圾回收压力,减少管理消耗,提升执行性能。可以说,除偶尔因性能问题而被迫采用对象池和自主内存管理外,我们基本无须参与内存管理操作。)

- 内存自动回收,再也不需要开发人员管理内存
- 开发人员专注业务实现,降低了心智负担
- 只需要new分配内存,不需要释放

// 3.2 GC(garbage collection,垃圾收集器)
    变量的产生要申请内存,无用时则要释放其占有的内存,而内存的管理对于程序员来说向来都是繁琐、高风险的事情,好在go与python一样提供了GC机制

    但需要强调的是,GC机制带来的并非全都是好处,随之而来的是程序运行效率的降低。 
    GC过程是:先stop the world,扫描所有对象判活,把可回收对象在一段bitmap区中标记下来,接着立即start the world,恢复服务,同时起一个专门gorountine回收内存到空闲list中以备复用,不物理释放。物理释放由专门线程定期来执行。GC瓶颈在于每次都要扫描所有对象来判活,待收集的对象数目越多,速度越慢。一个经验值是扫描10w个对象需要花费1ms,所以尽量使用对象少的方案,比如我们同时考虑链表、map、slice、数组来进行存储,链表和map每个元素都是一个对象,而slice或数组是一个对象,因此slice或数组有利于GC。

    除了GC机制之外,GO还支持自动内存分配等内存管理机制,这些因素导致Go语言在实时性上有些力不从心,因此Go并不适合开发强实时性的软件,但如果面对的是实时性要求不高的场景,比如高性能分布式系统领域,Go还是如鱼得水的

// 4、关于Go语言的静态链接
// 4.1: 静态链接???
    - 什么是静态链接:
    所谓静态链接是指把程序要调用到的函数或者过程全都链接到可执行文件中,成为可执行文件exe的一部分。换句话说,函数和过程的代码就在程序的exe文件中,这一个文件就包含了运行时所需的全部代码。

    - 静态连接的优点:不需要加载任何外部依赖就可以执行,部署简单,执行效率高
    - 静态连接的缺点:
      = 浪费硬盘:当生成可执行文件以后,可执行文件包含所有的代码,因此,在可执行文件运行时就不再需要静态库,程序源码中静态库白白地占用着磁盘空间。
      = 占用过多内存:如果有多个进程在内存中运行,内存中就存有多份相同的库函数代码,因此占用内存空间较多。

//4.2:动态链接??
    - 什么是动态链接:
    所谓动态链接指把程序要调用到的函数或者过程打成专门的库,该目标代码库可被其他进程在执行时动态调用和卸载。

    - 动态连接的优点:动态调用和卸载依赖库,即便有多个进程也不会导致内存额外占用
    - 动态连接的缺点:需要加载外部依赖才可以执行,部署时令人头疼,执行也会受到影响

//4.3:Go支持静态库和动态库
    Go 刚发布时,静态链接被当作优点宣传,因为依赖库直接打包到一个可执行文件内部,无须事先安装运行环境和下载诸多第三方库,只需这一个可执行文件可以直接部署运行,这种简单粗暴的方式解决了令人头疼的库依赖问题,极大地简化了部署和发布操作
    当基于4.1、4.2的描述,静态链接也并非十全十美,于是在这之后连着几个版本,Go编译器都在支持并完善动态库buildmode功能

// 5、Go是类C型的语言
    Go语言有时候被描述为类C型(C-like)的语言,或者是“21 世纪的C语言”。
    这意味着Go语言从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等很多思想,比如
    (1):使用&&表示AND
    (2):使用==表示等值比较
    (3):数组索引从0开始计算
    (4):(...)表示一段代码块,也表示属于一个作用域范围
    需要注意的是:
    类C型语言要求每行语句都要使用分号;结束,条件表达式都要求用括号包围,这两点Go都是不采纳的。
    虽然Go中可以用括号包围来改变优先级,如下
if (name == "small_egon" && age <= 18) || (name == "big_egon" && age > 18) {
    fmt.Println("ok!!!") // 不需要写分号";"结束
}
   但是Go的表达式也可以不加括号包围,如下
if name == "big_egon" {
    fmt.Println("ok!") // 不需要写分号";"结束
}

三 Go语言的其他亮点

// 1、 网络编程
由于golang诞生在互联网时代,因此它天生具备了去中心化、分布式等特性,具体表现之一就是提供了丰富便捷的网络编程接口,比如socket用net.Dial(基于tcp/udp,封装了传统的connect、listen、accept等接口)、http用http.Get/Post()、rpc用client.Call('class_name.method_name', args, &reply),等等。

// 2、 函数是第一等公民
函数也是一种类型的值
    可以赋值给其他变量
    可以当做参数传递给其他函数
    可以当做函数的返回值
    可以当做复合类型的元素
此外,Go语言接受了函数式编程的一些想法,支持匿名函数与闭包

// 3、 函数也可以可以有多个返回值
在C,C++中,包括其他的一些高级语言是不支持多个函数返回值的。但是这项功能又确实是需要的,所以在C语言中一般通过将返回值定义成一个结构体,或者通过函数的参数引用的形式进行返回。而在Go语言中,作为一种新型的语言,目标定位为强大的语言当然不能放弃对这一需求的满足,所以支持函数多返回值是必须的。

函数定义时可以在入参后面再加(a,b,c),表示将有3个返回值a、b、c。这个特性在很多语言都有,比如python。

这个语法糖特性是有现实意义的,比如我们经常会要求接口返回一个三元组(errno,errmsg,data),在大多数只允许一个返回值的语言中,我们只能将三元组放入一个map或数组中返回,接收方还要写代码来检查返回值中包含了三元组,如果允许多返回值,则直接在函数定义层面上就做了强制,使代码更简洁安全。

// 4、 语言交互性
语言交互性指的是本语言是否能和其他语言交互,比如可以调用其他语言编译的库。

在Go语言中直接重用了大部份的C模块,这里称为Cgo.Cgo允许开发者混合编写C语言代码,然后Cgo工具可以将这些混合的C代码提取并生成对于C功能的调用包装代码。开发者基本上可以完全忽略这个Go语言和C语言的边界是如何跨越的。

 golang可以和C程序交互,但不能和C++交互。可以有两种替代方案:1)先将c++编译成动态库,再由go调用一段c代码,c代码通过dlfcn库动态调用动态库(记得export LD_LIBRARY_PATH);2)使用swig(没玩过)

// 5、异常处理
golang在错误处理方面比其他语言如Java、PHP等更加精简高效,Go不支持try...catch这样的结构化的异常解决方式,因为觉得会增加代码量,且会被滥用,不管多小的异常都抛出。golang提倡的异常处理方式是:

- 普通异常:被调用方返回error对象,调用方判断error对象。
- 严重异常:指的是中断性panic(比如除0),使用defer...recover...panic机制来捕获处理。严重异常一般由golang内部自动抛出,不需要用户主动抛出,避免传统try...catch写得到处都是的情况。当然,用户也可以使用panic('xxxx')主动抛出,只是这样就使这一套机制退化成结构化异常机制了。

// 6、其他边边角角
    (1)、Go语言支持跨平台/交叉编译:可以在运行Linux操作系统的计算机上开发并编译Windows下的应用程序
    (2)、类型推导:类型定义:支持`var abc = 10`这样的语法,让golang看上去有点像动态类型语言,但golang实际上时强类型的,前面的定义会被自动推导出是int类型。
    (3)、作为强类型语言,隐式的类型转换是不被允许的,记住一条原则:让所有的东西都是显式的。
    简单来说,Go是一门写起来像动态语言,有着动态语言开发效率的静态语言。
    (4)、不能循环引用:即如果a.go中import了b,则b.go要是import a会报import cycle not allowed。好处是可以避免一些潜在的编程危险,比如a中的func1()调用了b中的func2(),如果func2()也能调用func1(),将会导致无限循环调用下去。
    (5)、defer机制:在Go语言中,提供关键字defer,可以通过该关键字指定需要延迟执行的逻辑体,即在函数体return前或出现panic时执行。这种机制非常适合善后逻辑处理,比如可以尽早避免可能出现的资源泄漏问题。
  可以说,defer是继goroutine和channel之后的另一个非常重要、实用的语言特性,对defer的引入,在很大程度上可以简化编程,并且在语言描述上显得更为自然,极大的增强了代码的可读性。
    (6)、Go语言的类型定义参考了C语言中的结构(struct),支持使用struct关键字定义结构体
    (7)、Go语言没有类和继承的概念,所以它和 Java 或 C++ 看起来并不相同。但是它通过接口(interface)的概念来实现多态性,所以Go语言是可以基于面向对象编程的。Go语言有一个清晰易懂的轻量级类型系统,在类型之间也没有层级之说。因此可以说Go语言是一门混合型的语言。
    (8)、支持反射
    (9)、一个类型只要实现了某个interface的所有方法,即可实现该interface,无需显式去继承。
Go编程规范推荐每个Interface只提供一到两个的方法。这样使得每个接口的目的非常清晰。另外Go的隐式推导也使得我们组织程序架构的时候更加灵活。在写JAVA/C++程序的时候,我们一开始就需要把父类/子类/接口设计好,因为一旦后面有变更,修改起来会非常痛苦。而Go不一样,当你在实现的过程中发现某些方法可以抽象成接口的时候,你直接定义好这个接口就OK了,其他代码不需要做任何修改,编译器的自动推导会帮你做好一切。
    (10)、编程规范:GO语言的编程规范强制集成在语言中,比如明确规定花括号摆放位置,强制要求一行一句,不允许导入没有使用的包,不允许定义没有使用的变量,提供gofmt工具强制格式化代码等等。奇怪的是,这些也引起了很多程序员的不满,有人发表GO语言的XX条罪状,里面就不乏对编程规范的指责。要知道,从工程管理的角度,任何一个开发团队都会对特定语言制定特定的编程规范,特别像Google这样的公司,更是如此。GO的设计者们认为,与其将规范写在文档里,还不如强制集成在语言里,这样更直接,更有利用团队协作和工程管理。
    (11)、Go与其他语言一样,拥有一个健全的包管理机制,同时得益于包之间的树状依赖
    (12)、Go语言源码文件格式默认都是UTF-8编码的,GO是第一门完全支持 UTF-8 的编程语言。

// 7、补充
Go语言作为一门年轻的语言,可选的第三方库暂时没有像Java、Python这种主流语言一样丰富,但越来越多的程序都开始用Go编写,相信随着Go语言的持续发展,其第三方库会越来越丰富

四 与其他编程语言对比

因为Go的语法和Erlang、Python类似,所以我们将这三门语言做个详细的对比。

相比于Python的40个特性,Go只有31个,可以说Go在语言设计上是相当克制的。比如,它没有隐式的数值转换,没有构造函数和析构函数,没有运算符重载,没有默认参数,也没有继承,没有泛型,没有异常,没有宏,没有函数修饰,更没有线程局部存储。

但是Go的特点也很鲜明,比如,它拥有协程、自动垃圾回收、包管理系统、一等公民的函数、栈空间管理等。

Go作为静态类型语言,保证了Go在运行效率、内存用量、类型安全都要强于Python和Erlang。

Go的数据类型也更加丰富,除了支持表、字典等复杂的数据结构,还支持指针和接口类型,这是Python和Erlang所没有的。特别是接口类型特别强大,它提供了管理类型系统的手段。而指针类型提供了管理内存的手段,这让Go进入底层软件开发提供了强有力的支持。

Go在面对对象的特性支持上做了很多反思和取舍,它没有类、虚函数、继承、泛型等特性。Go语言中面向对象编程的核心是组合和方法(function)。组合很类似于C语言的struct结构体的组合方式,方法类似于Java的接口(Interface),但是使用方法上与对象更加解耦,减少了对对象内部的侵入。Erlang则不支持面对对象编程范式,相比而言,Python对面对对象范式的支持最为全面。

在函数式编程的特性支持上,Erlang作为函数式语言,支持最为全面。但是基本的函数式语言特性,如lambda、高阶函数、curry等,三种语言都支持。

控制流的特性支持上,三种语言都差不多。Erlang支持尾递归优化,这给它在函数式编程上带来便利。而Go在通过动态扩展协程栈的方式来支持深度递归调用。Python则在深度递归调用上经常被爆栈。

Go和Erlang的并发模型都来源于CSP,但是Erlang是基于actor和消息传递(mailbox)的并发实现,Go是基于goroutine和管道(channel)的并发实现。不管Erlang的actor还是Go的goroutine,都满足协程的特点:由编程语言实现和调度,切换在用户态完成,创建销毁开销很小。至于Python,其多线程的切换和调度是基于操作系统实现,而且因为GIL的大坑级存在,无法真正做到并行。

而且从笔者的并发编程体验上看,Erlang的函数式编程语法风格和其OTP behavior框架提供的晦涩的回调(callback)使用方法,对大部分的程序员,如C/C++和Java出身的程序员来说,有一定的入门门槛和挑战。而被称为“互联网时代的C”的Go,其类C的语法和控制流,以及面对对象的编程范式,编程体验则好很多。

五 说几点不得不选择Go的理由

罗里吧嗦说了半天,无非是在夸GO如何的好,我都懒得看,那么下面说这几点,可能会更容易打动你

1、语法简单、好学、规则固定甚至有点死板,这意味着初学者与有经验的人写出的代码质量相差无几,性能保持高度一致、

2、Go简洁的语法让程序员更专注于业务逻辑的表达,远离底层,这通过意味着会带来更多的性能损耗,但您猜怎么着,Go程序依然保持高效,你说它香不香,Go似乎就是在把C与python统一到一起

3、Go的创始团队神仙阵容,又有世界级科技巨头谷歌推动,你怕他不火吗?

4、静态文件,部署方便

上一篇
下一篇
Copyright © 2022 Egon的技术星球 egonlin.com 版权所有 帮助IT小伙伴学到真正的技术