变量

变量声明

四种声明语句

  • var
  • const
  • type
  • func

var 变量名字 类型 = 表达式

批量格式

  1. var (
  2. a int
  3. b string
  4. c []float32
  5. d func() bool
  6. e struct {
  7. x int
  8. }
  9. )

简短变量声明
名字 := 表达式
这是Go语言的推导声明写法,编译器会自动根据右值类型推断出左值的对应类型

  1. anim := gif.GIF{LoopCount: nframes}
  2. freq := rand.Float64() * 3.0
  3. t := 0.0
  1. i := 100 // an int
  2. var boiling float64 = 100 // a float64
  3. var names []string
  4. var err error
  5. var p Point

匿名变量

可用于占位

  1. package main
  2. import "fmt"
  3. func GetData() (int, int) {
  4. return 100, 200
  5. }
  6. func main() {
  7. a, _ := GetData() // 只需要第一个返回值
  8. _, b := GetData() // 只需要第二个返回值
  9. // b := GetData() // 只用一个变量无法达到想要的效果
  10. fmt.Println(a, b)
  11. }

匿名变量不占用内存空间,不会分配内存。匿名变量与匿名变量之间也不会因为多次声明而无法使用。

变量作用域

局部变量
在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,函数的参数和返回值变量都属于局部变量。
局部变量不是一直存在的,它只在定义它的函数被调用后存在,函数调用结束后这个局部变量就会被销毁。

形式参数
在定义函数时函数名后面括号中的变量叫做形式参数

常量与枚举

常量声明

  1. const identifier [type] = value
  1. package main
  2. import "fmt"
  3. func main() {
  4. const LENGTH int = 10
  5. const WIDTH int = 5
  6. var area int
  7. const a, b, c = 1, false, "str" //多重赋值
  8. area = LENGTH * WIDTH
  9. fmt.Printf("面积为 : %d", area)
  10. println()
  11. println(a, b, c)
  12. }

特殊常量

  1. const a int = iota // the value of a is 0
  2. const b = iota // the value of b is still 0

定义枚举

  1. const (
  2. unknown = 0
  3. male = 1
  4. female = 2
  5. )
  1. const (
  2. unknown = iota // the value of unknown is 0
  3. male // the value of male is 1
  4. female // the value of female is 2
  5. )
  6. const (
  7. c1 = iota // the value of c1 is 0
  8. c2 = iota // the value of c2 is 1
  9. c3 = iota // the value of c3 is 2
  10. )

数据类型

浮点型

  1. package main
  2. import (
  3. "fmt"
  4. "math"
  5. )
  6. //全局变量 a
  7. var a int = 13
  8. func main() {
  9. var f float32 = 16777216 // 1 << 24
  10. const e = .71828 // 0.71828
  11. const g = 1. // 1
  12. fmt.Println(f == f+1) // "true"!
  13. fmt.Printf("%f\n", math.Pi) // 3.141593
  14. fmt.Printf("%.2f\n", math.Pi) // 3.14
  15. }

字符串

换行字符串

  1. const str = `
  2. 第一行
  3. 第二行
  4. 第三行
  5. \r\n
  6. `
  7. fmt.Println(str)

字符类型

Go语言的字符有以下两种:

  • 一种是 uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符。
  • 另一种是 rune 类型,代表一个 UTF-8 字符,当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型等价于 int32 类型。
  1. fmt.Println(str)
  2. var ch int = '\u0041'
  3. var ch2 int = '\u03B2'
  4. var ch3 int = '\U00101234'
  5. fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer
  6. fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character
  7. fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes
  8. fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point

派生类型


包括:

  • 指针类型(Pointer)
  • 数组类型
  • 结构化类型(struct)
  • Channel 类型
  • 函数类型
  • 切片类型
  • 接口类型(interface)
  • Map 类型

    数据类型转换

    在必要以及可行的情况下,一个类型的值可以被转换成另一种类型的值。由于Go语言不存在隐式类型转换,因此所有的类型转换都必须显式的声明:
    valueOfTypeB = typeB(valueOfTypeA)
  1. func main() {
  2. // 将常量保存为float32类型
  3. var c float32 = math.Pi
  4. // 转换为int类型, 浮点发生精度丢失
  5. fmt.Println(int(c), c)
  6. }

