Go语言简介
Go语言(或 Golang)起源于 2007 年,并在 2009 年正式对外发布。Go语言是一门非常年轻的语言,它的主要目标是"兼具 Python 等动态语言的开发速度和 C/ C++ 等编译型语言的性能与安全性"。Go语言是编程语言设计的又一次尝试,是对类C语言的重大改进,它不但能让你访问底层操作系统,还提供了强大的网络编程和并发编程支持。Go语言的用途众多,可以进行网络编程、系统编程、并发编程、分布式编程。Go语言的推出,旨在不损失应用程序性能的情况下降低代码的复杂性,具有"部署简单、并发性好、语言设计良好、执行性能好"等优势,目前国内诸多 IT 公司均已采用Go语言开发项目。Go语言有时候被描述为"C 类似语言",或者是"21世纪的C语言"。
开发环境配置
Go的开发环境可以选择VSCode或者Golang的,这是如今功能最多、使用最广泛的编辑器或者IDE。
-
VSCODE是一款由微软公司开发的,能运行在Mac OS X、Windows和Linux上的跨平台开源代码编辑器 虽然它是一款编辑器,但是它可以通过扩展程序为编辑器实现,包括代码高亮、代码提示编译调试、文档生成等功能配置完成之后可以视为一个功能齐全的IDE。安装VSCode,直接从官网下载安装即可,安装完成之后,需要在左边扩展里面搜索Go插件然后安装。
-
Goland是由JetBrains公司开发的一个新的商业IDE,相比VScode,它在重构、代码生成等方面做得更好。Goland 是一个收费软件, 我们可以直接从官网下载,然后可以30天免费试用。对于在校学生可以申请免费的教育许可证。在校期间都可以直接免费使用。(不过国内大部分高校的校园邮箱都被JetBrain拉黑了,无法申请许可证,因此建议自己上网找破解方法)
具体过程这里就不细说了,VScode的环境配置会相对麻烦一点,需要有耐心。
Go语言基础语法
- 看这一部分需要一定的C语言或C++的基础(相似的部分写的比较粗糙)
Helloworld示例
package main
import (
"fmt"
)
func main() {
//此处大括号必须与函数名在同一行,因为Go语言在编译时会自动添加分号(行尾为左括号或逗号等情况除外)
fmt.Println("hello world")
}
(可用这段代码来检查环境是否配置成功)
第一行 package main 代表这个文件属于main包的一部分,main包也就是程序的入口包。
第三行导入了标准库里面的fmt包。这个包主要是用来往屏幕输入输出字符串、格式化字符串。
import 的下面是main函数,main函数里面调用了 fmt.Println 函数(Go语言最基本的输出函数,其相对于 fmt.Print 的区别在于会在结尾进行换行;另外,fmt包中同样包含 Printf ,Scanln ,Scan 和 Scanf ,感兴趣的可以自己去查一下)输出 hello world 。
要运行这个程序的话,我们就直接在终端输入 go run helloworld.go(文件名.go)。如果我们想编译成二进制的话,可以用 go build helloworld.go 来编译。编译完成之后直接 /helloworld 就可以运行。
在FMT包里面还有很多的函数来做不同的输入输出格式化工作。大家可以在编辑器里面把鼠标悬浮在你的代码上,就可以看到每一个函数的文档。
你也可以进入pkg.go.dev ,搜索包名比如FMT然后就能看到这个包的在线文档(在网址后面直接加上/包名也可以),可以从里面去挑选你需要的函数来使用。
注释与转义字符
Go语言的注释与C语言相同,分为单行(//)和多行(/* */),另外对于整块需要注释掉的代码,可以选中后使用 ctrl 或 command + / 进行注释。
Go语言的转义字符用 \ + 字符 表示,与其他语言类似。
示例代码:
//转义字符
func main() {
fmt.Println("\"Hello world!\"\nHello world!")
}
输出如下:
变量与数据类型
Go语言是一种强类型语言,每一个变量都有它自己的数据类型。常见的数据类型包括字符串、整型、浮点型、布尔型、指针、结构体、复数等。其中,Go语言的指针与C语言和C++中的指针一致,但应用的场景较C语言而言更加单一,一般用于函数传参。Go语言没有单独的字符类型,其中 byte 是 uint8 的别名,范围为-128 ~ 127,对应ASCII;rune 是 int8 的别名,范围为0 ~ 255,对应UTF-8(ASCII的超集)。
常见的数据类型及数组的默认值如下:(Go语言中所有的值类型变量常量都会在声明时被分配内存空间并被赋予默认值)
int:0
float:0.00000
string:""
结构体:根据结构体内部的基础数据类型进行初始化赋值,下面会有demo
数组(切片):空数组
指针:nil
bool:false
在Go语言里面,大部分运算符的使用和优先级都和C或者C++类似,这里就不再概述。
此外还需要说明的是,Go语言的字符串是内置类型,可以直接通过加号拼接,也能够直接用等于号去比较两个字符串,相比C语言更为便捷。
下面讲述Go语言里面的变量的声明,在Go语言里面变量的声明有两种方式:
一种是通过 var name(变量名) string(数据类型,可以省略) = ""(赋值也可省略) 这种方式来声明变量,声明变量的时候,编译器一般会自动去推导变量的数据类型。如果有需要,你也可以选择写出类型。(这种方法也可以同时声明多个变量,格式如下:)
var{
变量名1 数据类型1 = ""
变量名2 数据类型2 = ""
...
}
另一种声明变量的方式是: 使用变量冒号 := 等于值。(注意这种方法不能用于声明全局变量)
最后来讲常量。常量的话就是把 var 改成 const ,但在批量声明常量时,所有未赋值的常量都默认与上一行的值相同(还有一个用于查询所在行数的iota函数,感兴趣的可以自己去查一下)。值得一提的是,Go语言里面的常量没有确定的类型,会根据使用的上下文来自动确定类型。
- 注意:需要跨包调用的变量和常量需要首字母大写,函数同理。
示例代码:
func main() {
var a = "initial"
var b, c int = 1, 2
var d = true
var e float64
var ptr *int = &a //指针用 * + 其他数据类型表示
f := float32(e)
g := a + "apple"
fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
fmt.Println(g) // initialapple
const s string = "constant"
const h = 500000000
const i = 3e20 / h
fmt.Println(s, h, i) //constant 500000000 6e+11
}
fmt格式字符(fmt verbs)
fmt verbs 一般被用于 fmt.Printf 函数中,其用法和功能与C语言中的 printf 函数基本一致。值得一提的是,在Go语言中,我们可以直接使用 %v 来输出对应的变量或常量,无需区分类型。
if...else语句
Go语言里的 if...else语句与其他语言类似,但需注意编译时自动加分号的问题。除此之外,Go语言支持在条件表达式之前执行一个简单的语句,但仅这个方式声明的变量的作用域仅在 if...else语句之内,与for循环类似。
示例代码:
func main() {
var num int
fmt.Scanln(&num)
if a:= 0; num < 0 && a == 0 {
fmt.Println(num, "is negative")
} else if num < 10 && a == 0 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
switch...case语句
Go语言里的 switch...case语句与C语言和C++类似,但有两点很大的不同:
-
Go语言在符合条件的case结尾会自动break,如果需要匹配下一项可以加上 fallthrough 。
-
Go语言里的switch语句支持任何变量类型,同时也可以不写变量,因为该语句中对于 case 的判断仅基于 case 后面条件的布尔值,这也使得Go语言中的 switch...case语句可以完全替代 if...else语句
示例代码:
func main() {
a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four or five")
default:
fmt.Println("other")
}
t := time.Now() //用于获取当前时间
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
switch {
case false:
fmt.Println("1")
fallthrough
case true:
fmt.Println("2")
fallthrough
case false:
fmt.Println("3")
fallthrough
default:
fmt.Println("default case")
}
for循环
Go语言 有且仅有 for循环这一种循环,但该循环可完全替代while循环的功能。此外,Go语言同样支持 break ,continue 和 goto 。
示例代码:
func main() {
i := 1
//无限循环
for {
i++
fmt.Println("loop")
if i > 10 {
break
}
}
//条件循环(与其他语言的while循环同理)
for i <= 15 {
fmt.Println(i)
i++
}
//标准循环
for j := 7; j < 9; j++ {
fmt.Println(j)
}
}
label(标签)
Go语言同样也支持label语法,用法分别是break label 、 goto label 和 continue label ,其中label的名称可任取。需要注意的是,Go语言中的break标签只能用于for循环且必须放在循环语句之前,不能用于switch语句中。
示例代码:
func TestGoto() { //结果为不断打印1
one:
fmt.Println(1)
goto one //跳转
fmt.Println(2)
fmt.Println(3)
}
func TestBreak() { //结果为1\n2\n3,continue标签的用法类似
OUTER:
for {
fmt.Println(1)
for {
fmt.Println(2)
break OUTER
}
}
fmt.Println(3)
}
函数(func)
Go语言里的函数与其他语言类似,仅需注意其格式即可。
在Go语言中,函数也是一种数据类型,其本质为一个指向函数内存地址的指针常量。因此,函数的返回值也可以是函数,也就是所谓的闭包函数。此外,Go语言还支持匿名函数。
示例代码:
func cal(a, b int) (int, int) {
//第1个int表示a和b的类型(也可以分开写),第2、3个int表示返回值的类型,仅一个返回值时无需括号
return a + b, a * b
}
func cal(a, b int) (sum, pro int) {
//若已知返回值对应变量可不用return传递返回值
sum := a + b
pro := a * b
return
}
Exception处理(defer ,panic ,recover)
Go语言通过defer、 panic、 recover这三个关键字进行 Exception处理(异常处理与捕获)。同时,Go语言并不支持传统的 try…catch…finally 这种异常。
这几个异常的使用场景可以这么简单描述:Go语言中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。
defer
defer 是Go语言中一种延迟调用机制,defer 后面的函数只有在当前函数执行完毕后,即 return语句之前才能执行,将延迟的语句按 defer 的逆序进行执行,也就是说先被defer的语句最后被执行,最后被defer的语句,最先被执行,符合栈先入后出的顺序,通常用于释放资源。
panic
假如函数F中书写了 panic语句,会终止其后要执行的代码,在 panic 所在函数F内如果存在要执行的 defer函数列表,按照 defer 的逆序执行。之后返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,则按照defer的逆序执行,这里的 defer 有点类似 try...catch...finally 中的 finally。如果还有嵌套的外层函数,则重复上述过程直到 goroutine(Go语言的轻量级线程,此处可理解为正在运行的程序)整个退出,并报告错误。
recover
Go语言中的 recover 用来控制一个 goroutine 的 panicking 行为,捕获 panic ,从而影响程序的行为。
一般的调用建议:(具体可参考下面的例子)
-
在defer函数中,通过 recover 来终止一个 goroutine 的 panicking 过程,从而恢复正常代码的执行.
-
可以获取通过 panic 传递的 error 。
简单来讲:Go语言中可以抛出一个 panic 的异常,然后在 defer 中通过 recover 捕获这个异常,然后正常处理。
- 注意:利用 recover 处理 panic 指令,defer 必须在 panic 之前声明,否则当 panic 时,recover 无法捕获到 panic 。
示例代码:
func main() {
defer func() { // 必须要先声明defer,否则不能捕获到panic异常
fmt.Println("d")
if err := recover(); err != nil {
fmt.Println(err) // 这里的err其实就是panic传入的内容
}
fmt.Println("e")
}()
f() //开始调用f
fmt.Println("f") //这里开始下面代码不会再执行
}
func f() {
fmt.Println("a")
panic("异常信息")
fmt.Println("b") //这里开始下面代码不会再执行
fmt.Println("c")
}
包(package)
Go语言使用包来组织源代码的,并实现命名空间的管理,任何一个Go语言程序必须属于一个包,即每个go程序的开头要写上package <pkg_name>(包名)。
Go语言包一般要满足如下三个条件:
- 同一个目录下的同级的所有go文件应该属于一个包;
- 包的名称可以跟目录不同名,不过建议同名;
- 一个Go语言程序有且只有一个main函数,他是Go语言程序的入口函数,且必须属于main包。当Go语言程序没有或者有多于一个main包时,进行编译时都会报错;
与其他语言类似,Go语言同样通过 import 来引入包。Go包的引入格式常见的有四种:(此处的fmt包仅用作举例,其他包同理)
- 标准引用
import fmt。 - 设置别名引用
import format_go(别名) fmt,这种引用可以使程序在调用fmt包时使用format_go(别名).的前缀。 - 省略方式的引用
import . fmt,这种引用相当于把fmt包的命名空间合并到当前程序的命名空间了,因此可以直接引用,调用fmt包内函数时不用再加上前缀fmt.。 - 仅执行包的初始化函数
import _ fmt。
另外,当一个go程序需要导入多个包时,可以使用单行导入或者多行导入,如下分别列出两种导入形式:
- 单行引入
import "package1"
import "package2`
- 多行引入
import (
package_1 "package01" //此处使用了设置别名引用
"package02"
)
- 另外,导入的包必须要被使用,否则程序编译时也会报错。若想消除此报错,可在引入的包前加上
'_'。
init函数
- 每个包(包括main包)都可以有自己的init函数,且可以有多个(名称均为init),该函数先于main函数执行。init函数的主要作用:
- 初始化不能采用初始化表达式初始化的变量。
- 程序运行前的注册。
- 实现sync.Once功能(用于使函数只执行一次)。
- 执行顺序(取决于包的依赖关系):被依赖包的全局变量 -> 被依赖包的init函数 -> … -> main包的全局变量 -> main的init函数 -> main函数 。假设一个go程序的引入包的顺序是:main包 -> 包A -> 包B -> 包C ,且包A、B、C均实现了init函数,那么整个初始化流程顺序则为:C.init -> B.init -> A.init -> main-> 包A -> 包B -> 包C 。
(语法的内容好像有点多,拆两篇发算了,这篇就到此为止吧╮(╯▽╰)╭)