golang基础知识汇总

5 天前

golang基础知识汇总

golang基础知识汇总

基础之打印hello world

package main

import "fmt"

func main() {
    fmt.Println("Hello World")
}

程序打印hello world是怎么做到的需要哪些东西,和我们学习的c语言有什么基础

每个程序都是由包开始的,从main包开始,在打印hello world这个程序中,我们还使用了另外一个包fmt,使用import的方式导入,fmt在基础使用就是打印,go这里面的打印其实又和java中的差不多,ln换行,其中fmt还有很多作用,可以慢慢尝试不同的方法打印,会有什么不同。

导入不同的包使用

import ("fmt"
        "math"
)

导出名

go中一个字母开头以大写的方式开头,就代表他是已经导出的,比如我们在使用fmt的时候,fmt.Println时候P是大写的代表他是导出的,如果我们使用fmt.println放到编译器里面看看会有什么报错

image-20231210133552708

image-20231210133552708

undefined没有被定义

函数

go里面的函数用法也很简单

package main

import "fmt"

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

func main() {
    fmt.Println(add(42, 13))
}

func开头 创建了一个add函数用法就是返回一x+y的值,其中add(x int, y int) int代表x和y是int类型的,后面的int代表返回的值是int类型的。

下面就是用main函数调用add函数实现一个和的功能

简化版

package main

import "fmt"

func add(x,y int) int {  //有多个变类型相同时可以省略,只写一个
    return x + y
}

func main() {
    fmt.Println(add(42, 13))
}

变量

go中声明一个变量的方法,var 语句用于声明一个变量列表,跟函数的参数列表一样,类型在最后。

exp: var i,m,b int

同时变量声明也可以包含初始值,每个变量对应一个初始值,如果初始化值已存在,可以省略写类型,变量直接从初始值中获得类型

在函数中声明变量可以用更简单的

package main

import "fmt"

func main() {
    var i, j int = 1, 2
    k := 3
    c, python, java := true, false, "no!"

    fmt.Println(i, j, k, c, python, java)
}

例如这里的k:=3就是一个短变量声明,这种方法只能在函数内部使用,放到函数外部就必须要使用var,func关键字开始。

给变量声明时没有明确的初始化值时,变量会被赋予零值

  • 数值类型的零值时0
  • 布尔类型的零值是bool
  • 字符串的零值是""(空字符串)
image-20231210142553617

image-20231210142553617

变量的类型转换

package main

import (
    "fmt"
    "math"
)

func main() {
    var x, y int = 3, 4
    var f float64 = math.Sqrt(float64(x*x + y*y))
    fmt.Println(f)
}

这个例子里面就是在main函数里面定义了两个int类型的,在后面又把他们转换成float64类型的函数内部还可以使用更简单的方式

i := 42
f := float64(i)
u := uint(f)

常量

常量的声明与变量类似,只不过是使用 const 关键字。

常量可以是字符、字符串、布尔值或数值。

常量不能用 := 语法声明。

package main

import "fmt"

const Pi = 3.14

func main() {
    const World = "世界"
    fmt.Println("Hello", World)
    fmt.Println("Happy", Pi, "Day")

    const Truth = true
    fmt.Println("Go rules?", Truth)
}

数值常量

数值常量是高精度的

一个未指定类型的常量由上下文来决定其类型。

for与if的使用

go里面for的用法还是和c中有着不小的差距

package main

import "fmt"

func main() {
    for i := 0; i < 10; i++ {
        sum := i * 2
        fmt.Println(sum)
    }

}

一个简单的例子,在go中我们不需要使用(),但是{}是必须的

基本的 for 循环由三部分组成,它们用分号隔开:

  • 初始化语句:在第一次迭代前执行
  • 条件表达式:在每次迭代前求值
  • 后置语句:在每次迭代的结尾执行

初始化语句通常为一句短变量声明,该变量声明仅在 for 语句的作用域中可见。

c里面的while就相当于go里面的while,直接看例子说明

package main

import "fmt"

func main() {

    sum := 1
    for sum < 100 {
        sum += sum
        fmt.Println(sum)

    }

}

if的使用

if的结构和for类似也是不需要,括号但是花括号是必须的,此外if在执行条件表达式前还可以先执行一个简单的语句

