Go语言新手入门浅谈

纵然是瞬间 提交于 2020-08-05 10:54:32

一、 前言

由于工作中存在与Go语言相关的内容,因此最近花费部分时间对Go语言进行了解,从基础语法开始对Go语言开始学习。Go语言语法简单,类C语法的特性导致学习Go语言学习容易,能够极快上手,然而若是希望深入理解Go语言仍需在项目实践中不断锤炼。

本篇文章首先浅谈我对Go语言诞生环境、语言特色等内容的了解,并且总结Go语言的基础语法,以作交流。本篇文章从一个新手学习Go语言的角度编写,若文中存在需修正之处,欢迎评论留言指正。

二、 Go语言行业应用

Go语言是2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人。Go语言于2009年11月开源,在2012年3月发布了Go 1.0稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区[1]

Go语言的诞生存在其特殊的氛围背景。2007年,Google的首席软件工程师Rob Pike在等待C++编译的漫长等待过程中,拉上另外两位大佬,Robert Griesemer和Ken Thompson,期望能够创造出另外一门能够拥有接近C的执行性能、接近解析性语言的开发效率以及高效的编译速度的开发语言取代C++。三位大佬将此项目命名为Golang,并将它作为自由时间的实验项目。在大约一年后,Google发现了Go语言的巨大潜力并且全力支持,最终发展到目前的地步。此诞生背景最终决定了Go语言的两大特色:类C特性以及为服务端服务。
在这里插入图片描述

图1 Go语言核心开发团队 得益于社区中丰富的第三方库,Python被广泛应用于机器学习、数据分析、数据整理等项目。同样的,得益于强大的并发能力、完美跨平台能力、高效的开发效率等因素,Go语言已逐渐被业界广泛应用于云上开发,诞生出许多优秀项目,如下表所示: 表1 使用Go语言编写的项目案例 [2]
项目名称 项目简介
Docker golang头号优秀项目,通过虚拟化技术实现的操作系统与应用的隔离,也称为容器;
Kubernetes 由google开发,简称k8s,k8s和docker是当前容器化技术的重要基础设施;
Etcd 一种可靠的分布式KV存储系统,有点类似于zookeeper,可用于快速的云配置;
TiDB 国内PingCAP团队开发的一个分布式SQL数据库,国内很多互联网公司在使用;
InfluxDB 时序型DB,着力于高性能查询与存储时序型数据,常用于系统监控与金融领域;
CockroachDB 云原生分布式数据库,继NoSQL之后出现的新的概念,称为NewSQL数据库;
Beego 国人开发的一款及其轻量级、高可伸缩性和高性能的web应用框架;
go-kit Golang相关的微服务框架,这类框架还有go-micro、typhon;
go-ethereum 官方开发的以太坊协议实现;

除了上表中展示的项目之外,国内外各大互联网巨头也逐渐将Go语言作为云上服务的技术栈使用。例如:以今日头条为例,早在2017年,今日头条使用Go语言构建了承载了超过80%流量的后端服务,高峰QPS超过700万,日处理请求量超过3000亿。此外京东的消息推送与分布式存储、知乎的推荐系统、网易云信的调度服务等均开始使用Go语言。
Go语言业务案例

图2 Go语言业务案例

三、 IDE介绍

1. Goland

JetBrains作为一家推出过许多功能强大IDE的公司,迄今为止已推出包括:Java的Intellj IDEA、PHP的PHPStorm、 Python的Pycharm、前端的WebStorm在内的集成开发环境。它旗下推出了一款专门面向Go语言的集成开发环境Goland,功能强大,使用十分便捷。Goland社区的插件丰富,功能齐全,使用过程中碰到的问题少,开箱即食,对新手十分友好。可在jetbrains官网下载Goland产品,下载页面:https://www.jetbrains.com/go/。与IDEA等存在面向个人用户的免费版本不同的是,Goland仅有收费版本,使用者在免费试用30天后需购买以获取使用权限。

2. VSCode

VSCode是目前比较流行的轻量型IDE,集成了众多语言的插件,以支持多种语言的开发,其中包括Go语言插件。本段将介绍如何使用VSCode搭建Go语言的开发环境。

  1. 下载VSCode,下载链接为:https://code.visualstudio.com
  2. 进入VSCode,安装Golang插件,如下图所示,依据次序点击执行: 选择插件市场->搜索Go插件->认准微软出品->点击安装。
    VSCode Go环境配置-Go插件安装
