Go基础笔记
Hello, World
创建文件 hello.go
1 | package main |
- 需要注意的是 { 不能单独放在一行,不然会报错
命令行运行
1 | go run hello.go |
在 Go 语言里,命名为 main 的包具有特殊的含义。 Go 语言的编译程序会试图把这种名字的包编译为二进制可执行文件。所有用 Go 语言编译的可执行程序都必须有一个名叫 main 的包。一个可执行程序有且仅有一个 main 包。
当编译器发现某个包的名字为 main 时,它一定也会发现名为 main()
的函数,否则不会创建可执行文件。 main()
函数是程序的入口,所以,如果没有这个函数,程序就没有办法开始执行。
程序编译时,会使用声明 main 包的代码所在的目录的目录名作为二进制可执行文件的文件名。
格式化字符串
Go 语言中使用 fmt.Sprintf 或 fmt.Printf 格式化字符串并赋值给新串:
- Sprintf 根据格式化参数生成格式化的字符串并返回该字符串。
- Printf 根据格式化参数生成格式化的字符串并写入标准输出。
1 | func main() { |
Println不支持格式化字符串,输入后会拼接字符串
变量
定义
var
关键字:表示定义变量
1 | func main() { |
语法糖:对局部变量可以使用:=
(自动推导),当在定义时
1 | func main() { |
指针
跟c++一样
1 | func main() { |
批量赋值
1 | func main() { |
如果使用自动识别的话,可以批量定义不同类型的变量
1 | func main() { |
匿名变量
通过_
来废弃变量
1 | func test() (int, int) { |
作用域
全局变量和函数的作用域是全局而不是正下方
1 | func main() { |
局部变量和全局变量同名,优先考虑局部变量
常量
const
关键字:表示定义常量
1 | func main() { |
iota
iota,特殊常量,可以认为是一个可以被编译器修改的常量。iota是go语言的常量计数器
iota 在 const关键字出现时将被重置为0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可埋解为 const 语句块中的行索引)。
iota 可以被用作枚举值:
1 | func main() { |
基本数据类型
布尔型
1 | func main() { |
数字型
整型
Go 也有基于架构的类型,例如:int、uint 和 uintptr。
序号 | 类型和描述 |
---|---|
1 | uint8 无符号 8 位整型 (0 到 255) |
2 | uint16 无符号 16 位整型 (0 到 65535) |
3 | uint32 无符号 32 位整型 (0 到 4294967295) |
4 | uint64 无符号 64 位整型 (0 到 18446744073709551615) |
5 | int8 有符号 8 位整型 (-128 到 127) |
6 | int16 有符号 16 位整型 (-32768 到 32767) |
7 | int32 有符号 32 位整型 (-2147483648 到 2147483647) 相当于c++的int |
8 | int64 有符号 64 位整型 (-9223372036854775808 到 9223372036854775807) 相当于c++的long long |
浮点型
序号 | 类型和描述 |
---|---|
1 | float32 IEEE-754 32位浮点型数 |
2 | float64 IEEE-754 64位浮点型数 |
3 | complex64 32 位实数和虚数 |
4 | complex128 64 位实数和虚数 |
其他
序号 | 类型和描述 |
---|---|
1 | byte 类似 uint8 |
2 | rune 类似 int32 |
3 | uint 32 或 64 位 |
4 | int 与 uint 一样大小 |
5 | uintptr 无符号整型,用于存放一个指针 |
byte 相当于 unit8
rune 相当于 int 32
int 相当于 int64
默认的小数是float64
1 | func main() { |
字符/字符串
go的字符串跟Python差不多,但是不允许修改
go不存在char类型,字符是直接通过整数存储的
1 | func main() { |
字符串直接通过加号拼接
1 | func main() { |
获取长度
1 | func main() { |
获取指定字节
不允许超出字符串长度,会报错
1 | func main() { |
数据类型转换
由于Go语言不存在隐式类型转换,因此所有的类型转换都必须显式的声明
1 | func main() { |
有些类型之间不能转换,整数浮点数转换超限也能按规则溢出转换,但是会造成精度丢失
1 | func main() { |
运算符
跟c++一模一样
指针运算符
1 | func main() { |
go没有三目运算符,也没有引用运算符
流程控制
if
条件无需加括号
1 | func main() { |
switch
不需要break
1 | func main() { |
使用 fallthrough
会强制执行后面的 case 语句,fallthrough
不会判断下一条 case 的表达式结果是否为 true。
1 | func main() { |
for
Go 语言的 For 循环有 3 种形式,只有其中的一种使用分号。
break
和continue
和c++英语
和 C++ 语言的 for
一样:
1 | for init; condition; post { } |
和 C++ 的 while
一样:
1 | for condition { } |
和 C++ 的 for(;;)
一样:
1 | for { } |
for-each
goland里输入 数组名.for
可以打出
- 对数组
1 | func main() { |
- 对map
1 | func main() { |
函数
go的函数本身就是一个数据类型
函数定义格式:
1 | func function_name( [parameter list] ) [return_types] { |
无返回值省略最后的括号
函数返回多个值
Go 函数可以返回多个值,例如:
1 | func swap(x, y string) (string, string) { |
指针传递
默认的传参都是和c++一样调用复制构造函数的传参
想要引用传参需要使用指针
1 | func swap(x, y *int) { |
可变参数
通过...数据类型
表示可变参数
可变参数只能有一个,且要放在参数列表后面
1 | func getSum(nums ...int) int { |
匿名函数
函数本身就是一个数据类型,可以赋值给变量
定义函数的本质就是开辟了一个空间,用于存储函数的执行代码
1 | func main() { |
Go 语言支持匿名函数,可作为闭包。匿名函数是一个”内联”语句或表达式。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。
匿名函数是一种没有函数名的函数,通常用于在函数内部定义函数,或者作为函数参数进行传递。
1 | package main |
函数方法
一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。
1 | func (variable_name variable_data_type) function_name() [return_type]{ |
1 | package main |
defer
defer语句会将传入的函数留到最后执行,其中,靠后的defer先执行,但是传参是在使用defer的时候就传好了
1 | func main() { |
输出:
1
3
5
4
2
回调函数
因为函数可以当做一个变量,所以函数可以作为另一个函数的参数
1 | import "fmt" |
函数闭包
函数闭包的特色在于能够改变局部变量的生命周期,使其能够持久存在并被操作
1 | func getSequence() func() int { |
数组
声明
Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:
1 | var arrayName [size]dataType |
其中,arrayName 是数组的名称,size 是数组的大小,dataType 是数组中元素的数据类型。:
1 | var balance [10]float32 |
初始化
在声明时,数组中的每个元素都会根据其数据类型进行默认初始化,对于整数类型,初始值为 0。
1 | var numbers [5]int |
还可以使用初始化列表来初始化数组的元素:
1 | var numbers = [5]int{1, 2, 3, 4, 5} |
还可以使用 := 简短声明语法来声明和初始化数组:
1 | numbers := [5]int{1, 2, 3, 4, 5} |
注意:在 Go 语言中,数组的大小是类型的一部分,因此不同大小的数组是不兼容的,也就是说 [5]int 和 [10]int 是不同的类型。
如果数组长度不确定,可以使用 ...
代替数组的长度,编译器会根据元素个数自行推断数组的长度:
1 | var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0} |
如果设置了数组的长度,我们还可以通过指定下标来初始化元素:
1 | // 将索引为 1 和 3 的元素初始化 |
初始化数组中 {} 中的元素个数不能大于 [] 中的数字。
如果忽略 [] 中的数字不设置数组大小,Go 语言会根据元素的个数来设置数组的大小:
访问数组元素
数组元素可以通过索引来读取(方法同python)
1 | var salary float32 = balance[9] |
多维数组
多维数组可通过大括号来初始值。
1 | a := [3][4]int{ |
注意:以上代码中倒数第二行的}必须要有逗号,因为最后一行的}不能单独一行,也可以写成这样:
1 | a := [3][4]int{ |
和c++一样多维数组只能省略最左边的大小
1 | func main() { |
数组传参
Go的数组作为参数和c++不同,Go 语言中的数组是值类型,因此在将数组传递给函数时,实际上是传递数组的副本。
1 | func test(a [4]int) { |
切片
定义
可以声明一个未指定大小的数组来定义切片:
1 | var identifier []type |
或使用 make()
函数来创建切片:
1 | var slice1 []type = make([]type, len) |
也可以指定容量,其中 capacity
为可选参数。
1 | make([]T, length, capacity) |
这里 len 是数组的长度并且也是切片的初始长度。
初始化
1 | s :=[] int {1,2,3} |
直接初始化切片,**[]** 表示是切片类型,**{1,2,3}** 初始化值依次是 1,2,3,其 cap=len=3。
1 | s := arr[:] |
初始化切片 s,是数组 arr 的引用。
1 | s := arr[startIndex:endIndex] |
将 arr 中从下标 startIndex 到 endIndex-1 下的元素创建为一个新的切片,前后都可以省略
1 | s1 := s[startIndex:endIndex] |
通过切片 s 初始化切片 s1。
1 | s := make([]int,len,cap) |
通过内置函数 make() 初始化切片s,**[]int** 标识为其元素类型为 int 的切片。
len() 和 cap() 函数
切片是可索引的,并且可以由 len() 方法获取长度。
切片提供了计算容量的方法 cap() 可以测量切片最长可以达到多少。
空(nil)切片
一个切片在未初始化之前默认为 nil,长度为 0
1 | func main() { |
切片截取
通过arr[begin:end]
来截取
跟Python一样左闭右开
append() 和 copy() 函数
如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。
1 | func main() { |
Map
定义
1 | // 创建一个空的 Map |
1 | // 使用字面量创建 Map |
获取
1 | // 获取键值对 |
修改
1 | // 修改键值对 |
长度
1 | // 获取 Map 的长度 |
遍历
1 | // 遍历 Map |
删除
1 | // 删除键值对 |
结构体
定义
结构体定义需要使用 type 和 struct 语句
1 | type Books struct { |
访问
使用.
运算符,结构体.成员名
权限
在 Go 语言中,结构体字段和方法的访问权限是通过字段名的大小写来确定的。
- 如果字段名以大写字母开头(例如
Name
),则该字段是导出的,可以在结构体所属的包外部访问。 - 如果字段名以小写字母开头(例如
age
),则该字段是非导出的,只能在结构体所属的包内部访问。
接口
Go 语言中的接口是隐式实现的,也就是说,如果一个类型实现了一个接口定义的所有方法,那么它就自动地实现了该接口。因此,我们可以通过将接口作为参数来实现对不同类型的调用,从而实现多态。
1 | package main |
错误处理
error 类型是一个接口类型,这是它的定义:
1 | type error interface { |
1 | package main |
并发
goroutine
goroutine 是轻量级线程,goroutine 的调度是由 Golang 运行时进行管理的。
goroutine 语法格式:
1 | go 函数名( 参数列表 ) |
Go 允许使用 go 语句开启一个新的运行期线程, 即 goroutine,以一个不同的、新创建的 goroutine 来执行一个函数。 同一个程序中的所有 goroutine 共享同一个地址空间。
1 | package main |
执行以上代码,你会看到输出的 hello 和 world 是没有固定先后顺序。因为它们是两个 goroutine 在执行:
channel
通道(channel)是用来传递数据的一个数据结构。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。
使用 make
函数创建一个 channel,使用 <-
操作符发送和接收数据。如果未指定方向,则为双向通道。
1 | ch <- v // 把 v 发送到通道 ch |
声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:
1 | ch := make(chan int) |
注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
以下通过两个 goroutine 来计算数字之和,在 goroutine 完成计算后,它会计算两个结果的和:
1 | func sum(s []int, c chan int) { |
缓冲区
通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:
1 | ch := make(chan int, 100) |
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。
遍历通道与关闭通道
Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下:
1 | v, ok := <-ch |
如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。
1 | func fibonacci(n int, c chan int) { |
Select语句
select
语句使得一个 goroutine 可以等待多个通信操作。select
会阻塞,直到其中的某个 case 可以继续执行:
1 | func fibonacci(c, quit chan int) { |
- 本文作者: NICK
- 本文链接: https://nicccce.github.io/TechNotes/Go-Note/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!