exp

package main

import (
    "fmt"
    "math"
)

func pow(x, n, lim float64) float64 {
    if v := math.Pow(x, n); v < lim {
        return v
    }
    return lim
}

func main() {
    fmt.Println(
        pow(3, 2, 10),
        pow(3, 3, 20),
    )
}

此外if else语句,用法和c里面的相似,在整个if else语句里面定义的东西,在语句外面不可使用。

练习:循环与函数

为了练习函数与循环,我们来实现一个平方根函数:用牛顿法实现平方根函数。

计算机通常使用循环来计算 x 的平方根。从某个猜测的值 z 开始,我们可以根据 z² 与 x 的近似度来调整 z,产生一个更好的猜测:

z -= (z*z - x) / (2*z)

重复调整的过程,猜测的结果会越来越精确,得到的答案也会尽可能接近实际的平方根。

在提供的 func Sqrt 中实现它。无论输入是什么,对 z 的一个恰当的猜测为 1。 要开始,请重复计算 10 次并随之打印每次的 z 值。观察对于不同的值 x(1、2、3 ...), 你得到的答案是如何逼近结果的,猜测提升的速度有多快。

提示:用类型转换或浮点数语法来声明并初始化一个浮点数值:

z := 1.0
z := float64(1)

然后,修改循环条件,使得当值停止改变(或改变非常小)的时候退出循环。观察迭代次数大于还是小于 10。 尝试改变 z 的初始猜测,如 x 或 x/2。你的函数结果与标准库中的 math.Sqrt 接近吗?

注: 如果你对该算法的细节感兴趣,上面的 z² − x 是 z² 到它所要到达的值(即 x)的距离, 除以的 2z 为 z² 的导数,我们通过 z² 的变化速度来改变 z 的调整量。 这种通用方法叫做牛顿法。 它对很多函数,特别是平方根而言非常有效。)

exp

package main

import (
    "fmt"
    "math"
)

func Sqrt(x float64) float64 {
    z := 1.0
    for i := 0; i < 10; i++ {
        z -= (z*z - x) / (2 * z)
        fmt.Printf("Iteration %v: z = %v\n", i+1, z)
    }
    return z
}  //这里实现了一个求平方根的函数,来和math包里面的求平方根函数对比

func main() {
    values := []float64{1, 2}
    for _, x := range values {
        result := Sqrt(x)
        fmt.Printf("Square root of %v (math.Sqrt): %v\n", x, math.Sqrt(x))
        fmt.Printf("Square root of %v (custom Sqrt): %v\n\n", x, result)
    }
}

switch

switch 是编写一连串 if - else 语句的简便方法。它运行第一个值等于条件表达式的 case 语句。

Go 的 switch 语句类似于 C、C++、Java、JavaScript 和 PHP 中的,不过 Go 只运行选定的 case,而非之后所有的 case。 实际上,Go 自动提供了在这些语言中每个 case 后面所需的 break 语句。 除非以 fallthrough 语句结束,否则分支会自动终止。 Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。

举个简单例子说明(只运行选定的 case,而非之后所有的 case

exp

package main

import (
    "fmt"
    "runtime"
)

func main() {
    fmt.Print("Go runs on ")
    switch os := runtime.GOOS; os {
    case "darwin":
        fmt.Println("OS X.")
    case "linux":
        fmt.Println("Linux.")
    default:
        // freebsd, openbsd,
        // plan9, windows...
        fmt.Printf("%s.\n", os)
    }
}

他只会输出你本机的系统

switch 的 case 语句从上到下顺次执行,直到匹配成功时停止。

如果switch没有条件的话,他就相当于switch=true,条件为真才跳出

defer用法

defer 语句会将函数推迟到外层函数返回之后执行。

推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。

exp

package main

import "fmt"

func main() {
    defer fmt.Println("world")

    fmt.Println("hello")
}
打印结果
//hello
//world

先打印了hello,在打印world

指针

go的指针保存了值的内存地址

指针用法

&操作符会生成一个指向其操作数的指针
i:=1
p=&i
*操作符表示指针指向的底层值
i:=1
p=&i
fmt.Println(*p) // 通过指针 p 读取 i
*p = 21         // 通过指针 p 设置 i
go没有指针运算,不同与c
package main

import "fmt"

func main() {
    i, j := 42, 2701

    p := &i         // 指向 i
    fmt.Println(*p) // 通过指针读取 i 的值
    *p = 21         // 通过指针设置 i 的值
    fmt.Println(i)  // 查看 i 的值

    p = &j         // 指向 j
    *p = *p / 37   // 通过指针对 j 进行除法运算
    fmt.Println(j) // 查看 j 的值
}

结构体

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    fmt.Println(Vertex{1, 2})
}

