目录
Go语言学习记录(一)
/  

Go语言学习记录(一)

备份一下之前的学习记录。

变量

var 语句用于声明一个变量列表,和函数的参数意义,类型在后

var name string

var a, b, c bool

var 语句可以出现在包或函数级别

短变量声明

在函数中,简洁赋值语句 := 可在类型明确的地方代替 var 声明

函数外的每个语句都必须以关键字开始(var、func等等),因此 := 结构不能在函数外使用

变量的使用

Golang的变量如果没有赋初值,编译器会使用默认值,比如int默认值是 0,string 默认值为 "" 空串,布尔类型默认是 false

在变量的作用域下,可以改变它的值,但是不能改变数据类型

例如:

var a = 32
a = 20
a = 32.4  // 此时就会报错
  1. Golang变量使用的三种方式

    • 第一种:指定变量类型,声明后若不赋值,使用默认值
    package main
    
    import "fmt"
    
    func main () {
    var i int
    fmt.Println("没有赋值的i的默认值为:", i)  // 0
    }
    
    • 第二种:根据值自行判定变量类型(类型推导)
    var j = 20.12
    fmt.Println("根据值自行判断变量类型(类型推导):", j)
    
    • 第三种:省略var,注意 := 左侧的变量不应该是已声明过的,否则会导致编译错误. 下面这种方式等价于 var name string name = "tom" := 不能省略,否则错误

      k := "tom"
      var l string
      l =  "jack"
      fmt.Println(k, "' :=' => 'var l string '", l)
      
    package main
    
    import "fmt"
    
    func main () {
            // 定义变量 变量类型 如果没有赋值,会有一个默认值 0
            var i int
            // 给i赋值
            i = 10
            fmt.Println("i的值:", i)  // 10
    
    }
    
  2. 多变量声明

    package main
    
    import "fmt"
    
    func main () {
            // 一次性声明多个变量
            // 第一种
            var n1, n2, n3 int
            fmt.Println("n1=", n1, "n2=", n2, "n3=", n3)
    
            // 第二种
            var a1, name, a2 = 100, "tom", 888
            fmt.Println("a1=", a1, "name=", name, "n2=", a2)
    
            // 第三种
            b1, b2, b3 := 666, "jerry", 888
            fmt.Println("b1=", b1, "b2=", b2, "b3=", b3)
    }
    
  3. 全局变量定义

    package main
    
    import "fmt"
    
    var a1, a2, a3 = "tom", 12, 55
    var b1 = "jerry"
    var b2 = 43
    var b3 = 39
    var (
      c1 = "jack"
      c2 = 88
      c3 = 99
    )
    
    func main () {
    
            fmt.Println("a1=", a1, "a2=", a2, "a3=", a3)
            fmt.Println("b1=", b1, "b2=", b2, "b3=", b3)
            fmt.Println("c1=", c1, "c2=", c2, "c3=", c3)
    
    }
    

数据类型

基本数据类型

  • 数值型
    • int 为有符号数,uint为无符号数,无符号表示的范围会更大
      • 整数类型(int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, byte)
      • 浮点类型(float32, float64)
  • 字符型
    • 没有专门的字符型,使用byte来保存单个字母字符
  • 布尔型(bool)
  • 字符串(string) [官方将string归属到基本数据类型]

派生 / 复杂数据类型

  • 指针(Pointer)
  • 数组
  • 结构体(struct)
  • 管道(channel)
  • 函数(也是一种类型)
  • 切片(slice)
  • 接口(interface)
  • map

函数

func 函数名 (参数名 参数类型) 返回值类型

func add (x int, y int) int {
  return x + y
}

多返回值

func add (x int, y int) (int, int) {
  return x + y, x - y
}

匿名函数

和JavaScript中的匿名函数一样。

func (参数列表) (返回值列表) {
  函数体
}

在定义匿名函数时,调用函数

func (data int) {
  fmt.Println("data: ", data)
}(100)
// 注意后面的(100), 表示对匿名函数的调用
// 传递参数为 100

将匿名函数赋值给变量

add := func (x, y int) int {
  return x + y;
}
package main

import "fmt"

func main () {

    add := func (x, y int) int {
        return x + y
    }
    sum := add(3, 5)
    fmt.Println("sum=", sum)
}

匿名函数用作回调函数

package main

import "fmt"

func deal (x int, y int, add func(a, b int) int) int {
    return add(x * 2, y * 2)
}

func main () {

    result := deal(3, 4, func (x, y int) int{
        return x + y
    })
    fmt.Println("result=", result)
}

命名返回值

如下代码所示,函数形参:如果多个参数的类型相同,前面的参数类型可以省略,最后一个参数标明 参数类型

命名返回值:直接定义返回值 变量,没有参数的return语句就会返回 已命名的返回值。

直接返回语句应当在短函数中,在长的函数中 它们会影响代码的可读性

package main

import "fmt"

// 命名返回值
func add (x, y int) (a, b int) {
        a = x + y
        b = x - y
        return
}