条件语句

Go语言的 if / if else语句 省去了条件的括号

  1. if a < 20 {
  2. fmt.Printf("a 小于 20\n" )
  3. }

switch 语句

  1. switch var1 {
  2. case val1:
  3. ...
  4. case val2:
  5. ...
  6. default:
  7. ...
  8. }

select 语句,与switch类似,但是select会随机执行一个可运行的case。如果没有case可运行,它将阻塞,直到有case可运行

  1. func demo() {
  2. var c1, c2, c3 chan int
  3. var i1, i2 int
  4. select {
  5. case i1 = <-c1:
  6. fmt.Printf("received ", i1, " from c1\n")
  7. case c2 <- i2:
  8. fmt.Printf("sent ", i2, " to c2\n")
  9. case i3, ok := (<-c3): // same as: i3, ok := <-c3
  10. if ok {
  11. fmt.Printf("received ", i3, " from c3\n")
  12. } else {
  13. fmt.Printf("c3 is closed\n")
  14. }
  15. default:
  16. fmt.Printf("no communication\n")
  17. }
  18. }

循环语句

go 循环语句同样省略了条件括号

函数

多个返回值

  1. func swap(x, y string) (string, string) {
  2. return y, x
  3. }

闭包

  1. func getSequence() func() int {
  2. i := 0
  3. return func() int {
  4. i += 1
  5. return i
  6. }
  7. }
  8. func demo() {
  9. /* nextNumber 为一个函数,函数 i 为 0 */
  10. nextNumber := getSequence()
  11. /* 调用 nextNumber 函数,i 变量自增 1 并返回 */
  12. fmt.Println(nextNumber())
  13. fmt.Println(nextNumber())
  14. fmt.Println(nextNumber())
  15. /* 创建新的函数 nextNumber1,并查看结果 */
  16. nextNumber1 := getSequence()
  17. fmt.Println(nextNumber1())
  18. fmt.Println(nextNumber1())
  19. }

方法

一个方法就是一个包含了接受者的函数,接受者可以是命名类型或者结构体类型的一个值或者是一个指针

  1. func (variable_name variable_data_type) function_name() [return_type]{
  2. /* 函数体*/
  3. }
  1. /* 定义结构体 */
  2. type Circle struct {
  3. radius float64
  4. }
  5. // 属于 Circle 类型对象中的方法
  6. func (c Circle) getArea() float64 {
  7. //c.radius 即为 Circle 类型对象中的属性
  8. return 3.14 * c.radius * c.radius
  9. }
  10. func demo() {
  11. var c1 Circle
  12. c1.radius = 10.00
  13. fmt.Println("圆的面积 = ", c1.getArea())
  14. }

结构体

Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型
结构体是由一系列具有相同类型或不同类型的数据构成的数据集合
定义

  1. type struct_variable_type struct {
  2. member definition
  3. member definition
  4. ...
  5. member definition
  6. }

用于变量的声明

  1. variable_name := structure_variable_type {value1, value2...valuen}
  2. variable_name := structure_variable_type { key1: value1, key2: value2..., keyn: valuen}

