本教程仅简要记录GO
语言与已学过的Python
,C++
、Swift
等语言的基础方法上面的差别.
并不做深入讨论,仅作为备忘录存在。
变量定义
1 | var a int |
四种变量的定义方式:
- 第一种不初始化,此时必须指明变量的类型
- 第二种初始化,但是可能初始化值有歧义,需要显式定义数据类型
- 第三种和第四种等价,建议第四种,编译器会自行判断变量类型,类似
Python
这里注意:
a := 1
只能在函数中这么使用
全局变量的声明方式为:
1 | var( |
空白标识符在函数返回的时候使用,表示不获取该位置的返回值
1 | func main() { |
变量不进行声明,会给一个默认值:
数据类型 | 初始化默认值 |
---|---|
int | 0 |
float32 | 0 |
pointer | nil |
string | "" |
常量定义
1 | const a int = 1 |
全新的用法:iota:
iota是在声明常量的时候自动加一的编译器存储的变量。
1 | const ( |
1 | a = 0, b = 1, c = 2 |
第一个 iota 等于 0,每当 iota 在新的一行被使用时,它的值都会自动加 1
在使用枚举声明常量,如果变量不声明初始值,其初始值定义方式与上一行一样,有贯穿效果的。但是iota的值是会不断+1的!!
1 | const ( |
条件语句
条件语句——switch
1 | switch var1 { |
Go
中的Switch
是不带贯穿的,如果默认,在完成case后会自动break
。如果我们需要执行后面的 case,可以使用 fallthrough
。
但是Go
中可以同时判断多个case:
1 | case val1, val2, val3 |
此外,其还可以对type
进行判断。 ^_^
1 | switch x.(type){ |
条件语句——select
select
语句只能用于通道操作,每个 case 必须是一个通道操作,要么是发送要么是接收。select
语句会监听所有指定的通道上的操作,一旦其中一个通道准备好就会执行相应的代码块。- 如果多个通道都准备好,那么
select
语句会随机选择一个通道执行。如果所有通道都没有准备好,那么执行 default 块中的代码。
1 | select { |
以下描述了 select 语句的语法:
每个 case 都必须是一个通道
所有 channel 表达式都会被求值
所有被发送的表达式都会被求值
如果任意某个通道可以进行,它就执行,其他被忽略。
如果有多个 case 都可以运行,
select
会随机公平地选出一个执行,其他不会执行。否则:
- 如果有
default
子句,则执行该语句。 - 如果没有
default
子句,select 将阻塞,直到某个通道可以运行;Go
不会重新对 channel 或值进行求值。
- 如果有
函数
1 | func function_name( a int, b string ) (int, string, float32) { |
如果传入的参数一样,在最后定义类型即可:func swap(x, y string){}
引用传参
和C++
一样,Go
是带有指针的!!!!QAQ
1 | /* 定义交换值函数*/ |
在调用的时候,需要传入参数的地址:swap(&a, &b)
函数作为实参
1 | getSquareRoot := func(x float64) float64 { |
这个和Swift
有点像,如果一个参数的数据需要计算出来,就可以通过这种方式定义一个函数,来对实参的初始化数据进行计算。
闭包 T_T
和Swift
的闭包基本一样,属于一种匿名的函数,通常用于在函数内部定义函数,或者作为参数传递。
1 | func getSequence() func() int { |
↑ 该函数名称为getSequence
,没有传入参数,返回的是一个类型为:返回值为int
的函数。
当该函数被初始化到一个实参上,该函数内部数据i
被创建并保留。此时这个实参就为一个func() int
的函数。
1 | func getSequence() func() int { |
↑,作用就是i
被存储到了函数内部,每次重新函数创建都会重建内部参数。
方法
类中或者结构体中的函数就是方法
其定义语句为:
1 | func (variable_name variable_data_type) function_name() [return_type]{ |
1 | /* 定义结构体 */ |
数组
声明以及初始化
Go 语言数组声明需要指定元素类型及元素个数,语法格式如下:
1 | var balance [10]float32 |
数组的初始化:
1 | var numbers = [5]int{1, 2, 3, 4, 5} |
:=
的用法和变量时候的用法一致。
如果数组的长度不是固定的!可以用...
进行替代
1 | var balance = [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0} |
Go语言允许仅对某些位置的元素进行初始化
1 | balance := [5]float32{1:2.0,3:7.0} |
这样,只有索引为1和3的元素进行了初始化,其余元素均为0。
二维数组
1 | // var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type |
初始化
1 | a := [3][4]int{ |
注意:以上代码中倒数第二行的 } 必须要有逗号,因为最后一行的 } 不能单独一行,也可以写成这样
1 | a := [3][4]int{ |
对二维数组的访问和其余语言一样。
向函数传递数组
1 | func myFunction(param [10]int) { |
指针 T_T
和C++
基本一样的指针结构
声明方式:
1 | // var var_name *var-type |
可以认为在指针操作中:
&
运算符是获得地址的操作
*
运算符是对对地址进行一个解析的操作,来获得该地址上存储的数据。
空指针
当一个指针被定义后没有分配到任何变量时,它的值为 nil。
nil 指针也称为空指针。
nil在概念上和其它语言的null、None、nil、NULL一样,都指代零值或空值。
一个指针变量通常缩写为 ptr。
指针数组
指针数组为,一个数组,其中的每一个元素都是一个指针。初始化方式:
1 | var ptr [MAX]*int; |
指针的数组可以通过遍历的方式对应数组的每一个元素。
1 | func main() { |
指向指针的指针
1 | var ptr **int |
在解析指向指针的指针,也需要用**
来解析。
指针作为函数参数
这个和c++
一样。其实就是一个引用传参的变体。将地址传进去,这样对该地址的数据的操作,函数外侧该数据也会发生变化。
结构体
和Swift
的结构体大差不差,值传递的类,定义如下:
1 | type struct_variable_type struct { |
声明如下:
1 | variable_name := structure_variable_type {value1, value2...valuen} |
注意,和
Swift
一样,结构体是值传递,也就是说,对于a=b
,是新建一个一样的结构体赋值给a
,所以对a
的更改,并不会影响b
。所以对于函数的传参,值传参是无法更改函数外结构体的内容,必须用引用传参或者指针传参。
对切片的声明
1 | var identifier []type |
↑采用未声明长度的数组进行切片的声明。
↓或者采用make()
函数进行创建
1 | var slice1 []type = make([]type, len) |
也可以指定容量的大小,capacity
作为可选参数,定义切片的最大长度。
1 | make([]T, length, capacity) |
切片初始化
1 | s :=[] int {1,2,3 } // []中是空的 |
切片拥有len()
和cap()
函数,分别用来获得长度以及测量切片最长多长。
如果是空的切片,则和nil
相比较。
切片是可以通过类似Python
中的[:]
进行截取的。
切片拥有append()
和copy()
函数,前者用来增加元素,后者用来拷贝切片到新的切片。
我们可以看出切片,实际的是获取数组的某一部分,len切片<=cap切片<=len数组,切片由三部分组成:指向底层数组的指针、len、cap。
Range
和Python
一样,用来和for
一起遍历数组,但是其和python
中加入了enumrate
一样,是可以获得Index
的。
1 | for key, value := range oldMap { |
Map
Map 是一种无序的键值对的集合。
Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。
其实就是Python
中的dict
。
1 | /* 使用 make 函数 */ |
initialCapacity 是可选的参数,用于指定 Map 的初始容量。Map 的容量是指 Map 中可以保存的键值对的数量,当 Map 中的键值对数量达到容量时,Map 会自动扩容。如果不指定 initialCapacity,Go 语言会根据实际情况选择一个合适的值。
接口!! Interface!!
Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。
接口可以让我们将不同的类型绑定到一组公共的方法上,从而实现多态和灵活的设计。
Go 语言中的接口是隐式实现的,也就是说,如果一个类型实现了一个接口定义的所有方法,那么它就自动地实现了该接口。因此,我们可以通过将接口作为参数来实现对不同类型的调用,从而实现多态。
1 | package main |
错误处理
Go
内置的错误接口类型:
1 | type error interface { |
1 | package main |
接口与错误处理要和结构体合在一起。
并发!!!
1 | go f(x, y, z) |
1 | func say(s string) { |
通道Channel
通道(channel)是用来传递数据的一个数据结构。
通道可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯。操作符 <-
用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。
1 | ch <- v // 把 v 发送到通道 ch |
对通道进行声明:
1 | ch := make(chan int) |
注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。
通道缓冲区
通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:
1 | ch := make(chan int, 100) |
带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。
注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。