func main () {
        fmt.Println(add(9, 8))
        a, b := add(9, 8)
        fmt.Println("a=", a, "b=", b)
}

/*
输出:
	17 1
	a= 17 b= 1
*/

类型转换

表达式 T(v) 将值v转换为类型 T

package main

import "fmt"

func main () {
        var i int = 42
        fmt.Printf("i=%d, type=%T \n", i, i)
        var f float64 = float64(i)
        fmt.Printf("f=%f, type=%T \n", f, f)
        var u uint = uint(f)
        fmt.Printf("u=%d, type=%T \n", u, u)
}
/*
输出:
	i=42, type=int 
  f=42.000000, type=float64 
  u=42, type=uint 
/*

更加简单的方式 写法

package main

import "fmt"

func main () {
        i := 42
        fmt.Printf("i=%d, type=%T \n", i, i)
        f := float64(i)
        fmt.Printf("f=%f, type=%T \n", f, f)
        u := uint(f)
        fmt.Printf("u=%d, type=%T \n", u, u)
}

条件语句

在if的简短语句中声明的变量同样可以在任何对应的else块中使用。

package main

import (
  "fmt"
  "math/rand"
  "time"
)

func main () {
    var a int
    // 设置 种子
    rand.Seed(time.Now().UnixNano())
    a = rand.Intn(10)
    if v := a*2; v > 5 {
        fmt.Println("v的值大于5, a=", a, "v=", v)
    } else {
        fmt.Println("v小于等于5, a=", a, "v=", v)
    }
    // 从这里开始就不能使用v了,只能在定义变量的if以及对应else块中使用
}

Switch case

package main

import (
    "fmt"
    "runtime"
)

func main () {

    fmt.Println("Go runs on")
    switch os := runtime.GOOS; os {
        case "darwin": 
            fmt.Println("OS X .")
        case "linux":
            fmt.Println("Linux .")
        default:
            fmt.Printf("%s . \n", os)
    }

}

在Java语言中switch case 要和break搭配。

Java语言中:如果当前匹配成功的case语句块没有break语句,则从当前case开始

后续的所有case的值都会输出,如果后续的case语句块有break语句则会跳出判断

public class JavaSwitch {


    public static void main (String[] args) {

        int a = 3;
        switch(a) {

            case 2: System.out.println("a=2");
            case 3: System.out.println("a=3");
            case 4: System.out.println("a=4");
            default: System.out.println("default 执行");

        }

    }

}

a=3

a=4

default 执行

在go语言中的switch case中没有用break,当匹配到第一个符合的case,就会跳出判断。

package main

import "fmt"

func main () {

    a := 5
    switch {

        case a > 2:
            fmt.Println("a大于2")
        case a > 4:
            fmt.Println("a大于4")
        default:
            fmt.Println("其他...")

    }

}

a大于2

defer的使用规则

摘自:https://studygolang.com/articles/10167

在golang中,defer代码块会在函数调用链表中增加一个函数调用。这个函数调用不是普通的函数调用

而是会在函数正常返回之后,添加一个函数调用。

因此 defer通常用来释放函数内部变量;例如:打开、创建文件,需要释放。

我们平时都是在最后释放资源,如果中间发送了异常,就直接return了,后面的代码不再执行

可以使用defer,避免这种情况。

  • 当defer被声明时,其参数就会被实时解析
为什么第一个defer输出的值还是3 ?
实际上 当 到第一个refer的时候,a的值已经确定了

var a int = 3
// 推迟调用, defer 跟 函数调用

defer fmt.Println("推迟执行后:a的值:", a)

// 此时 这里 就相当于 解析成了 fmt.Println("推迟执行后:a的值:", 3)

  • 当有多个defer的时候,defer的执行顺序为 先进后出(类似于 数据结构中的
package main

import "fmt"

func main () {

    var a int = 3
    // 推迟调用, defer 跟 函数调用
    defer fmt.Println("推迟执行后:a的值:", a)
    fmt.Println("a的值:", a)
    a += 3
    defer fmt.Println("最后,a的值:", a)
    fmt.Println("我是main函数的最后一行代码")
}

a的值: 3

我是main函数的最后一行代码

最后,a的值: 6

推迟执行后:a的值: 3

第一个defer,是最后执行的

  • defer可以读取 有名返回值

    package main
    
    import "fmt"
    
    func c () (i int) {
        defer func () {
            i++
        }()
        return 1
    }
    
    func main () {
    
        a := c()
        fmt.Println("a=", a)
    
    }
    

    输出

    a=2

    defer是在return调用之后才执行的。这里需要明确defer代码块的作用域

    仍然在函数之内

    defer的作用域仍然在c函数之内,因此defer仍然可以读取c函数内的变量(如果无法读取函数内变量,那又如何进行变量清除呢...)

    当执行return 1之后,i的值就是1,此时此刻,defer代码块开始执行,对i进行自增操作,因此输出2

指针

Go拥有指针。指针保存了值的内存地址

类型 *T 是指向T类型值的指针。其零值为 nil

var p *int

& 操作符会生成一个指向其操作数的指针

i := 42
p = &i

* 操作符表示指针指向的底层值

fmt.Println(*p)  // 通过指针读取 i
*p = 21  // 通过指针p设置i

这也就是通常所说的 “简介引用” 或 “重定向”。

与C不同,Go没有指针运算

package main

import "fmt"

func main () {

    var p *int
    i := 42
    p = &i
    // p= 0x14000124008   获取的i所在的 内存地址
    fmt.Println("p=", p)
    // *p=42  获取i的值
    fmt.Println("*p=", *p)

    // 通过*p 对i重新赋值
    *p = 66
    // i= 66
    fmt.Println("i=", i)

}

结构体

一个结构体 struct 就是一组字段 field

结构体字段 使用 . 来访问 v.X

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main () {

    v := Vertex{1, 2}
    fmt.Println(v)
    v.X = 6
    fmt.Println(v.X)

}

{1 2}

6

结构体指针

结构体字段可以通过结构体指针来访问

如果我们有一个指向结构体的指针p,那么可以通过(*p).X来访问其字段X。不给这么写太啰嗦了。所以语言也允许我们使用隐式间接引用,直接写p.X就可以

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main () {

    v := Vertex{1, 2}
    fmt.Println(v)
    p := &v
    p.X = 1e9
    fmt.Println(p.X)

}

{1 2}

1000000000

结构体文法

结构体文法通过直接列出字段的值新分配一个结构体。

使用Name:语法可以仅列出部分字段(字段名的顺序无关)

特殊的前缀 & 返回一个指向结构体的指针

package main

import "fmt"

type Vertex struct {
    X, Y int
}
var (
    // 创建一个Vertex类型的结构体
    v1 = Vertex{1, 2}
    // Y: 0被隐式赋予
    v2 = Vertex{X: 1}
    // X: 0, Y: 0
    v3 = Vertex{}
    // 创建一个 *Vertex 类型的结构体(指针)
    v4 = &Vertex{1, 2}
)
func main () {

//    fmt.Println(*v4)
    fmt.Println(v1, v2, v3, v4)

}

数组

类型 [n]T 表示拥有n个T类型的值的数组

表达式

var a [10]int

会将变量a声明为拥有10个整数的数组。

数组的长度是其类型的一部分,因此数组不能改变大小

Go提供了更加便利的方式来使用数组

package main

import "fmt"

func main () {

    var a [2]string
    a[0] = "Hello"
    a[1] = "World"
    fmt.Println(a[0], a[1])
    fmt.Println(a)
    primes := [6]int{2, 3, 5, 7, 11, 13}
    fmt.Println(primes)

}

在多行切片、数组或映射文字中,每行必须以逗号结尾

例如上面的程序:

primes := [6] int {2, 3, 5, 7, 11,

13

}

如果这样写,编译的时候会报错。

syntax error: unexpected newline, expecting comma

解决办法就是在13后面加个 逗号

切片

每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角

在实践中,切片比数组更常用。

类型 [ ]T 表示一个元素类型为 T的切片。

切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:

a[low : high]

它会选择一个半开区间,包括第一个元素,但排除最后一个元素。

左开右闭 [low : high )

primes := [6]int{2, 3, 5, 7, 11, 13}
	var s []int = primes[1:4]

切片就像数组的引用

切片并不存储任何数据,它只是描述了底层数组中的一段。

更改切片的元素会修改器底层数组中对应的元素

与它共享底层数组的切片都会观测到这些改变。

package main

import "fmt"

func main () {
    names := [4]string {
        "John",
        "Paul",
        "George",
        "Ringo"}
    fmt.Println(names)
    a := names[0:2]
    b := names[1:3]
    fmt.Println(a, b)
    b[0] = "XXXX"
    fmt.Println(a, b)
    fmt.Println(names)
}

ps:这里有个细节,names的最后的花括号 没有单独占一行,如果最后的花括号

输出:

[John Paul George Ringo]

[John Paul] [Paul George]

[John XXXX] [XXXX George]

[John XXXX George Ringo]

切片文法

切片文法类似于没有长度的数组文法

这是一个数组 文法

[3]bool {true, true, false}

下面这样则会创建一个和上面相同的数组,然后构建一个引用了它的切片

[]bool {true, true, false}
s := []struct{
  i int
  b bool
}{
  {2, true},
  {3, false},
  {5, true},
  {7, trye},
  {11, false},
  {13, true}, // 这里的逗号不能少
}

切片的长度和容量

切片拥有 长度容量

切片的长度就是它所包含的元素个数

切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数

切片s的长度和容量可通过表达式len(s) 和 cap(s)来获取


标题:Go语言学习记录(一)
作者:gitsilence
地址:https://blog.lacknb.cn/articles/2021/07/04/1625371720620.html