学过c就能理解的

结构体字段可以通过点号访问

exp

package main

import "fmt"

type Vertes struct {
    X int
    Y int
}

func main() {
    s := Vertes{1, 2}
    s.Y = 3
    fmt.Println(s.Y)
}
通过 s.Y改变Y的值并打印Y的值

结结构体指针

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

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

了解到上一节的指针便可以很快的搞明白这个,只不过把指针指向了结构体,通过指针改变变量的值

结构体文法

在Go语言中,结构体的文法指的是如何定义一个结构体类型。结构体文法的基本语法如下:

type StructName struct {
    Field1 type1
    Field2 type2
    // ...
}

其中:

  • StructName 是结构体的名称。
  • Field1Field2 等是结构体的字段名。
  • type1type2 等是字段的数据类型。

例如,下面是一个表示矩形的结构体的定义:

type Rectangle struct {
    Width  float64
    Height float64
}

这个结构体包含两个字段,分别是 WidthHeight,它们的数据类型都是 float64。通过这个结构体,你可以创建表示具体矩形的实例。

结构体文法也支持在创建结构体实例时,通过字段名指定字段的值,例如:

// 创建一个 Rectangle 实例
rect := Rectangle{
    Width:  10.0,
    Height: 5.0,
}

这种方式可以提高代码的可读性,因为不依赖于字段的顺序。结构体文法是Go语言中用于定义和创建结构体类型的基本语法。

数组

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

表达式

var a [10]int

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

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

比数组更灵活的切片

切片

数组的的大小是固定的,但是切片回味==会为数组提供动态大小,灵活的

切片的使用方法

用类型[]T表示一个元素类型为T的切片,切片通过下标来表示上下界限,例如

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

var s []int = primes[1:4]

打印s就会输出{3 5 7}

切片另说

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] = "XXX"
    fmt.Println(a, b)
    fmt.Println(names)
}
image-20231211143927659

image-20231211143927659

能看懂为什么打印的是这个就能明白切片

切片不会存储任何数据,他只是描述了一段底层数组中的一段,如果更改了切片的元素,那么它对应的底层数组的元素也会修改,那么下面再引用,切片,它对应的内容都会修改。

此外切片可以利用他的默认行为来忽略上下界

a[0:10]
a[:10]
a[0:]
a[:]    --等价的

切片文法

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

这是一个数组文法:

[3]bool{true, true, false}

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

[]bool{true, true, false}

exp

package main

import "fmt"

func main() {
    q := []int{2, 3, 5, 7, 11, 13}
    fmt.Println(q)

    r := []bool{true, false, true, true, false, true}
    fmt.Println(r)

    s := []struct {
        i int
        b bool
    }{
        {2, true},
        {3, false},
        {5, true},
        {7, true},
        {11, false},
        {13, true},
    }
    fmt.Println(s)
}

这个代码中通过切片文法演示了如何初始化不同类型的切片。在切片文法中,通过使用花括号 {} 将元素列表括起来,可以轻松地创建并初始化切片。

切片的长度与容量

切片拥有 长度容量

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

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

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

package main

import "fmt"

func main() {
    s := []int{2, 3, 5, 7, 11, 13}
    printSlice(s)

    // 截取切片使其长度为 0
    s = s[:0]
    printSlice(s)

    // 拓展其长度
    s = s[:4]
    printSlice(s)

    // 舍弃前两个值
    s = s[2:]
    printSlice(s)
}

func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
//打印内容
len=6 cap=6 [2 3 5 7 11 13]
len=0 cap=6 []
len=4 cap=6 [2 3 5 7]
len=2 cap=4 [5 7]

用 make 创建切片

切片可以用内建函数 make 来创建,这也是创建动态数组的方式。

