Go基础语法 | 青训营笔记

135 阅读13分钟

Go基础语法 | 青训营笔记

Go语言特点

  1. 语法简单,易上手,语言新内容少,学习周期短
  2. 丰富标准库,完善的工具链,非常适合网络服务开发
  3. 静态编译,编译速度快
  4. 云原生+跨平台
  5. 异步线程
  6. 垃圾回收机制

基本类型

Golang 更明确的数字类型命名,支持 Unicode,支持常用数据结构,支持八进制、 六进制,以及科学记数法。标准库 math 定义了各数字类型取值范围。

类型长度(字节)默认值说明
bool1false
byte10uint8
rune40Unicode Code Point, int32(相当于char[])
int, uint4或8032 或 64 位
int8, uint810-128 ~ 127, 0 ~ 255,byte是uint8 的别名
int16, uint1620-32768 ~ 32767, 0 ~ 65535
int32, uint3240-21亿~ 21亿, 0 ~ 42亿,rune是int32 的别名
int64, uint6480
float3240.0
float6480.0
complex648复数
complex12816
uintptr4或8以存储指针的 uint32 或 uint64 整数
array值类型
struct值类型
string""UTF-8 字符串
slice(切片)nil引用类型(相当于一个动态窗口,优点是可以直接改变数组中的值)
mapnil引用类型
channel(通道)nil引用类型
interface(接口)nil接口
function(函数)nil函数

(在golang中nil有几种不同的含义,它代表了pointer, channel, func, interface, map 或者 slice 的zero value。nil值是有类型的,指针的nil值指向0x0)

使用Go切片

Go语言中,数组和切片有以下区别:

  1. 切片是数组的抽象,功能更加强大;
  2. 切片是指针类型,数组是值类型;
  3. 数组的长度是固定的,而切片不是(切片可以看成动态的数组);
  4. 切片比数组多一个容量(cap)属性;
  5. 切片的底层是数组切片是引用类型,因此在当传递切片时将引用同一指针,修改值将会影响其他的对象当切片和数组作为参数在函数中传递时。数组传递的是值,而切片传递的是指针切片基于数组,但提供了更高的灵活性,可以使用范围来截取切片

切片有着比数组更加广泛的用途,优点:

  1. 可以用来实现动态数组
  2. 引用传递,可以直接改变原值,在容量过大时避免了值传递的开销

Go整体结构

  1. main方法的包名必须是main
  2. 单行单句无需;
 package main
 import ("fmt")
 ​
 func main() {
   fmt.Println("Hello World!")
 }

Go变量的声明