图3 VSCode Go环境配置-Go插件安装
  1. 在VSCode的Setting中配置User Settings中的GOROOT及GOPATH,其他配置默认即可。如图4所示:
    其中:GOROOT是Go的安装路径,GOPATH是编译后二进制的存放目的地和import包时的搜索路径,也是你的工作目录。

VSCode Go环境配置-参数设置

图4 VSCode Go环境配置-参数设置
  1. 此时新建HelloWorld.go,编写代码,点击运行后,控制台会返回提示,有工具需要安装,可点击右下角弹出的提示框install All进行一键安装,如图5所示。也可打开命令提示符输入如下命令进行工具安装(本地需安装git):

安装gocode:go get -u -v github.com/nsf/gocode
安装gopkgs:go get -u -v github.com/tpng/gopkgs
安装go-outline:go get -u -v github.com/lukehoban/go-outline
安装godef:go get -u -v github.com/rogpeppe/godef
安装golint:
cd $GOPATH/src/golang.org/x
git clone https://github.com/golang/tools.git
git clone https://github.com/golang/lint.git
go get golang.org/x/lint/golint







在安装过程中,由于GFW的原因,可能会导致部分工具源不可用,此问题可通过从Github使用git clone下载到本地再使用本地安装解决

VSCode Go环境配置-工具安装

图5 VSCode Go环境配置-工具安装
  1. 安装完毕后重启VSCode,即可完成Go语言插件的安装。

四、 语法介绍

1. Hello World

创建hello.go,并且键入如下代码,运行Hello World:

package main

import "fmt"

func main() {
	fmt.Println("Hello, World!");
}

在此代码中,包含三块主要信息:package、import、func。其中,一个独立go程序需要至少一个main package。此外,还需通过func main声明主函数入口,通过import引入 标准化输入输出包,以实现Hello Wolrd 字符串的打印。
关于package的使用,细节如下所示:
a. Go程序是由一个或多个package组成,如果独立程序,则需要一个main package
b. 每一个package由若干个源文件组成,每个go源文件均需头部声明自身所属的package。
c. 其中同属于一个package的源文件应当存放于同一个目录之下
d. 在使用其他package的包内容时,可以通过import packagename “[package path]”来实现,代码如下:




package study

import packageName "path/to/package"

2. 变量声明

Go语言的变量声明方式非常简单且易于阅读,最常规的方式为通过var关键字声明变量: var variable_name variable_type,关键字、变量名、变量类型之间以空格分隔,实例代码如下所示:

func main() {
   var a int
   a = 20
   fmt.Println(a)
}

值得注意的是,Go语言的规范十分严格,设计人员将编码规范融入Go语言的编译规则中。以此实例为例,若仅仅定义、赋值了变量a而未使用,编译时会报错。
除此之外,变量的声明及初始化赋值还存在其他方式,如下所示:

 func main() {
   var a, b int = 20, 30
   var c = "HelloWorld"
   str := ""
   fmt.Println(a, b, c, str)
}

代码中各变量声明方式含义如下所示:

  • 定义int型的两个变量a和b,并且分别赋值为20和30
  • 定义新变量c,并且赋予初始值,由编译器编译时自动决定变量c的类型
  • str := “” 的作用 等同于 var str = “”,表示定义新变量、初始化赋值,并且由编译器编译时自动决定变量str的类型。值得注意的是:=左边必须是未定义过的变量名,已定义过变量使用:=赋值会在编译时出错

3. 函数与方法

Go语言中的函数

函数是基本的代码块,用于执行不同的业务任务流程。函数定义时指定函数名称、参数以及返回类型,具体格式如下所示:

func function_name( [parameter list] ) [return_types] {
   函数体
}

函数由func关键字开始声明,并依次包含函数名、参数列表、返回值列表。值得注意的是,Go语言中的函数可有无返回值、仅返回一个值和返回多个值三种情况可选。其中,返回多个值的实例代码如下所示:

func swap(x, y int) (int, int) {
   return y, x
}

func main() {
   a, b := 1, 2
   a, _ = swap(a, b)
   fmt.Println(a, b)
}

当调用多返回值的函数时,需要有对应数量的变量对象接收函数返回值。若不需要多个返回值中的某几个,可以使用Go语言中的空白标识符 _ 占位。

Go语言中的方法

方法也是函数,它带有一个特殊的接收器类型,它是在func关键字和方法名之间编写的。可以在方法内部访问接收器。方法能给用户自定义的类型添加新的行为。它和函数的区别在于方法有一个接收者,给一个函数添加一个接收者,那么它就变成了方法。实例代码如下所示:

type Person struct {
   age int
   name string
   gender string
}

func (p Person) Growup1() Person {
   p.age++
   return p
}