访问结构体

  1. 结构体.成员名
  1. package main
  2. import "fmt"
  3. type Anime struct {
  4. title string
  5. producer string
  6. anime_id int
  7. }
  8. func main() {
  9. var anime1 Anime /* 声明 anime1 为 Anime 类型 */
  10. var anime2 Anime /* 声明 anime2 为 Anime 类型 */
  11. /* anime 1 描述 */
  12. anime1.title = "咒术回战"
  13. anime1.producer = "MAPPA"
  14. anime1.anime_id = 6495407
  15. /* anime 2 描述 */
  16. anime2.title = "来自深渊"
  17. anime2.producer = "KINEMA CITRUS"
  18. anime2.anime_id = 6495700
  19. /* 打印 anime1 信息 */
  20. fmt.Printf("anime 1 title : %s\n", anime1.title)
  21. fmt.Printf("anime 1 producer : %s\n", anime1.producer)
  22. fmt.Printf("anime 1 anime_id : %d\n", anime1.anime_id)
  23. /* 打印 anime2 信息 */
  24. fmt.Printf("anime 2 title : %s\n", anime2.title)
  25. fmt.Printf("anime 2 producer : %s\n", anime2.producer)
  26. fmt.Printf("anime 2 anime_id : %d\n", anime2.anime_id)
  27. }

结构体指针

如果想在函数里面改变结构体实例,需要给函数传入结构体指针

  1. var struct_pointer *Anime
  1. package main
  2. import "fmt"
  3. type Anime struct {
  4. title string
  5. producer string
  6. anime_id int
  7. }
  8. func main() {
  9. var anime1 Anime /* 声明 anime1 为 Anime 类型 */
  10. var anime2 Anime /* 声明 anime2 为 Anime 类型 */
  11. /* anime 1 描述 */
  12. anime1.title = "咒术回战"
  13. anime1.producer = "MAPPA"
  14. anime1.anime_id = 6495407
  15. /* anime 2 描述 */
  16. anime2.title = "来自深渊"
  17. anime2.producer = "KINEMA CITRUS"
  18. anime2.anime_id = 6495700
  19. changeAnime(anime2)
  20. changeAnimeByPointer(&anime1)
  21. fmt.Println(anime1)
  22. fmt.Println(anime2)
  23. }
  24. func changeAnime(anime Anime) {
  25. anime.title = "黑之契约者"
  26. }
  27. func changeAnimeByPointer(anime *Anime) {
  28. anime.title = "进击的巨人"
  29. }

这样就能改变结构体实例了

{进击的巨人 MAPPA 6495407} {来自深渊 KINEMA CITRUS 6495700}

方法

见函数中的方法
Go没有class概念,但是可以在结构体外部定义方法

数组和切片

数组

  1. // 定义方式
  2. var a [3]int // 定义长度为3的int型数组, 元素全部为0
  3. var b = [...]int{1, 2, 3} // 定义长度为3的int型数组, 元素为 1, 2, 3
  4. var c = [...]int{2: 3, 1: 2} // 定义长度为3的int型数组, 元素为 0, 2, 3
  5. var d = [...]int{1, 2, 4: 5, 6} // 定义长度为6的int型数组, 元素为 1, 2, 0, 0, 5, 6
  6. var d [0]int // 定义一个长度为0的数组
  7. var e = [0]int{} // 定义一个长度为0的数组
  8. var f = [...]int{} // 定义一个长度为0的数组
  9. // 遍历数组
  10. for i := range a {
  11. fmt.Printf("a[%d]: %d\n", i, a[i])
  12. }
  13. for i, v := range b {
  14. fmt.Printf("b[%d]: %d\n", i, v)
  15. }
  16. for i := 0; i < len(c); i++ {
  17. fmt.Printf("c[%d]: %d\n", i, c[i])
  18. }

切片

  • Go数组的长度在定义后是固定的,不可改变的,而切片是对数组的抽象。
  • 切片的长度和容量是不固定的,可以动态增加元素,切片的容量也会根据情况自动扩容
  • 切片实际是个struct结构体,有个指针array,指向存放数据的数组
    1. type slice struct {
    2. array unsafe.Pointer
    3. len int
    4. cap int
    5. }
    len是切片的长度,cap是切片的容量

切片语法

var slice_var []data_type = make([]data_type, len, cap)

  1. func printSlice(param []int) {
  2. fmt.Printf("slice len:%d, cap:%d, value:%v\n", len(param), cap(param), param)
  3. }
  4. func main() {
  5. slice1 := []int{1} // 数组
  6. slice2 := make([]int, 3, 100) // 切片
  7. printSlice(slice1)
  8. printSlice(slice2)
  9. }
  10. slice len:1, cap:1, value:[1]
  11. slice len:3, cap:100, value:[0 0 0]