基本语法:go是一个强类型的语言,声明变量必需可以让编译器推断出值的类型

  1. 直接声明
 var a int
 var a,b,c,d bool
 a = 1
 var s string//(默认为"")
 var a *int
 var a []int
 var a map[string] int
 var a chan int
 var a func(string) int
 var a error // error 是接口
  1. 类型推断(i

    赋值后自动判断类型

 var d = true
 var a,b,c = "a",false,322
  1. 类型推断(ii

    进一步简化,改用:=只需要写变量名,直接赋值后自动推断类型

 d := true
 s := "hello world"
 a,b,c := "a",false,322

(Tip: 如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明)

Go常量的声明

常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。

const

常量的声明只需要把var换为const

Go语言中的map基础类型

 const d = 2525
 const d int = 1324
 const a,b,c = "a",false,322

常量还可以用作枚举

 const (
     Unknown = 0
     Female = 1
     Male = 2
     b = "abc"
     a = len(b)
 )

其中只可以使用内置函数

iota

特殊的常量,可以认为是一个可以被编译器修改的常量。

iota在 const 关键字出现时将被重置为 0(const 内部的第一行之前),const 中每新增一行常量声明将使 iota 计数一次(iota 可理解为 const 语句块中的行索引)。

iota 可以被用作枚举值:

 const (
     a = iota //0
     b        //1
     c        //2
 )

实例:

 package main
 ​
 import "fmt"
 ​
 func main() {
     const (
             a = iota   //0
             b          //1
             c          //2
             d = "ha"   //独立值,iota += 1
             e          //"ha"   iota += 1
             f = 100    //iota +=1
             g          //100  iota +=1
             h = iota   //7,恢复计数
             i          //8
     )
     fmt.Println(a,b,c,d,e,f,g,h,i)
 }
 //结果:0 1 2 ha ha 100 100 7 8
 const (
     i=1<<iota
     j=3<<iota
     k
     l
 )
 //i= 1
 //j= 6
 //k= 12
 //l= 24

运算符除指针->和三目运算符部分外其他与c完全一致,**为乘方

Go条件语句

if语句

  1. 无需添加()
  2. 可以在if开始前初始化语句中的局部变量
 if a>12 {
     fmt.Println("a>12")
 }
 ​
 if a := 3; a>12 {
     fmt.Println("a>12")
 }else if a<=2 {
     fmt.Println("a<=2")
 }else{
     fmt.Println("2<a<=12")
 }

switch

  1. 无需添加 break,go语言默认没有switch穿透,
  2. 变量 var1 可以是任何类型,而 val1 和 val2 则可以是同类型的任意值。类型不被局限于常量或整数,但必须是相同的类型;或者最终结果为相同类型的表达式。
 switch var1 {
     case val1:
         ...
     case val2:
         ...
     default:
         ...
 }
  1. switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。
 switch x.(type){
     case type:
        statement(s);      
     case type:
        statement(s); 
     default: 
        statement(s);
 }
  1. 使用 fallthrough 会强制执行后面的 case 语句,fallthrough 不会判断下一条 case 的表达式结果是否为 true。
 package main
 ​
 import "fmt"
 ​
 func main() {
 ​
     switch {
     case false:
             fmt.Println("case1 条件语句为 false")
             fallthrough
     case true:
             fmt.Println("case2 条件语句为 true")
             fallthrough
     case false:
             fmt.Println("case3 条件语句为 false")
             fallthrough
     case true:
             fmt.Println("case4 条件语句为 true")
     case false:
             fmt.Println("case5 条件语句为 false")
             fallthrough
     default:
             fmt.Println("默认")
     }
 }

select

  1. select 是 Go 中的一个控制结构,类似于 switch 语句。
  2. select 语句只能用于通道操作,每个 case 必须是一个通道操作,要么是发送要么是接收。
  3. select 语句会监听所有指定的通道上的操作,一旦其中一个通道准备好就会执行相应的代码块。
  4. 如果多个通道都准备好,那么 select 语句会随机选择一个通道执行。如果所有通道都没有准备好,那么执行 default 块中的代码。
  • 每个 case 都必须是一个通道

  • 所有 channel 表达式都会被求值

  • 所有被发送的表达式都会被求值

  • 如果任意某个通道可以进行,它就执行,其他被忽略。

  • 如果有多个 case 都可以运行,select 会随机公平地选出一个执行,其他不会执行。

    否则:

    1. 如果有 default 子句,则执行该语句。
    2. 如果没有 default 子句,select 将阻塞,直到某个通道可以运行(select 会循环检测条件,如果有满足则执行并退出,否则一直循环检测。)Go 不会重新对 channel 或值进行求值。

Go循环语句

go没有while语句和do-while语句,只有for语句

 for a:=1;a<10 {
     a++
 }
 for {
     //无限循环
     if a<10{
         break
     }
 }

break continue goto(无条件跳转至某一行)

可以使用循环后选择跳出继续或者跳到哪一个具体循环

Go range遍历

for 循环的 range 格式可以对 slice、map、数组、字符串等进行迭代循环。格式如下:

 for key, value := range oldMap {
     newMap[key] = value
 }

以上代码中的 key 和 value 是可以省略。

如果只想读取 key,格式如下:

for key := range oldMap
或
for key, _ := range oldMap

如果只想读取 value,格式如下:

 for _, value := range oldMap

Go函数

入口main()

函数定义如下:

 func function_name( [parameter list] ) [return_types] {
    函数体
 }

调用与c类似,多了值传递或者引用传递,或者传递指针变量

可以方便的返回多个值,和Python类似

函数闭包

每调用一个函数,调用出的函数内的变量初始化一次,相当于每个函数分别独立封闭;

 package main
 ​
 import "fmt"
 ​
 func getSequence() func() int {
     i := 0
     return func() int {
         i += 1
         return i
     }
 }
 func main() {
     // nextNumber 为一个函数,函数 i 为 0 
     nextNumber := getSequence()
     // 调用 nextNumber 函数,i 变量自增 1 并返回 
     fmt.Print(nextNumber())
     fmt.Print(nextNumber())
     // 创建新的函数 nextNumber1,并查看结果 
     nextNumber1 := getSequence()
     fmt.Print(nextNumber1())
     fmt.Print(nextNumber())
 }
 ​
 //运行结果:1213

函数变量

Go中函数可以直接作为变量使用

方法函数

 func (variable_name variable_data_type) function_name() [return_type]{
     //函数体
  }
 package main
 ​
 import (
     "fmt"
 )
 ​
 type Circle struct {
     radius float64
 }
 func main() {
     var c1 Circle
     c1.radius = 10.00
     fmt.Println("圆的面积 = ", c1.getArea())
 }
 ​
 func (c Circle) getArea() float64 {
     return 3.14 * c.radius * c.radius
 }

结构体

声明结构体

结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:

 type struct_variable_type struct {
    member definition
    member definition
    ...
    member definition
 }
 variable_name := structure_variable_type {value1, value2...valuen}//必须按定义顺序
 variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}

访问结构体成员

如果要访问结构体成员,需要使用点号 . 操作符,格式为:

 结构体.成员名

注意没有->操作符,使用结构体指针访问结构体成员,同样使用 . 操作符

结构体更加类似于java中的类型,而非结构体。

 package main
 ​
 import "fmt"
 ​
 type Books struct {
     title   string
     author  string
     book_id int
 }
 ​
 func main() {
     var Book1 Books
     var Book2 Books
     Book1.title = "homo青春修炼手册"
     Book1.author = "哼哼哼啊啊啊啊啊啊"
     Book1.book_id = 114514
     Book2.title = "6"
     Book2.author = "feellmoose"
     Book2.book_id = 666666
     printBook(&Book1)
     printBook(&Book2)
 }
 ​
 func printBook(book *Books) {
     fmt.Printf("Book title : %s\n", book.title)
     fmt.Printf("Book author : %s\n", book.author)
     fmt.Printf("Book book_id : %d\n", book.book_id)
 }

Slices(切片)

Go 语言切片是对数组的抽象。

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。

定义切片

  1. 声明一个未指定大小的数组来定义切片
 var identifier []type
  1. 使用 make() 函数来创建切片( make()函数是go语言的一个内置函数。只用于channel,map和slice的创建 )
 var slice1 []type = make([]type, len)
 slice1 := make([]type, len)//不需要添加len

具体方法如下:

 make([]T, length, capacity)

空(nil)切片

一个切片在未初始化之前默认为 nil,长度为 0

初始化切片

  1. 直接初始化切片, [] 表示是切片类型,其 cap=len
 s :=[] int {1,2,3} 
  1. 通过数组初始化,将数组中从下标 第一个下标 到 第二个小标的前一个 元素创建为一个新的切片,可以省略前后任意一个或者两个参数
 s := arr[startIndex:endIndex] 
  1. 通过切片初始化,将切片中从下标 第一个下标 到 第二个小标的前一个 元素创建为一个新的切片
 s1 := s[startIndex:endIndex] 
  1. 通过内置函数 make() 初始化切片s[]int 标识为其元素类型为 int 的切片。
 s :=make([]int,len,cap) 

append() 和 copy() 函数

增加切片容量:如果想增加切片的容量,我们必须创建一个新的更大的切片并把原分片的内容都拷贝过来。

Map(集合)

Map 是一种无序的键值对的集合,可以通过 key 来快速检索数据,key 类似于索引,指向数据的值

在Go中我们可以像迭代数组和切片那样迭代它。遍历 Map 时返回的键值对的顺序是不确定的

在获取 Map 的值时,如果键不存在,返回该类型的零值,例如 int 类型的零值是 0,string 类型的零值是 ""。

Map 是引用类型,如果将一个 Map 传递给一个函数或赋值给另一个变量,它们都指向同一个底层数据结构,因此对 Map 的修改会影响到所有引用它的变量。

定义集合

  1. 使用内建函数 make 或使用 map 关键字来定义 Map
 // 使用 make 函数
 map_variable := make(map[KeyType]ValueType, initialCapacity)

其中 KeyType 是键的类型,ValueType 是值的类型,initialCapacity 是可选的参数,用于指定 Map 的初始容量。Map 的容量是指 Map 中可以保存的键值对的数量,当 Map 中的键值对数量达到容量时,Map 会自动扩容。如果不指定 initialCapacity,Go 语言会根据实际情况选择一个合适的值。

  1. 使用字面量直接定义并初始化 Map:
 // 使用字面量创建 Map
 m := map[string]int{
     "apple": 1,
     "banana": 2,
     "orange": 3,
 }

使用集合

  1. 获取元素:
 // 获取键值对
 v1 := m["apple"]
 v2, ok := m["pear"]  // 如果键不存在,ok 的值为 false,v2 的值为该类型的零值
  1. 修改元素:
 // 修改键值对
 m["apple"] = 5
  1. 获取 Map 的长度:
 // 获取 Map 的长度
 len := len(m)
  1. 遍历 Map:
 // 遍历 Map
 for k, v := range m {
     fmt.Printf("key=%s, value=%d\n", k, v)
 }
  1. 删除元素:
 // 删除键值对
 delete(m, "banana")

Go类型转换

类型转换用于将一种数据类型的变量转换为另外一种类型的变量。

Go 语言类型转换基本格式如下:

 type_name(expression)

type_name 为类型,expression 为表达式。

字符串类型转换

将一个字符串转换成另一个类型,可以使用以下语法:

 var str string = "10"
 var num int
 num, _ = strconv.Atoi(str)

以上代码将字符串变量 str 转换为整型变量 num。

注意,strconv.Atoi 函数返回两个值,第一个是转换后的整型值,第二个是可能发生的错误,我们可以使用空白标识符 _ 来忽略这个错误

接口类型转换

接口类型转换有两种情况 :类型断言类型转换

类型断言用于将接口类型转换为指定类型,其语法为:

 value.(type) 
 或 
 value.(T)

其中 value 是接口类型的变量,type 或 T 是要转换成的类型。

如果类型断言成功,它将返回转换后的值和一个布尔值,表示转换是否成功。

类型转换用于将一个接口类型的值转换为另一个接口类型,其语法为:

 T(value)

T 是目标接口类型,value 是要转换的值。

在类型转换中,我们必须保证要转换的值和目标接口类型之间是兼容的,否则编译器会报错。

Go接口

Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

接口可以让我们将不同的类型绑定到一组公共的方法(接口)上,从而实现多态和灵活的设计。

Go 语言中的接口是隐式实现的,也就是说,如果一个类型实现了一个接口定义的所有方法,那么它就自动地实现了该接口。因此,我们可以通过将接口作为参数来实现对不同类型的调用,从而实现多态(所以我们还是得把一堆代码全都敲上去)。

定义接口

 type interface_name interface {
     method_name1 [return_type]
     method_name2 [return_type]
     method_name3 [return_type]
     ...
     method_namen [return_type]
  }
  
  //定义结构体
  type struct_name struct {
     //variables
  }
  
  //实现接口方法
  func (struct_name_variable struct_name) method_name1() [return_type] {
     //方法实现
  }
  ...
  func (struct_name_variable struct_name) method_namen() [return_type] {
     //方法实现
  }

实例(来自菜鸟教程):

 package main
 ​
 import "fmt"
 ​
 type Shape interface {
     area() float64
 }
 ​
 type Rectangle struct {
     width  float64
     height float64
 }
 ​
 func (r Rectangle) area() float64 {
     return r.width * r.height
 }
 ​
 type Circle struct {
     radius float64
 }
 ​
 func (c Circle) area() float64 {
     return 3.14 * c.radius * c.radius
 }
 ​
 func main() {
     var s Shape
 ​
     s = Rectangle{width: 10, height: 5}
     fmt.Printf("矩形面积: %f\n", s.area())
 ​
     s = Circle{radius: 3}
     fmt.Printf("圆形面积: %f\n", s.area())
 }

Go 错误处理

Go 语言通过内置的错误接口提供了非常简单的错误处理机制

error 类型是一个接口类型,这是它的定义:

 type error interface {
     Error() string
 }

我们可以在编码中通过实现 error 接口类型来生成错误信息

函数通常在最后的返回值中返回错误信息。使用 errors.New 可返回一个错误信息:

 func Sqrt(f float64) (float64, error) {
     if f < 0 {
         return 0, errors.New("math: square root of negative number")
     }
     //实现
 }

如果在调用上述 Sqrt 的时候传递的一个负数,然后可以得到 non-nil 的 error 对象,将此对象与默认的 nil 比较,结果为 true,则处理异常,fmt 包在处理 error 时会调用 Error 方法输出错误:

 result, err:= Sqrt(-1)
 ​
 if err != nil {
    fmt.Println(err)
 }

Go 并发

Go 语言支持并发,我们只需要通过 go 关键字来开启协程即可,协程是轻量级线程,协程的调度是由 Golang 运行时进行管理的。

 go f(args)

Go 允许使用 go 语句开启一个协程, 即协程,以一个不同的、新创建的协程来执行一个函数。

 package main
 ​
 import (
     "fmt"
     "time"
 )
 ​
 func f(i int) {
     fmt.Print(i)
 }
 ​
 func main() {
     for i := 0; i < 12; i++ {
         fmt.Print("start ")
         go f(i)
     }
     time.Sleep(time.Duration(2) * time.Second)
 }
 //执行结果:start start start 01start start start start start start 352647start start start 981011

Go通道(channel)

通道(channel)是用来传递数据的一个数据结构,主要的作用是实现并发同步。

通道可用于两个协程之间通过传递一个指定类型的值来同步运行和通讯。操作符 <- 用于指定通道的方向,发送或接收。如果未指定方向,则为双向通道。

 ch <- v    // 把 v 发送到通道 ch
 v := <-ch  // 从 ch 接收数据
            // 并把值赋给 v

声明一个通道很简单,我们使用chan关键字即可,通道在使用前必须先创建:

 ch := make(chan int)

注意:默认情况下,通道是不带缓冲区的。发送端发送数据,同时必须有接收端相应的接收数据。

通道缓冲区

通道可以设置缓冲区,通过 make 的第二个参数指定缓冲区大小:

 ch := make(chan int, 100)

带缓冲区的通道允许发送端的数据发送和接收端的数据获取处于异步状态,就是说发送端发送的数据可以放在缓冲区里面,可以等待接收端去获取数据,而不是立刻需要接收端去获取数据。

不过由于缓冲区的大小是有限的,所以还是必须有接收端来接收数据的,否则缓冲区一满,数据发送端就无法再发送数据了(我们可以把缓冲区定义的大一些)

注意:如果通道不带缓冲,发送方会阻塞直到接收方从通道中接收了值。如果通道带缓冲,发送方则会阻塞直到发送的值被拷贝到缓冲区内;如果缓冲区已满,则意味着需要等待直到某个接收方获取到一个值。接收方在有值可以接收之前会一直阻塞。

Go 遍历通道与关闭通道

Go 通过 range 关键字来实现遍历读取到的数据,类似于与数组或切片。格式如下

 v, ok := <-ch

如果通道接收不到数据后 ok 就为 false,这时通道就可以使用 close() 函数来关闭。

defer

延迟执行,也就是在函数结束前执行,用于执行程序出现运行时错误时的兜底处理,可以用来关闭指针或者通道。

panic()recover()都是go语言的内建函数。panic()用于手动创建一个运行时错误。recover()专门用于恢复,recover()必须用在defer延迟里。