这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
前言
本篇文章主要是从宏观层面讲一下go语言的基本特性以及go语言的一些基本语法。
GO语言介绍
百度百科这样描述go语言,Go(又称Golang)是Google开发的一种静态强类型、编译型、并发型,并具有垃圾回收功能的编程语言。
- 静态强类型指的是go语言中的数据类型在编译之后便可确定类型,典型代表是C、JAVA语言等,而动态语言则需要在运行时才能知道变量数据类型,典型代表是Python、JavaScript、PHP等。
- 编译型语言在执行之前要先经过编译过程,编译成为一个可执行的机器语言的文件,比如exe,这样只要程序不修改就可以不用编译,直接运行可执行文件,大大加快程序运行速度,典型代表C和C++,而解释型语言程序则是边解释边执行,需要执行速度较慢,但跨平台性好,典型代表是Python、Java。
- 并发型,go语言支持高并发应用,目前我了解到的是go语言在底层设计时天然支持多核CPU,并且有个协程Goroutine的概念,这是个轻量级线程,能够轻松支持上万的并发任务,而且开辟一个协程就是使用go+函数的形式,非常简单,协程之间数据通信使用管道Channel,对于共享资源可以进行加锁等操作以及go语言提供原子操作,go可以将多个任务分配给各个cpu,虽说go语言支持高并发,但这种形式是并行,并发是单个cpu上处理多个任务,使用时间片、中断机制等来处理。
- 垃圾回收机制,用过C和C++的人都知道,因为C和C++没有垃圾回收机制,在堆区申请空间之后如果不再使用,需要将空间释放,否则将造成内存泄漏,而Java和Go一样均具有垃圾回收机制,系统会自动释放垃圾内存,不需要程序员手动释放。目前我了解到的垃圾回收算法主要是以下三种。
- 引用计数:每个对象维护一个引用计数,当被引用对象被创建或被赋值给其他对象时引用计数自动 +1。如果这个对象被销毁,那么计数-1,当计数为0时,回收该对象。
- 优点:对象可以很快被回收,不会出现内存耗尽或者达到阈值才回收。
- 缺点:不能很好的处理循环引用。
- 标记-清除:从根变量开始遍历所有引用的对象,引用的对象标记“被引用”,没有标记的则进行回收。
- 优点:解决了引用计数的缺点。
- 缺点:需要 STW(stop the world),暂时停止程序运行。
- 分代收集:按照对象生命周期长短划分不同的代空间,生命周期长的放入老年代,短的放入新生代,不同代有不同的回收算法和回收频率。
- 优点:回收性能好
- 缺点:算法复杂
- 引用计数:每个对象维护一个引用计数,当被引用对象被创建或被赋值给其他对象时引用计数自动 +1。如果这个对象被销毁,那么计数-1,当计数为0时,回收该对象。
Go语言八个特性
- 高性能、高并发
- 语法简单、学习曲线平缓
- 丰富的标准库
- 完善的工具链
- 静态编译
- 快速编译
- 跨平台
- 垃圾回收
这八个特性前面基本上都说明了,这个跨平台特性需要说明一下,Java最大的特性之一也是跨平台,主要原因是使用了java虚拟机,java虚拟机屏蔽了本地的操作,提供了统一的函数。golang则直接将代码编译成立本地代码,调用本地操作。java的关键是虚拟机,golang关键是编译器。
GO语言入门
IDE选择
现在主流使用golang或者是vscode,我最后选择vscode,下载Go for Visual Studio Code插件。
GO环境安装
到官网下载go开发环境,下载的包提供了编译器、工具和标准库,下载安装的地址是GOROOT对应的路径,现在使用go mod来管理go包,需要设置GOPATH路径,下载的包都在这个路径下。 项目初始化流程如下:
go mod init <module_name>设置项目模块名,在当前目录下生成go.mod文件go mod tidy将需要的依赖加到go.modgo build xxx.go生成可执行文件./xxx运行可执行文件go run xxx.go直接运行go文件(会在临时文件目录下生成一个可执行文件)go get -u <package_name>可在Go Packages搜索工具包
GO语言基本语法
-
变量类型介绍。 go语言是一门强类型语言,每一个变量都有它自己的变量类型。 常见的变量类型包括字符串、整数、浮点型、布尔型等。 go 语言的字符串是内置类型,可以直接通过加号拼接,也能够直接用等于号去比较两个字符串。 在go语言里面,大部分运算符的使用和优先级都和 C 或者 C++ 类似,这里就不再概述。 下面讲述go语言里面的变量的声明,在go语言里面变量的声明有两种方式,一种是通过 var name string = "" 这种方式来声明变量,声明变量的时候,一般会自动去推导变量的类型。如果有需要,也可以显示写出变量类型。另一种声明变量的方式是: 使用变量 冒号 := 等于值。 下面来讲说常量。常量的话就是把 var 改成const,值得一提的是go语言里面的常量,它没有确定的类型,会根据使用的上下文来自动确定类型。
-
if else语句介绍。go语言里面的 if else 写法和 C 或者 C++ 类似。不同点是 if 后面没有括号。如果写括号的话,那么在保存的时候的编辑器会自动把括号去掉。第二个不同点是 Golang 里面的if,它必须后面接大括号,不能像 C 或者 C++ 一样,直接把 if 里面的语句同一行。
-
for循环介绍。在go里面没有 while 循环、do while 循环,只有唯一的一种 for 循环。最简单的 for 循环就是在 for 后面什么都不写,代表一个死循环。循环途中可以用 break 跳出,也可以使用经典的 C 循环,就是 for I 等于0, I 小于 NI 加加。这中间三段,任何一段都可以省略。 在循环里面,可以用 break 或者 continue 来跳出或者继续循环。
-
switch 分支结构介绍。go语言里面的 switch 分支结构。看起来也 C 或者 C++ 比较类似。同样的在 switch 后面的那个变量名,并不是要括号。 这里有个很大的一点不同的是,在c++里面, switch case 如果不不显示加 break 的话会然后会继续往下跑完所有的 case, 在go语言里面的话是不需要加 break 的。 相比 C 或者 C++ , go语言里面的 switch 功能更强大。可以使用任意的变量类型,甚至可以用来取代任意的 if else 语句。可以在 switch 后面不加任何的变量,然后在 case 里面写条件分支。这样代码相比用多个 if else 代码逻辑会更为清晰。
-
数组介绍。数组就是一个具有编号且长度固定的元素序列。比如这里的话是一个可以存放 5 个int元素的数组 A 。 对于一个数组,可以很方便地取特定索引的值或者往特定索引取存储值,然后也能够直接去打印一个数组。不过,在真实业务代码里面,很少直接使用数组,因为它长度是固定的,用的更多的是切片。
-
切片介绍。不同于数组可以任意更改长度,然后也有更多丰富的操作。比如说可以用 make 来创建一个切片,可以像数组一样去取值,使用 append 来追加元素。 注意 append 的用法的话,必须把 append 的结果赋值为原数组。 因为 slice 的原理实际上是它有一个它存储了一个长度和一个容量,加一个指向一个数组的指针,在执行 append 操作的时候,如果容量不够的话,会扩容并且返回新的 slice。 slice 此初始化的时候也可以指定长度。 slice 拥有像 python 一样的切片操作,比如这个代表取出第二个到第五个位置的元素,不包括第五个元素。不过不同于python,这里不支持负数索引。
-
map介绍。 在其他编程语言里面,它可能可以叫做哈希或者字典。 map 是实际使用过程中最频繁用到的数据结构。 可以用 make 来创建一个空 map ,这里会需要两个类型,第一个是那个 key 的类型,这里是 string 另一个是 value 的类型,这里是 int 。 可以从里面去存储或者取出键值对。可以用 delete 从里面删除键值对。 golang的map是完全无序的,遍历的时候不会按照字母顺序,也不会按照插入顺序输出,而是随机顺序。
-
下面来介绍range。对于一个 slice 或者一个 map 的话,可以用 range 来快速遍历,这样代码能够更加简洁。 range 遍历的时候,对于数组会返回两个值,第一个是索引,第二个是对应位置的值。如果不需要索引的话,可以用下划线来忽略。
-
函数介绍。 Golang 和其他很多语言不一样的是,变量类型是后置的。 Golang 里面的函数原生支持返回多个值。在实际的业务逻辑代码里面几乎所有的函数都返回两个值,第一个是真正的返回结果,第二个值是一个错误信息。
-
指针介绍。go里面也支持指针, 相比 C 和 C++ 里面的指针,支持的操作很有限。指针的一个主要用途就是对于传入参数进行修改。来看这个函数。这个函数试图把一个变量+2。但是单纯像上面这种写法其实是无效的。因为传入函数的参数实际上是一个拷贝,那也说这个+2,是对那个拷贝进行了+2, 并不起作用。如果需要起作用的话,那么需要把那个类型写成指针类型,那么为了类型匹配,调用的时候会加一个 & 符号。
-
结构体介绍。结构体的话是带类型的字段的集合。 比如这里 user 结构包含了两个字段,name 和 password。可以用结构体的名称去初始化一个结构体变量,构造的时候需要传入每个字段的初始值。也可以用这种键值对的方式去指定初始值,这样可以只对一部分字段进行初始化。同样的结构体也能支持指针,这样能够实现对于结构体的修改,也可以在某些情况下避免一些大结构体的拷贝开销。
-
结构体方法介绍。在 Golang 里面可以为结构体去定义一些方法。会有一点类似其他语言里面的类成员函数。 比如这里,把上面一个例子的 checkPassword 的实现,从一个普通函数,改成了 结构体方法。 这样用户可以 像 a.checkPassword(“xx”) 这样去调用。 具体的代码修改,就是把第一个参数,加上括号,写到函数名称前面。 在实现结构体的方法的时候也有两种写法,一种是带指针,一种是不带指针。这个它们的区别的话是说如果带指针的话,那么就可以对这个结构体去做修改。如果不带指针的话,那实际上操作的是一个拷贝,就无法对结构体进行修改。
-
错误处理介绍。 在 go 语言里面符合语言习惯的做法就是使用一个单独的返回值来传递错误信息。 不同于 Java 自家家使用的异常。go语言的处理方式,能够很清晰地知道哪个函数返回了错误,并且能用简单的 if else 来处理错误。 在函数里面,可以在那个函数的返回值类型里面,后面加一个 error, 就代表这个函数可能会返回错误。 那么在函数实现的时候, return 需要同时 return 两个值,要么就是如果出现错误的话,那么可以 return nil 和一个 error。如果没有的话,那么返回原本的结果和 nil。
-
字符串操作。在标准库 strings 包里面有很多常用的字符串工具函数,比如 contains 判断一个字符串里面是否有包含另一个字符串 , count 字符串计数, index 查找某个字符串的位置。 join 连接多个字符串 repeat 重复多个字符串 replace 替换字符串。
-
字符串格式化。在标准库的 FMT 包里面有很多的字符串格式相关的方法,比如 printf 这个类似于 C 语言里面的 printf 函数。不同的是,在go语言里面的话,可以很轻松地用 %v 来打印任意类型的变量,而不需要区分数字字符串。也可以用 %+v 打印详细结果,%#v 则更详细。
-
JSON 操作介绍。go语言里面的 JSON 操作非常简单,对于一个已有的结构体,只要保证每个字段的第一个字母是大写,也就是是公开字段。那么这个结构体就能用 JSON.marshaler 去序列化,变成一个 JSON 的字符串。 序列化之后的字符串也能够用 JSON.unmarshaler 去反序列化到一个空的变量里面。 这样默认序列化出来的字符串的话,它的风格是大写字母开头,而不是下划线。可以在后面用 json tag 等语法来去修改输出 JSON 结果里面的字段名。
-
时间处理。在go语言里面最常用的就是 time.now() 来获取当前时间,然后也可以用 time.date 去构造一个带时区的时间,构造完的时间。上面有很多方法来获取这个时间点的年月日小时分钟秒,然后也能用点 sub 去对两个时间进行减法,得到一个时间段。时间段又可以去得到它有多少小时,多少分钟、多少秒。 在和某些系统交互的时候,经常会用到时间戳。那可以用 .UNIX 来获取时间戳。
-
字符串和数字之间的转换。在 go 语言当中,关于字符串和数字类型之间的转换都在 STR conv 这个包下,这个包是 string convert 这两个单词的缩写。 可以用 parseInt 或者 parseFloat 来解析一个字符串,可以用 Atoi 把一个十进制字符串转成数字, 可以用 itoA 把数字转成字符串。 如果输入不合法,那么这些函数都会返回error。
-
获取进程信息。在 go 里面,能够用 os.argv 来得到程序执行的时候的指定的命令行参数。 比如编译的一个 二进制文件,command。 后面接 abcd 来启动,输出就是 os.argv 会是一个长度为 5 的 slice ,第一个成员代表二进制自身的名字, 可以用 so.getenv来读取环境变量。
Go 语言实战
在这一部分,字节内部课:Go 语言上手 - 基础语法通过三个简单的小项目带领学生学习了 Go 语言语法及其标准库使用:一个经典的猜数字游戏,给定一个随机数,让用户猜测这个数并给出与这个数相比是大了还是小了;一个在线词典,通过 HTTP 爬虫爬取其他在线词典网站的结果并返回;一个 SOCKS5 代理,简单的实现了 SOCKS 5 的握手流程,但实现起来还是有点难度,只是把原理搞清楚了。