func Growup2(p Person) Person {
   p.age++
   return p
}

func main() {
   p := Person{name: "张三",age: 25, gender: "男"}
   p = p.Growup1()
   fmt.Println(p)
}

在此代码中,Groupup1与Groupup2两者实现的功能及效果一致。

4. 结构体

结构体的类型在java、c等语言之中均存在,同时也是十分重要的类型。通过结构体的使用将不同类型的数据组合形成新的数据类型。Go语言中同样存在结构体的类型。结构体的定义如下代码所示:

type struct_variable_type struct {
   member definition;
   member definition;
   ...
   member definition;
}

当定义了结构体后,即可将结构体应用于变量的声明。普通变量的声明及赋值方法同样适用于结构体变量的声明及初始化,如下列实例所示:

type Person struct {
   name string
   age int
   gender string
}

func main() {
   var p Person
   fmt.Println(p)
}

此处应值得注意的是,Go语言通过首字母大小写来控制访问权限,无论是变量、常量、方法还是自定义类型,首字母大写即可被外部包访问,反之则不行。以此实例为例,此处Person类型可被外部包访问,若类型名为person则不行。
除此之外结构体拥有多种初始化方法,包括:

func main() {
   p1 := Person{"张三", 25, "男"}
   p2 := Person{ gender: "女",name: "李四", age: 25}
   p3 := new(Person)
}

代码中各方式含义如下:

  • 按照结构体中成员变量定义的顺序依次提供初始化值。
  • 通过field:value的方式初始化,此方式无需按照结构体中成员变量定义的顺序提供值,可任意改变顺序。
  • 通过new方式初始化结构体,结构体中各成员变量会赋予类型默认的初始值。

结构体的访问

在上述初始化方式之后,p1与p2可通过.点操作符用于访问结构的各个字段,以获取或更改字段的值。P3通过new方式初始化,所得的结果为结构体指针。

// The new built-in function allocates memory. The first argument is a type,
// not a value, and the value returned is a pointer to a newly
// allocated zero value of that type.
func new(Type) *Type

在C语言中此情况需要使用来访问对象并操作成员变量,而在Go语言中无需使用,可直接通过点.操作符访问指针对象以及结构中的各个字段。具体代码如下所示:

func main() {
   p1 := Person{"张三", 25, "男"}
   p2 := Person{ gender: "女",name: "李四", age: 25}
   p3 := new(Person)
   p3.age = 18
   p3.name = "Cristina"
}

结构体的比较

结构体是值类型,如果结构体中的成员变量是可以比较的,那结构体是可以比较的。如果结构体中对应的字段均相等,则认为两个结构体变量相等。实例代码如下所示:

func main() {
   p1 := Person{"张三", 25, "男"}
   p2 := Person{"张三", 25, "男"}
   if p1 == p2 {
      fmt.Println("Person1 and Person2 are equal")
   }
}

运行结果如下:

Person1 and Person2 are equal

5. 循环控制

Go语言中循环过程控制主要通过for实现循环执行操作。和java语言类似,For语句的基本语法结构为:

For init; condition; post {}

其中:初始化语句仅被执行一次,在初始化语句执行之后,循环体执行之前将检查condition的条件,如果condition的值为true则执行循环体,否则结束循环。在一次循环体结束后则执行post语句,在此之后再次检查condition状态,并依据结果决定是否继续循环。
在Go语言中没有while关键字,但是可以通过for语句实现while的效果,while效果的for语句的基本语法结构为:

For condition {}

此外,可以使用for语句配合range关键字对slice、map、数组进行遍历,此实例代码如下所示:

func main()  {
   array := [...]int{1,2,3,4,5,6,7,8}
   for i,v := range array {
      fmt.Printf("数组中第 %d 个值为 %d \n", (i + 1), v)
   }
}

6. 内建容器

Go语言中,除了数字类型、字符类型等基本类型外,还包括数组、切片、Channel等派生类型。本篇文章中主要记录数组、切片两种数据类型的使用。

数组

与C语言中数组的概念一致,Go语言中数组也是一段固定长度的连续内存区间。数组的声明需要指定元素类型以及元素个数,语法格式如下:var variable_name [SIZE] variable_type
以此格式为标准,可得如下实例:

var arr1 [5]int

此实例中,定义了长度为5的整数型数组,并且为所有元素赋予初始值为0。除此之外,Go语言提供了其他声明并初始化数组的方式,具体如实例所示:

func main()  {
   var arr1 [5]int
   arr2 := [4]int{1,2,3,4}
   arr3 := [...]int{1,2,3,4,5,6,7,8}
   fmt.Println(arr1, arr2, arr3)
}

