介绍

Go(Golang) 是谷歌开发的一种 静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。


我们为什么要学习Go?其实我觉得是因为公司发展越来越快的一个必然趋势,随着发展,很多东西是nodejs不一定能很好的支持。我们需要后端多样化,以在未来某个时段我们能有更大的能力去面对未知的事务。


Golang 的Hello World

还记得以前学习C语言的时候,老师都是从Hello World开始讲起,今天我们也是从这里开始我们的Golang之旅。



main
helloworld


下载

官网下载地址(点我)

下载对应系统的安装包

解压缩:

添加PATH环境变量:

安装好了使用下面命令测试:

go env

GOPROXY

Go 除了自身带了很多包,社区也有很多开源项目的包,大部门都是在github、google、等域名上面,如果你的terminal没有翻墙,拉取会很慢,这个时候,我们可以设置这个环境变量(非系统环境变量):

这样,拉取所有的包都走的是七牛云。


Golang 的语法


Golang 是一门上手非常快、并发性能非常高的语言,我们先来介绍Go的基本语法。


package

.go-
package main

包的导入有两种方式:

我们推荐使用第二种写法,有时候,我们可能只想引用某个包,让其执行 init 函数,而不会用到包里面的函数,可以这样做:



main 函数

main 函数是我们项目的入口程序,如果一个go代码被编译,它没有main函数,是无法编译出可执行文件的。

main 函数的写法是:


语句

一个 statement 就是一行,不需要结尾的分号

比如:


注释

///** **/

比如:



基本类型

Go 是强类型的语言,不会像我们目前使用的nodejs一样,一个变量可以随意赋任意类型的值。在Go中,一旦定义了某个变量的类型,则这个变量只能赋予该类型的值。

truefalse

数字类型 可以分为 整数类型、浮点数类型和一些其它特殊意义的类型

整数类型

data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">

序号

类型

描述

浮点数类型

data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">

序号

类型

描述

其它数字类型

data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">

序号

类型

描述


变量

变量名的规则和其它语言都类似, 只能由字母、数字和下划线取名,且不能以数字开头:

其实声明变量的方式由很多种

变量的声明是可以多个变量可以一起声明的:


运算符

运算符用于程序在执行算术运算或者逻辑运算时使用。

Go内置的运算符有:

  • 算术运算符
  • 关系运算符
  • 逻辑运算符
  • 位运算符
  • 赋值运算符
  • 其它运算符

算术运算符

假设A=10 B=20

data-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">

运算符

描述

实例

关系运算符

假设A=10 B=20,关系运算符的结果时bool类型的值

逻辑运算符

假设 A = true B = false

位运算符

  • 按位与 &
  • 按位或 |
  • 按位异或 ^: 1^1=0;1^0=1;0^0=0;
  • 左移 <<
  • 右移 >>

赋值运算符

=、+=、-=、*=、\=、%=、<<=、>>=、&=、^=、|=

其它运算符

指针取地址: &, 取值 *


条件语句

Go语言提供下面几种条件语句:

  • if 语句
  • switch 语句
  • select 语句

if 语句

Go 语言中,if 语句的语法:

注意,这里的条件表达式不需要括号,举个实例:

switch 语句

Go 语言的switch语句很强大,和nodejs 的不太一样,Go语言中的Switch语法:

fallthrough

比如:


select 语句

select 语句一般用于超时控制,是Go的一个控制结构,每个case都是一个channel操作,select 将随机选取一个可以运行的case执行,如果没有可以运行的case,则select语句会阻塞,直到有可以执行的case。默认子句总是可以执行的。

语法如下:

不过一般我们都不会用default,就像上面讲的,我们一般是用来做超时控制的,举个例子:

这里,我们请求一个函数,同时设置一个3s的超时,如果函数3s内没有返回数据,则select 会执行超时这个case,否则就执行了取结果的数据。当然,这个例子是没有代表性的,因为实际工程上要比这个复杂一点,因为要涉及到资源的释放之类的,后面会讲到。


循环语句

Go的循环语句和nodejs也不太一样,我们先看语法:

函数

func

func 关键字后面接的是函数名字,函数名字一般是用驼峰命名,如果是小驼峰,则这个函数只能在当前文件被调用,如果是大驼峰,则可以被其它包引入调用。

变量名 变量类型

举个例子:

!!!非常重要!!!

函数传参一般有两种:

  • 值传递是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数
  • 引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

而Golang使用的是值传递


init



数组

数组是一种具有唯一的相同的数据类型、固定长度的数据项序列,这个类型可以是整型、字符串或者任意自定义的结构体。

定义数组必须要指定长度,语法如下:

以下是例子:


指针

&*

举个例子:

在C语言中,指针属于比较难的一块,什么是指针? 一个指针变量,指向了一个值的地址:



比如一个变量 C 保存了字符 'K',地址在 0x11A,而一个指向c的指针变量p,它的值就是C的地址11A。

