1.GO语言特性
- 高性能、高并发: Go语言对于并发的支持是纯天然的,仅需一个关键字,就可以开启一个异步协程。
- 语法简单: GO语言的语法类似于C/C++,并在其基础上进行了大幅度的简化,以此换来了更好的维护性和平滑的学习曲线
- 快速编译: Go 是一门编译型语言,Go 语言的工具链将源代码及其依赖转换成计算机的机器指令(静态编译),编译后的二进制文件不依赖额外的运行环境,编译速度也非常快。
- 丰富的标准库: 从字符串处理到源码AST解析,功能强大且丰富的标准库是Go语言坚实的基础。
- 完善的工具链: Go有着完善的开发工具链,涵盖了编译,测试,依赖管理,性能分析等方方面面。 Go 语言提供的工具都通过一个单独的命令
go调用,go命令有一系列子命令。 - 垃圾回收: Go有着优秀的GC性能,大部分情况下GC延时都不会超过1毫秒。
2. GO语言入门
2.1 配置开发环境
- 安装Golang: 浏览器输入go.dev,打开Golang的官网,点击download。
- 打不开可以尝试Golang中国镜像( studygolang.coom/dl )
- 如果访问 github 的速度非常慢,可以配置 go mod proxy,打开 goproxy.cn/ 按照提示操作即可,配置完成你下载第三方包的速度会大大加快。
- 配置集成开发环境: GO的开发环境可以选择VSCode或者是Golang。
- 只需在VSCode中的左边扩展里搜索Go插件然后安装
- 只需在VSCode中的左边扩展里搜索Go插件然后安装
2.2 基础语法
Hello World
- 示例代码
package main
import (
"fmt"
)
func main() {
fmt.Println("hello world")
}
- 代码解释
package关键字代表的是当前go文件属于哪一个包,启动文件通常是main包,启动函数是main函数,在自定义包和函数时命名应当尽量避免与之重复。import是导入关键字, 声明必须跟在文件的package声明之后。括号里面的是被导入的包名。标准库的FMT包主要是用来往屏幕输入输出字符串、格式字符串的。注意:必须恰当导入需要的包,缺少了必要的包或者导入了不需要的包,程序都无法编译通过。func是函数声明关键字,用于声明一个函数。fmt.Println("hello world")是一个语句,调用了fmt包下的Println函数进行控制台输出
注意:Go 语言不需要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号,因此换行符添加的位置会影响 Go 代码的正确解析。举个例子,函数的左括号 { 必须和 func 函数声明在同一行上,且位于末尾,不能独占一行。
-
运行程序
在命令行输入
go run fileName.go。也可以在go build来编译,编译完成后直接./helloworld就可以运行
nil
在Go语言中,nil 是一个预声明的**标识符,用来表示:
- 一个指针、通道、函数、接口或切片类型的变量没有任何指向或包含的值(即没有指向任何东西)。
- 一个函数没有返回值。
- 一个方法没有绑定到特定的接收者。
变量
Go 语言变量名由字母、数字、下划线组成,其中首个字符不能为数字。
变量声明
在GO中的类型声明是后置的,声明方式如下:
- 第一种:指定变量类型,如果没有初始化,则变量默认为零值。例如:
var b int - 第二种:根据值自行判断变量类型。例如:
var d = true - 第三种:使用
:=(如果变量已经使用var声明过了,再使用:=声明变量,就产生编译错误) 例如:f := "Runoob"
示例代码
package main
import (
"fmt"
"math"
)
func main() {
var a = "initial"
var b, c int = 1, 2
var d = true
var e float64
f := float32(e)
g := a + "foo"
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, math.Sin(h), math.Sin(i))
}
控制流程
示例代码
package main
import (
"fmt"
"time"
)
func main() {
// If 语句
if 8 > 5 {
fmt.Println("8 is greater than 5")
} else {
fmt.Println("8 is not greater than 5")
}
// Switch 语句
i := 2
switch i {
case 1:
fmt.Println("i is 1")
case 2:
fmt.Println("i is 2")
case 3:
fmt.Println("i is 3")
default:
fmt.Println("i is none of the above")
}
// For 循环
for i := 0; i < 3; i++ {
fmt.Println("iteration", i)
}
// For 循环的另一种形式,相当于 C 语言中的 while 循环
for {
fmt.Println("loop forever")
break // 需要一个 break 来退出循环,否则会无限循环
}
}
条件语句
if else
Go语言和C/C++的if else语句的区别
- Go语言的
if语句不需要括号包围条件表达式。 - Go语言的
if语句中,条件后面不需要分号。 - Go语言支持
if语句的简短形式,即可以在条件语句中直接声明变量(短变量声明),该变量的作用域仅在if代码块内。
注意:Go 没有三目运算符,所以不支持 ?: 形式的条件判断。
switch
Go语言和C/C++的switch语句的区别
- Go语言的
switch不需要break语句,每个case块执行完毕后会自动退出。 case后面可以是任何类型的表达式,不仅仅是整数。- 如果没有指定
default,并且没有匹配的case,则switch语句什么也不做,直接结束。 - Go语言的
switch可以不使用任何变量或表达式,直接根据条件判断执行不同的case块。
循环语句
Go语言和C/C++的循环语句的区别
- Go语言的
for循环更加简洁,不需要初始化、条件和迭代部分,可以直接只写条件。 - Go语言的
for循环中,条件后面不需要分号。 - Go语言没有
while和do-while循环,所有的循环控制都使用for循环实现。
数组
GO语言的数组与C/C++类似。不过在实际开发过程中,因为数组的长度是固定的,我们更多使用的是切片。
声明数组
Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:
var arrayName [size]dataType
初始化数组
- 指定数组长度的方式初始化:在声明时,数组中的每个元素会根据其数据类型进行默认初始化,对于整数类型,初始值为0。例如:
var a [5]int - 使用值列表的方式初始化:
a := [5]int{1, 2, 3, 4, 5}
示例代码
package main
import "fmt"
func main() {
var a [5]int
a[4] = 100
fmt.Println("get:", a[2])
fmt.Println("len:", len(a))
b := [5]int{1, 2, 3, 4, 5}
fmt.Println(b)
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
}
切片(Slice)
Go 语言切片是对数组的抽象。Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
数组和slice之间有着紧密的联系。一个slice是一个轻量级的数据结构,提供了访问数组子序列(或者全部)元素的功能,而且slice的底层确实引用一个数组对象。一个slice由三个部分构成:指针、长度和容量。指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。
定义切片
- 声明一个未指定大小的数组来定义切片:
var sclicName []type - 使用make() 函数来创建切片
slice1:=make([]T, length, capacity)
切片初始化
- 直接初始化切片:
s :=[] int {1,2,3 } - 初始化切片 s,是数组 arr 的引用:
s := arr[startIndex:endIndex] - 通过切片 s 初始化切片 s1
s1 := s[startIndex:endIndex]
s :=make([]int,len,cap)
空(nil)切片
一个切片在未初始化之前默认为 nil,长度为 0
切片截取
slice的切片操作s[i:j],其中0 ≤ i≤ j≤ cap(s),用于创建一个新的slice,引用s的从第i个元素开始到第j-1个元素的子序列。新的slice将只有j-i个元素。如果i位置的索引被省略的话将使用0代替,如果j位置的索引被省略的话将使用len(s)代替。
len() 和 cap() 函数
len(splic):切片是可索引的,并且可以由 len() 方法获取长度。
cap(splic):切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。
append() 和 copy() 函数
如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。
package main
import "fmt"
func main() {
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
c := make([]string, len(s))
copy(c, s)
fmt.Println(c) // [a b c d e f]
fmt.Println(s[2:5]) // [c d e]
fmt.Println(s[:5]) // [a b c d e]
fmt.Println(s[2:]) // [c d e f]
good := []string{"g", "o", "o", "d"}
fmt.Println(good) // [g o o d]
}
map
map的特点
- Map 是一种无序的键值对的集合。
- Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
- Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,遍历 Map 时返回的键值对的顺序是不确定的。
- 在获取 Map 的值时,如果键不存在,返回该类型的零值,例如 int 类型的零值是 0,string 类型的零值是 ""。
- Map 是引用类型,如果将一个 Map 传递给一个函数或赋值给另一个变量,它们都指向同一个底层数据结构,因此对 Map 的修改会影响到所有引用它的变量。
定义map
可以使用内建函数 make 或使用 map 关键字来定义 Map:
/* 使用 make 函数 */
map_variable := make(map[KeyType]ValueType, initialCapacity)
创建 Map
使用字面量创建 Map
m := map[string]int{
"apple": 1,
"banana": 2,
"orange": 3,
}
获取元素
// 获取键值对
v1 := m["apple"]
v2, ok := m["pear"] // 如果键不存在,ok 的值为 false,v2 的值为该类型的零值
修改元素
// 修改键值对
m["apple"] = 5
获取 Map 的长度
// 获取 Map 的长度
len := len(m)
遍历 Map
// 遍历 Map
for k, v := range m {
fmt.Printf("key=%s, value=%d\n", k, v)
}
删除元素
delete() 函数用于删除集合的元素, 参数为 map 和其对应的 key
// 删除键值对
delete(m, "banana")
range
对于一个 slice 或者一个 map 的话,我们可以用 range 来快速遍历,这样代码能够更加简洁。 range 遍历的时候,对于数组会返回两个值,第一个是索引,第二个是对应位置的值。如果我们不需要索引的话,我们可以用下划线来忽略。
package main
import "fmt"
func main() {
nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num) // index: 0 num: 2
}
}
fmt.Println(sum) // 9
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v) // b 8; a A
}
for k := range m {
fmt.Println("key", k) // key a; key b
}
}
函数
函数定义
Go 语言函数定义格式如下:
func function_name( [parameter list] ) [return_types] {
函数体
}
函数定义解析:
- func:函数由 func 开始声明
- function_name:函数名称,参数列表和返回值类型构成了函数签名。
- parameter list:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
- return_types:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
- 函数体:函数定义的代码集合。
函数返回多个值
Go 函数可以返回多个值,例如:
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("Google", "Runoob")
fmt.Println(a, b) //执行结果:Runoob Google
}
指针
相比 C 和 C++ 里面的指针,支持的操作很有限。指针的一个主要用途就是对于传入参数进行修改。
- 什么是指针
一个指针变量指向了一个值的内存地址。类似于变量和常量,在使用指针前你需要声明指针。指针声明格式:
var var_name *var-type
var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。以下是有效的指针声明:
var ip *int /* 指向整型*/
var fp *float32 /* 指向浮点型 */
-
如何使用指针
- 定义指针变量。
- 为指针变量赋值。
- 访问指针变量中指向地址的值。
- 在指针类型前面加上 * 号(前缀)来获取指针所指向的内容。
-
示例代码
package main
import "fmt"
func add2(n int) {
n += 2
}
func add2ptr(n *int) {
*n += 2
}
func main() {
n := 5
add2(n)
fmt.Println(n) // 5
add2ptr(&n)
fmt.Println(n) // 7
}