在第二种方式中,指定数组长度并使用 := 而非var关键字声明,此方法要求声明的同时必须赋予初始化的值,并且初始化数组内元素的数量必须等于指定的数组长度。
在第三种方式中,可无需指定数组长度,Go语言会根据元素的个数自动设置数组的大小。但需要注意:“[ ]”中必须使用“…”,若无“…” 得到的结果则不是数组而是切片。

切片

Go语言的数组定义完成后长度不可变,在某些特定场景下数组不太适用。Go语言原生支持一种灵活的内置类型:切片。切片是动态分配大小的连续空间,切片本身可以不储存数据,仅产生对底层数组的一个视图,从而灵活地对切片进行数据处理(类似“动态数组”),并通过此方法对底层数组的数据进行处理。此处视图的概念与数据库中的视图概念类似。
切片的内部结构包含地址、大小与容量。其中:
地址:即此段连续内容空间的起始地址
大小:即切片可取值的范围长度。长度可扩展。
容量:即切片所对应底层数组中,从起始地址开始的可用长度。容量可向后扩展,不可向前扩展。
slice结构




图6 slice结构

从数组或切片中生成新的切片

切片默认指向一段连续内存区域,可以是数组和切片本身,在一段连续内存区域生成切片是一种生成新切片的典型方式。格式代码如下:

slice[startindex : endindex]

其中:

  • slice表示切片目标对象
  • startindex表示切片对象的起始索引
  • endindex表示切片对象的结束索引

值得注意的是,此方法生成的切片仅仅是源连续内存区域的视图,本身不存储数据,因此对此方法生成切片的数据的更改会直接反应到内存区域,即:被切片的源切片和数组对应值也会更改。以如下实例代码为例:

func main()  {
   a := []int{1,2,3,4}
   b := a[0:]
   b[0] = 10
   fmt.Println(a, b)
}

运行结果为:

[10 2 3 4] [10 2 3 4]

声明切片以生成新切片

每种类型,包括自定义类型,都可以拥有其切片类型,以表示多个该类型元素的连续集合,此方法声明格式如下:

var slice_name []type_name

其中:

  • Slice_name表示切片类型的变量名
  • Type_name 表示该切片中对应的元素类型

使用make()函数构造新切片

可以使用make()函数动态地创建一个新的切片,该方法格式如下:

make( []type_name, len, cap )

其中:

  • Type_name:创建的新切片的元素类型
  • Len:为该切片分配多少初始元素
  • Cap:提前分配的内存区域大小。Cap的设计主要为了提前分配空间,以防止扩展时可能的再次分配空间而影响性能。

当slice扩展至超过容量边界时,系统会自动扩展容量,容量扩展策略是扩展至当前容量的双倍,以下列实例代码为例:

func testslice()  {
   a := make([]int, 3)
   fmt.Println(len(a), cap(a))
   a = append(a, 3)
   fmt.Println(len(a), cap(a))
}

func main()  {
   testslice()
}

运行结果为:

3 3

4 6

映射(map)

map是十分重要的数据结构,用以存储任意类型数据的关联关系。Go语言中,map类型定义格式如下所示:

map[KeyType]ValueType

实例代码如下所示:

func testmap()  {
   m := make(map[string]int)
   m["id1"] = 10001
   fmt.Println(m["id1"])
}

func main()  {
   testmap()
}

此外,map支持在声明时初始化填充内容,实例代码如下:

m := map[string]int{
   "id1":10001,
   "id2":10002,
   "id3":10003,
}

map的遍历

map的遍历可使用range关键字完成循环,range关键字可依次返回map中的key与value,同时获得键与值。实例代码如下:

func getall()  {
   m := map[string]int{
      "id1":10001,
      "id2":10002,
      "id3":10003,
   }
   for k,v := range m {
      fmt.Println("key : " , k, " value : " , v)
   }
}

func main()  {
   getall()
}

运行结果为:

key : id1 value : 10001

key : id2 value : 10002

key : id3 value : 10003

五、 总结

Go语言语法简单,通过以上基础语法学习后,可以使用Go语言实现简易功能,然而Go语言中依然存在许多特性,需要长期练习实践、阅读源码才可深入理解。此外Go语言中还可利用goroutine、 channel实现高性能并发,在闲暇及项目中多多思考以熟练使用。

参考资料:

[1]. 为什么要使用 Go 语言?Go 语言的优势在哪里? - 腾讯云技术社区的回答。点击跳转
[2]. 为什么要使用 Go 语言?Go 语言的优势在哪里? - 波罗学的回答。点击跳转

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!