make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:

a := make([]int, 5)  // len(a)=5

要指定它的容量,需向 make 传入第三个参数:

b := make([]int, 0, 5) // len(b)=0, cap(b)=5

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:]      // len(b)=4, cap(b)=4
package main

import "fmt"

func main() {
    a := make([]int, 5)
    printSlice("a", a)

    b := make([]int, 0, 5)
    printSlice("b", b)

    c := b[:2]
    printSlice("c", c)

    d := c[2:5]
    printSlice("d", d)
}

func printSlice(s string, x []int) {
    fmt.Printf("%s len=%d cap=%d %v\n",
        s, len(x), cap(x), x)
}

向切片追加元素

为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append 函数。内建函数的文档对此函数有详细的介绍。

func append(s []T, vs ...T) []T

append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。

append 的结果是一个包含原切片所有元素加上新添加元素的切片。

s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。

package main

import "fmt"

func main() {
    var s []int
    printSlice(s)

    // 添加一个空切片
    s = append(s, 0)
    printSlice(s)

    // 这个切片会按需增长
    s = append(s, 1)
    printSlice(s)

    // 可以一次性添加多个元素
    s = append(s, 2, 3, 4)
    printSlice(s)
}

func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}

代码中就可以很好的表现出这一点,初始的切片长度和容量都是0,但是我们使用append函数向里面追加元素的时候,append就会自动新建一个比原始切片两倍的切片

range

go里面的range和python里面的range大差不差,感觉用法都一样,当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。

贴个代码自行感受一下

package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
    for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v)
    }
}

range续

可以将下标或值赋予 _ 来忽略它。

for i, _ := range pow
for _, value := range pow

若你只需要索引,忽略第二个变量即可。

for i := range pow

映射

map

package main

import "fmt"

type Vertex struct {
    Lat, Long float64
}

var m map[string]Vertex

func main() {
    m = make(map[string]Vertex)
    m["Bell Labs"] = Vertex{
        40.68433, -74.39967,
    }
    fmt.Println(m["Bell Labs"])
}

研读这一段代码

定义了一个结构体float64类型的

然后

var m map[string]Vertex
这一行代码创建了一个映射 m,其中的键是字符串,对应的值是 Vertex 类型的对象。
m = make(map[string]Vertex)
    m["Bell Labs"] = Vertex{
        40.68433, -74.39967,
        }
这一段代码就是利用到了映射,string类型键值必须是string类型的,对应的Vertex的值是float64的

下面一个代码段更好的理解

// 创建一个空的映射
m := make(map[string]Vertex)

// 添加一个键值对
m["someKey"] = Vertex{1.0, 2.0}

// 访问键对应的值
v := m["someKey"]
fmt.Println(v) // 输出 {1.0, 2.0}

修改映射

在映射中插入或修改元素

m[key] = elem

获取元素:

elem = m[key]

删除元素:

delete(m, key)

通过双赋值检测某个键是否存在:

elem, ok = m[key]

keym 中,oktrue ;否则,okfalse

key 不在映射中,那么 elem 是该映射元素类型的零值。

同样的,当从映射中读取某个不存在的键时,结果是映射的元素类型的零值。

:若 elemok 还未声明,你可以使用短变量声明:

修改exp

package main

import "fmt"

type Vertex struct {
    X, Y float64
}

func main() {
    // 创建一个空的映射
    m := make(map[string]Vertex)

    // 添加键值对
    m["someKey"] = Vertex{1.0, 2.0}

    // 输出原始值
    fmt.Println("Original:", m)

    // 修改映射中的值
    m["someKey"] = Vertex{3.0, 4.0}

    // 输出修改后的值
    fmt.Println("Modified:", m)
}

删除

exp

package main

import "fmt"

type Vertex struct {
    X, Y float64
}

func main() {
    // 创建一个映射并添加键值对
    m := make(map[string]Vertex)
    m["someKey"] = Vertex{1.0, 2.0}

    // 输出原始映射
    fmt.Println("Original:", m)

    // 删除键为 "someKey" 的键值对
    delete(m, "someKey")

    // 输出删除后的映射
    fmt.Println("After deletion:", m)
}

用的delete函数

练习映射

题目

