当前位置: 首页 > 科技观察

GO语言系列(一):认识GO语言

时间:2023-03-13 08:53:44 科技观察

前言本专栏综合解读软件领域相关知识,侧重深度技术内容,主要涵盖编程语言、系统架构、开源框架、技术管理、等,分为若干主题,每个主题包含多篇文章。本文为专栏第一篇,GO语言系列第一篇。今天想从各个方面谈谈我对GO语言的总体印象。后续文章将深入介绍各种特性和编程技巧。从历史上看,Go语言的作者有RobertGriesemer、RobPike和KenThompson,其中KenThompson因其对UNIX和C语言发展的巨大贡献而为程序员所熟知。到目前为止有哪些软件是用Go语言写的?容器软件Docker,基础软件ETCD和Kubernetes,数据库软件TiDB和InfluxDB,消息系统NSQ,缓存组件Gr??oupCache。可以看到,几乎在基础设施软件的每一个领域,都出现了用Go语言编写的新软件,而且这些软件的市场份额还在不断上升。除了作为基础软件的语言,Go语言作为通用服务器端语言的机会也越来越多。从Beego、Gorilla等Go语言web框架的流行也可以看出一些发展趋势。示例程序让我们通过一个简单的示例程序来看看GO的编码风格:Packagemainimport"fmt"funcmain(){fmt.Println("hello,world");}如何运行上面的代码?GO语言是一种编译型语言,GO工具链将程序的源文件转换成机器相关的原生指令(二进制)。最基本的工具是run命令,可以编译、链接、链接一个或多个GO源文件(带.go后缀)。之后开始运行生成的可执行文件,看一下实际运行情况:$gorunhelloworld.go打印:hello,world上面的编译、链接、运行都是一次性的,也就是说,当你下次运行gorun命令,所有内部流程都会重做。我们可以通过gobuild命令生成一个二进制程序,然后任意调用,如下图:如果用编译语言编写的程序需要被机器识别,就需要经过编译和链接两个步骤。编译是将源代码编译成机器码,链接是将各个模块的机器码和依赖库串联起来生成可执行文件。让我们来看看编译型语言的优点和缺点。由于预编译过程的存在,可以优化代码,只需要一次编译。运行时效率会很高,而且可以独立于语言环境运行。缺点是修改后需要编译整个模块。与编译型语言相比,解释型语言只是在程序运行时逐行翻译。那么什么是链接呢?准确的说是链接和加载,也就是这两个步骤是在编译之后进行的,这样程序才能在内存中运行。链接是通过链接器完成的,它将多个目标文件链接成一个完整的、可加载的、可执行的目标文件。整个过程包括符号解析(将目标文件中的应用程序符号与一致性定义联系起来)以及将符号定义与内存位置相关联。命名规范GO语言中的函数、常量、变量、类型、语句、标签、包的名称都有比较统一的命名规则。名称以字母或下划线开头,后面可以跟任意数量的字符、数字或下划线。注意,GO语言区分大小写,不能使用关键字作为名称。GO程序员在遇到由一个单词组成的名字时,一般会使用“驼峰式”风格。说到这里,我们来看看Java的命名规范。以$为例,Oracle官网建议不要使用$或_作为变量名,建议名称中完全不要使用“$”字符。原文是“然而,约定是始终以字母开头变量名,而不是‘$’或‘_’”。腾讯在这一点上也有同样的看法。百度认为,类名虽然可以支持使用“$”符号,但仅在系统生成时使用(如匿名类、代理类),不能使用编码。StackOverFlow上很多人都提出过这种问题。主流意见是不需要太在意。你只需要注意原始代码中是否存在“_”即可。如果存在,请保留它。如果它不存在,请尽量避免使用它。还有一个原因是尽量不要使用“_”,因为低分辨率显示,肉眼很难区分“_”(一个下划线)和“__”(两个下划线)。我个人认为可能是受C语言编码标准的影响。因为在C语言中,宏名、变量名、内部函数名在系统头文件中都是以_开头的,所以当你#include系统头文件的时候,这些文件中的名字都是定义好的,如果你使用的名字有冲突可能造成各种奇怪的现象。综合各种资料,建议不要使用“_”、“$”、空格作为名字的开头,以免难读或引起奇怪的问题。对于类名,俄罗斯Java专家YegorBugayenko的建议是尽可能使用现实生活中实体的抽象。如果类名以“-er”结尾,则不推荐使用这种命名方式。他指出这篇文章有个例外,就是工具类,比如StringUtils、FileUtils、IOUtils。对于接口名称,不要使用IRecord、IfaceEmployee、RedcordInterface,而是使用真实世界的实体名称。当然,以上都是针对Java的,与GO无关。GO语言更受C语言的影响。变量概述GO语言包括四种主要的声明方式:变量(var)、常量(const)、类型(type)和函数(func)。下面说说与变量相关的一些感受:1.var声明创建一个特定类型的变量,然后给它附加一个名字,并设置它的初始值。每个声明都有一个通用形式:varnametype=expression。还有一点,GO语言允许空字符串,不会报空指针错误。2、变量可以使用name:=expression来声明,注意:=表示声明,=表示赋值。如果变量定义为varxint,则表达式&x(addressofx)获得一个指向整型变量的指针,该整型变量的类型为整型指针(*int)。如果该值称为p,我们可以说p指向x,或者说p包含x的地址。p指向的变量写成*p。表达式*p获取变量的值(在本例中为整数),因为*p表示标量,所以它也可以出现在赋值运算符的左侧以更新变量的值。x:=1p:=&x//p是一个整型指针,指向xfmt.Println(*p)//输出"1"*p=2//相当于x=2fmt.Println(x)//输出"2"注意,相对于Java的NULL,GO表示指针类型的零值为nil。3.使用内置的new函数创建变量。表达式new(T)创建一个类型为T的未命名变量,将其初始化为类型T的零值,并返回其地址(地址类型为*T)。使用new创建的变量和普通取地址的局部变量没有区别,只是不需要引入(或声明)虚名,可以直接通过new(T)在表达式中使用。funcnewInt()*int{returnnew(int)}等价于:funcnewInt()*int{vardummyintreturn&dummy}gofmt工具GO语言提供了很多工具,比如gofmt,可以对代码进行格式化,我们来看看它是如何实现的。gofmt会读取程序并格式化,比如gofmtfilename命令,会打印格式化后的代码。我们来看一个示例程序(程序名demo.go):");fmt.Println(a);fmt.Println((b));fmt.Println(c);}运行gofmtdemo.go后,输出代码如下:packagemainimport"fmt"//thisisdemotoformatcode//withgofmtcommandvaraint=2varbint=5varcstring=`helloworld`funcprint(){fmt.Println("Valuefora,bandcis:")fmt.Println(a)fmt.Println((b))fmt.Println(c)}语言垃圾级别回收器如何知道一个变量是否应该被回收?其基本思想是,每个包级变量,以及当前正在执行的函数的每个局部变量,都可以作为变量可追溯路径的来源,可以通过指针和其他引用变量找到。如果变量的路径不存在,则标量变得不可访问,因此它不会影响任何其他计算。由于变量的生命周期由其可达性决定,因此局部变量可以在包含它的循环的一次迭代之后继续存在。GO语言的垃圾收集器设计目标是非阻塞收集器。GO1.5实现了10毫秒内回收(注意,根据实验,这个说法只有在GC有足够的CPU时间的情况下才能成立)。从设计原则上看,Go的收集器是并发的、三色的、mark-and-clear的收集器。它的设计思想由Dijkstra于1978年提出,目标是匹配现代硬件和现代软件的特性。低延迟需求是一个很好的匹配。总结综上所述,每一种新语言的出现都是有原因的。一般来说,有两个原因:1.存在当前主流语言无法解决的复杂场景或特定问题;2.需要更具性价比的语言。我想,除了贝尔实验室会做一些完全出于个人感受的事情,没有人会随便部署没有出路的新技术。正如RobPike所说,“复杂性以倍增的方式增长”。为了解决某个问题,使系统的某个部分稍微复杂一点,必然会增加其他部分的复杂性。在添加系统功能、选项和配置以及快速发布的持续压力下,简单性常常被忽视。实现简单性需要在项目开始时浓缩想法的本质,并在项目的整个生命周期中制定更具体的指导方针,以辨别哪些变化是好的,哪些是坏的或致命的。通过足够的努力,好的改变可以在不损害FredBrooks所说的软件设计的“概念完整性”的情况下实现他们的目标。糟糕的改变不会那样做,致命的改变会为了方便而牺牲简单性。但只有通过简单的设计,系统才能在发展过程中保持稳定、安全和自洽。Go语言不仅包括语言本身及其工具和标准库,还保持着极简的行为文化。今天的文章只是初步的印象介绍,下篇文章见。【本文为专栏作家“周铭耀”原创稿件,转载请联系原作者】点此阅读更多该作者好文