如果slice类型的变量定义后没有初始化赋值,那值就是默认值nil。对于nil切片,len和cap函数执行结果都是0

  1. func demo() {
  2. slice3 := []int{} // 不是0值
  3. var slice4 []int // 0值
  4. fmt.Println("is nil: ", slice3 == nil, slice4 == nil)
  5. }

切片的使用

访问

  1. func demo02() {
  2. slice := make([]int, 3, 10)
  3. /*下标访问切片*/
  4. slice[0] = 1
  5. slice[1] = 2
  6. slice[2] = 3
  7. for i := 0; i < len(slice); i++ {
  8. fmt.Printf("slice[%d]=%d\n", i, slice[i])
  9. }
  10. /*range迭代访问切片*/
  11. for index, value := range slice {
  12. fmt.Printf("slice[%d]=%d\n", index, value)
  13. }
  14. }
  15. slice[0]=1
  16. slice[1]=2
  17. slice[2]=3
  18. slice[0]=1
  19. slice[1]=2
  20. slice[2]=3

截取array[left_index:right_index]
类似python的截取,左开右闭区间

  1. func demo03() {
  2. // 对数组做切片
  3. array := [3]int{1, 2, 3} // array是数组
  4. slice3 := array[1:3] // slice3是切片
  5. fmt.Println("slice3 type:", reflect.TypeOf(slice3))
  6. fmt.Println("slice3=", slice3) // slice3= [2 3]
  7. slice4 := slice3[1:2]
  8. fmt.Println("slice4=", slice4) // slice4= [3] 左开右闭区间
  9. /* slice5->slice4->slice3->array
  10. 切片后的指针会指向原切片的数组空间,会影响原数据
  11. 对slice5的修改,会影响到slice4, slice3和array
  12. */
  13. slice5 := slice4[:]
  14. fmt.Println("slice5=", slice5) // slice5= [3]
  15. slice5[0] = 10
  16. fmt.Println("array=", array) // array= [1 2 10]
  17. fmt.Println("slice3=", slice3) // slice3= [2 10]
  18. fmt.Println("slice4=", slice4) // slice4= [10]
  19. fmt.Println("slice5=", slice5) // slice5= [10]
  20. }

常用函数

  • len():获取切片的长度,也就是实际存储了多少个元素
  • cap(): 获取切片的容量。如果切片的元素个数要超过当前容量,会自动扩容
  • append(): 不改变原切片的值
    • 只能对切片使用append()函数,不能对数组使用append()
  • copy():拷贝一个切片里的数据到另一个切片
    • 只从源切片srcSlice拷贝min(len(srcSlice), len(dstSlice))个元素到目标切片dstSlice里。
    • 如果dstSlice的长度是0,那一个都不会从srcSlice拷贝到dstSlice里。
    • 如果dstSlice的长度M小于srcSlice的长度N,则只会拷贝srcSlice里的前M个元素到目标切片dstSlice里

      切片在函数内的使用

      ```go func change1(param []int) { param[0] = 100 // 这个会改变外部切片的值 param = append(param, 200) // append不会改变外部切片的值 }

func change2(param []int) { param = append(*param, 300) // 传切片指针,通过这种方式append可以改变外部切片的值 }

func demo04() { slice := make([]int, 2, 100) fmt.Println(slice) // [0, 0]

  1. change1(slice)
  2. fmt.Println(slice) // [100, 0]
  3. change2(&slice)
  4. fmt.Println(slice) // [100, 0, 300]

}

  1. - slice切片如果是函数参数,函数体内对切片底层数组以访问方式的修改会影响到实参
  2. - 如果在函数体内通过append直接对切片添加新元素,不会改变外部切片的值
  3. - 如果函数使用切片指针作为参数,在函数体内可以通过append修改外部切片的值
  4. <a name="eIvSq"></a>
  5. ## map集合
  6. Go语言里的map底层是通过**hash**实现的,是一种无序的基于`<key, value>`对组成的数据结构,key是唯一的<br />**语法**
  7. ```go
  8. func main() {
  9. var dict map[string]int = map[string]int{}
  10. dict["a"] = 1
  11. fmt.Println(dict)
  12. var dict2 = map[string]int{}
  13. dict2["b"] = 2
  14. fmt.Println(dict2)
  15. dict3 := map[string]int{"test": 0}
  16. dict3["c"] = 3
  17. fmt.Println(dict2)
  18. dict4 := make(map[string]int)
  19. dict4["d"] = 4
  20. fmt.Println(dict4)
  21. }