通过一个实例来看怎么使用指针:


结构体

结构体是Go中非常重要的一个模块,后面的开发一定会用的到,不管是创建Service还是创建Orm的model。

数组是只能存储一个类型固定长度的值,结构体呢?可以存储多个不同类型的值,简单来说,结构体就是一系列不同或者相同类型的集合。

结构体语法定义如下:

举个例子:

这样我们就定义了一个结构体,声明使用如下:

声明了之后,使用方式如下:

*

切片(slice)

数组长度是固定的,对于长度不知道或者不固定的数据,我们就很难使用数组去实现这种业务。

所以就有了切片(动态数组),切片长度是不固定的,切片的定义和数组的定义很类似,不过不需要长度

切片初始化和赋值:

现在,我们说了数组和切片,可以开始说一下长度和容量了。

len(v Type)cap(v Type)

数组的长度和容量都是固定的!

切片的长度表示当前切片值的个数,切片的容量是什么呢?

我们需要从切片的本质去看:

这个就是切片的结构体,Data 指向数组的指针,Len表示当前切片的长度,Cap表示Data数组的大小。

用一个实际的例子来说:

d是切片,这个时候Data指向的就是b这个数组,一个六个输出,依次说一下:

  1. d切了[0:3],所以长度是3,容量是b数组的长度,也就是4,当没有赋值时,int的默认零值是0,所以b=[0,0,0,0],d=[0,0,0]
  2. 由于切片的Data指向了b,所以修改是修改b数组的值,所以 b = [1, 0, 0, 0],d = [1, 0, 0]
  3. append函数用于切片末尾追加一个值,注意,这个不是直接修改d,必须要重新用d接收返回值,所以此时d的长度是4,容量也是4,由于d的Data指向b,所以b和d的值都是 [1, 0, 0, 2]
  4. 由于d的Data指向b,所以b和d的值都是[3, 0, 0, 2]
  5. 这个时候,d再次追加了一个值,这个时候,b=[3,0,0,2],和上一次打印是一致的,而d的值为[3, 0, 0, 2, 4],追加成功,并且长度确实加了1,但是容量却从4变为了8.可能看到这里大家就会疑惑了,d的Data不是指向了b吗,为什么这一次b没有被修改呢?这是因为数组是长度不变的,切片是动态数组,当切片容量不够时,就会新申请一片连续的内存作为Data的指向,并且将原来的值复制过去,这个就是切片的扩容(扩容算法不展开讲),所以一旦发生了扩容,切片的指向就会发生变化
  6. 所以这个时候,再次修改d,也不会影响b了,所以b=[3, 0, 0, 2],d=[5, 0, 0, 2, 4]
nil

就像上面说的,直接切片一个数组或者别的切片得到的新切片,其实Data指向的还是原来的,但是有时候我们不要这个引用,因为我们要传参的时候想要修改这个值但是不能影响实参,所以我们就需要拷贝切片:


Map 集合

Go 种的Map集合和我们使用的Nodejs种的对象类似,它就是一组无序的键值对的集合。

Map值的获取就比较有意思了,我们前面有一直说到,一个变量定义了,它都会有自己对应的零值,所以在map获取值的时候,如果某个key是不存在的,则返回了value类型对应的零值,比如:

这个时候是有问题的。大家思考一下

如果我本来就是有一个存在的key,并且值我就是设置为0呢?

这个时候就没有办法区分这个key的值到底是默认的零值还是我们自己设置的零值。所以我们需要用另外一种写法:

还记得if这个写法吗?忘了的同学可以往前面看看。这个时候,我们多接受了一个ok参数,这个参数的类型是bool,如果为true表示key存在,如果为false表示key不存在,获取的是零值。

==!=
delete


接口 interface

Go语言提供了另外一种数据类型 即接口,他把所有具有共性的方法定义在一起,任何其它类型只要实现了这些方法(接口定义里面的全部)就相当于实现了这个接口。

比如:

这样,UserService就实现了UserInterface这个接口。

错误处理

Go 语言通过内置的错误接口提供了非常简单的错误处理机制(Go 中没有所谓try catch)。

error 类型是一个接口类型,定义如下:

errors

知道了 error 类型是一个接口,所以我们可以自定义error类型:


Go 并发

并发这里,是初识Golang的最后一节,我们选择Go的一个很大原因就是Go的并发能力很强,并且使用非常简单!

简单到什么程度?

go
gogoroutine

main函数就是一个主协程。

举个例子:

当你运行的时候,可以看到,每一次输出的结果都是不一致的。加一个睡眠是因为,主协程运行结束,则程序就退出了,所以还没有运行完的协程就没有运行机会了。

这样来看,有没有感觉和我们的异步是一样的,哈哈。


结束语

今天讲的东西,我全部都没有深入去讲,希望这一篇文章,能让我们大家从一个noder,从此也可以叫为 gopher。