实现 WordCount。它应当返回一个映射,其中包含字符串 s 中每个“单词”的个数。函数 wc.Test 会对此函数执行一系列测试用例,并输出成功还是失败。

package main

import (
    "golang.org/x/tour/wc"
    "strings"
)

func WordCount(s string) map[string]int {
    // 使用 strings.Fields 函数将输入字符串分割成单词数组
    words := strings.Fields(s)

    // 创建一个映射来存储单词出现的次数
    wordCount := make(map[string]int)

    // 遍历单词数组,更新映射中的计数
    for _, word := range words {
        wordCount[word]++
    }

    return wordCount
}

func main() {
    // 使用测试函数来验证 WordCount 函数的正确性
    wc.Test(WordCount)
}

这边定义了一个函数WordCount,是一个map类型的

下面的strings.Fields(s)是go里面的一个函数

`strings.Fields(s)` 是 Go 语言标准库中 `strings` 包提供的一个函数,用于将字符串 `s` 按照空格分割成一个字符串切片(slice)

就是读取这个包"golang.org/x/tour/wc"里面给出的测试单词,并将他分割

函数值

函数也是值,他也可以像其他值一样传递,函数值可以用作函数的参数或返回值。

exp

package main

import (
    "fmt"
    "math"
)

func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}

func main() {
    hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }
    fmt.Println(hypot(5, 12))

    fmt.Println(compute(hypot))
    fmt.Println(compute(math.Pow))
}

具体分析

func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}

第一眼看上去很迷糊,其实逐层分析就会很简单

这个compute函数接受一个fn参数,而这个参数的类型是一个函数,这个函数接受两个float64类型的数,并返回一个float64的数

func main() {
    hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    }

这哥main函数里面创建了一个匿名函数,hypot := func(x, y float64) float64

两个float64类型的参数

fmt.Println(hypot(5, 12))

fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))

第一个调用了这个匿名函数给x y赋值,

第二个将hypot传递给了compute函数,所以计算结果是5,

第三个将math.Pow传递给了compute计算平方

函数的闭包

Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。

例如,函数 adder 返回一个闭包。每个闭包都被绑定在其各自的 sum 变量上。

还是通过例子说明

package main

import "fmt"

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(
            pos(i),
            neg(-2*i),
        )
    }
}

逐个分析

func adder() func(int) int {
    sum := 0
    return func(x int) int {
        sum += x
        return sum
    }
}

创建了一个闭包函数,在这里这个闭包函数返回一个函数,函数内部引用了一个sum变量

  1. 它初始化为 0。这个变量 sum 被内部的匿名函数引用,并且由于闭包的特性,这个变量的生命周期会持续到返回的函数不再被引用。

  2. 返回的匿名函数可以访问 adder 函数作用域内的变量 sum。每次这个函数被调用时,它将传入的参数 x 加到 sum 上,并返回新的 sum 值。

func main() {
    pos, neg := adder(), adder()
    for i := 0; i < 10; i++ {
        fmt.Println(
            pos(i),
            neg(-2*i),

main函数里面又创建了两个adder闭包,他们是相互独立的,各自引用里面的sum变量,互不干扰。

练习:斐波纳契闭包

实现一个 fibonacci 函数,它返回一个函数(闭包),该闭包返回一个斐波纳契数列 (0, 1, 1, 2, 3, 5, ...)

使用闭包函数实现还是比较简单的

package main

import "fmt"

// 返回一个“返回int的函数”
func fibonacci() func() int {
    a, b := 0, 1
    return func() int {
        result := a
        a, b = b, a+b
        return result
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}

简单的数学问题加闭包函数的基础使用

方法

Go 没有类。不过你可以为结构体类型定义方法。

方法就是一类带特殊的 接收者 参数的函数。

方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。

看例子说明其用法

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v.Abs())
}

在这里结构体的东西就不多赘述了,关键点就在

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

这里定义了一个Abs方法,该方法计算并返回 Vertex 结构体实例 v 的欧几里德范数(Euclidean norm)或称为长度

下面main函数就是引用了

方法即函数

记住:方法只是个带接收者参数的函数。

现在这个 Abs 的写法就是个正常的函数,功能并没有什么变化。

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func Abs(v Vertex) float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(Abs(v))
}

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...