常用api
判断key在map里是否存在,语法value, is_exist := map[key]

  1. func demo01() {
  2. // 构造一个map
  3. str := "aba"
  4. dict := map[rune]int{}
  5. for _, value := range str {
  6. dict[value]++
  7. }
  8. fmt.Println(dict) // map[97:2 98:1]
  9. // 访问map里不存在的key,并不会像C++一样自动往map里插入这个新key
  10. value, ok := dict['z']
  11. fmt.Println(value, ok) // 0 false
  12. fmt.Println(dict) // map[97:2 98:1]
  13. // 访问map里已有的key
  14. value2 := dict['a']
  15. fmt.Println(value2) // 2
  16. }

获取键值对数量

  1. counter := make(map[string]int)
  2. fmt.Println(len(counter))
  3. counter["a"] = 1
  4. fmt.Println(len(counter))

删除键值对

  1. func demo02() {
  2. dict := map[string]int{"a": 1, "b": 2}
  3. fmt.Println(dict) // map[a:1 b:2]
  4. // 删除"a"这个key
  5. delete(dict, "a")
  6. fmt.Println(dict) // map[b:2]
  7. // 删除"c"这个不在的key,对map结果无影响
  8. delete(dict, "c")
  9. fmt.Println(dict) // map[b:2]
  10. }

注意:

  • map不是并发安全的,并发读写要加锁
  • 切片slice,函数类型function,集合map,不能用作map的key

    接口interface

  • 定义:接口是一种抽象的类型,是一组method的集合,里面只有method方法,没有数据成员。当两个或两个以上的类型都有相同的处理方法时才需要用到接口。

  • 多个struct类型可以实现同一个interface
  • 一个struct类型可以实现多个interface ```go / 定义接口 / type interface_name01 interface { method_name1() [return_type] method_name2() [return_type] method_name3() [return_type]
    1. ...
    method_namen() [return_type] } / 定义接口 / type interface_name02 interface { interface_name01 // 可以继承自其它接口,得到这个接口的method集合 method_name1() [return_type] method_name2() [return_type] method_name3() [return_type]
    1. ...
    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] { / 方法实现/ }

  1. ```go
  2. package main
  3. import "fmt"
  4. // 定义动物类接口
  5. type Animal interface {
  6. speak()
  7. }
  8. // cat结构体
  9. type Cat struct {
  10. name string
  11. age int
  12. }
  13. // cat方法
  14. func (cat Cat) speak() {
  15. fmt.Println("cat miaomiaomiao")
  16. }
  17. // dog结构体
  18. type Dog struct {
  19. name string
  20. age int
  21. }
  22. // Dog方法
  23. func (dog *Dog) speak() {
  24. fmt.Println("dog wangwangwang")
  25. }
  26. func main() {
  27. var animal Animal = Cat{"gaffe", 1}
  28. animal.speak() // cat miaomiaomiao
  29. /*
  30. 如果用了指针接受者,那给interface变量赋值的时候要传指针
  31. */
  32. animal = &Dog{"caiquan", 2}
  33. animal.speak() // dog wangwangwang
  34. }
  • interface可以继承:一个interface里包含其它interface ```go // interface1 type Felines interface { feet() }

// interface2, 继承了interface1 type Mammal interface { Felines born() } ```