Go语言入门 | 豆包MarsCode AI刷题

72 阅读6分钟

以下是一篇关于依据《Go 语言之旅》编写的 Go 基础语法笔记的序言示例;

基础

结构体、切片和映射

  • 指针
    & 操作符会生成一个指向其操作数的指针
    * 操作符表示指针指向的底层值。
var p *int
i := 42
p = &i
fmt.Println(*p) // 通过指针 p 读取 i
*p = 21         // 通过指针 p 设置 i
  • 结构体
    结构体(struct)是一组字段(field) 结构体字段通过.访问

  • 数组
    类型[n]T表示一个数组,拥有n个类型为T的值
    数组的长度是其类型的一部分,因此数组不能改变大小。

  • 切片
    切片就像数组的引用 切片并不存储任何数据,它只是描述了底层数组中的一段。更改切片的元素会修改其底层数组中对应的元素。 切片为数组元素提供了动态大小的、灵活的视角
    类型 []T 表示一个元素类型为 T 的切片

    用make创建切片,分配一个元素为零值的数组并返回一个引用其的切片

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

    切片通过两个下标来界定,一个下界和一个上界(左开右闭),二者以冒号分隔:

    a[low : high]
    

    追加元素append函数,append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。append 的结果是一个包含原切片所有元素加上新添加元素的切片。

    var s []int
    s = s.append(s, 2, 3, 4)
    

    for 循环的 range 形式可遍历切片或映射。当使用 for 循环遍历切片时,每次迭代都会返回两个值。 第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。

    练习—切片:实现 Pic。它应当返回一个长度为 dy 的切片,其中每个元素是一个长度为 dx,元素类型为 uint8 的切片。当你运行此程序时,它会将每个整数解释为灰度值 (好吧,其实是蓝度值)并显示它所对应的图像。图像的解析式由你来定。几个有趣的函数包括 (x+y)/2x*yx^yx*log(y) 和 x%(y+1)

    package main
    
    import "golang.org/x/tour/pic"
    
    func Pic(dx, dy int) [][]uint8 {
            pic := make([][]uint8, dy)
            for i,_ := range pic {
                    pic[i] = make([]uint8, dx)
                    for j,_ := range pic[i] {
                            pic[i][j] = uint8(i % (j + 1))
                    }
            }
            return pic
    }
    
    func main() {
            pic.Show(Pic)
    }
    

    注意:=用于声明并初始化变量,=用于对变量赋值

  • map映射
    map 映射将键映射到值。映射的零值为 nil 。nil 映射既没有键,也不能添加键。make 函数会返回给定类型的映射,并将其初始化备用。 获取元素:

    elem = m[key]
    

    删除元素:

    delete(m, key)
    

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

    elem, ok = m[key]
    

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

    [strings.Fields]会自动切分一个字符串到一个数组,每个数组里是一个word

    package main
    
    import (
            "golang.org/x/tour/wc"
            "strings"
    )
    
    func WordCount(s string) map[string]int {
            m := make(map[string]int)
            words := strings.Fields(s)
            for _, word := range words {
                    m[word] = m[word] + 1
            }
            return m
    }
    
    func main() {
            wc.Test(WordCount)
    }
    复制代码
    
  • 闭包 闭包是一个函数值,引用了函数体之外的变量

    1.    
        package main
    1.
    1.  import "fmt"
    1.
    1.  // fibonacci is a function that returns
    1.  // a function that returns an int.
    1.  func fibonacci() func() int {
    1.  a, b := 0, 1
    1.  return func() int {
    1.  c := a
    1.  a, b = b, a+b
    1.  return c
    1.  }
    1.  }
    1.
    1.  func main() {
    1.  f := fibonacci()
    1.  for i := 0; i < 10; i++ {
    1.  fmt.Println(f())
    1.  }
    1.
    

    在这个斐波那契数列的例子中,返回的匿名函数值是闭包,引用了外部变量a和b

方法和接口

方法

Go 没有类。不过可以为类型定义方法。 方法就是一类带特殊的 接收者 参数的函数。方法只是个带接收者参数的函数。
对于指针类型的接收者,为了方法能够修改其接收者指向的值,避免在每次调用方法时复制该值。若值的类型为大型结构体时,这样会更加高效。注意:带指针参数的函数必须接受一个指针:而接收者为指针的的方法被调用时,接收者既能是值又能是指针:

package main

import "fmt"

type Vertex struct {
	X, Y float64
}

func (v *Vertex) Scale(f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func ScaleFunc(v *Vertex, f float64) {
	v.X = v.X * f
	v.Y = v.Y * f
}

func main() {
	v := Vertex{3, 4}
	v.Scale(2)
	ScaleFunc(&v, 10)

	p := &Vertex{4, 3}
	p.Scale(3)
	ScaleFunc(p, 8)

	fmt.Println(v, p)
}

接口

接口是一组方法签名的集合,任何类型只要实现了接口中定义的所有方法,就隐式地实现了这个接口,无需显式声明。接口在 Go 中用来实现多态行为(多态:不同类型实现相同的接口,允许在运行时动态决定具体调用。- 解耦:通过接口定义行为,避免直接依赖具体类型,提升代码扩展性)。任何类型只要实现了这些方法,就可以被视为实现了这个接口。

package main

import (
	"fmt"
	"math"
)
//定义一个 Abser 接口,要求实现一个 Abs 方法。
type Abser interface {
	Abs() float64
}

func main() {

	//通过接口变量 a 演示多态行为
	var a Abser
	
	f := MyFloat(-math.Sqrt2)
	//v := Vertex{3, 4}

	a = f  // a MyFloat 实现了 Abser
	//a = &v // a *Vertex 实现了 Abser

	// 下面一行,v 是一个 Vertex(而不是 *Vertex)
	// 所以没有实现 Abser。
	// a = v

	fmt.Println(a.Abs()) //a.Abs():1.4142135623730951 a.Abs():5
}
// 定义两个类型 MyFloat 和 Vertex,并分别实现 Abs 方法
type MyFloat float64

func (f MyFloat) Abs() float64 {
	if f < 0 {
		return float64(-f)
	}
	return float64(f)
}

type Vertex struct {
	X, Y float64
}

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

fmt 包中定义的 Stringer 是最普遍的接口之一。

type Stringer interface {
    String() string
}

Stringer 是一个可以用字符串描述自己的类型。fmt 包(还有很多包)都通过此接口来打印值。
练习:Stringer

通过让 IPAddr类型实现fmt.Stringer来打印点号分隔的地址。

例如,IPAddr{1, 2, 3, 4}应当打印为 "1.2.3.4"。

package main

import "fmt"

type IPAddr [4]byte

// TODO: 为 IPAddr 添加一个 "String() string" 方法。
func (ip IPAddr) String() string {
	return fmt.Sprintf("%v.%v.%v.%v", ip[0], ip[1], ip[2], ip[3])
}

func main() {
	hosts := map[string]IPAddr{
		"loopback":  {127, 0, 0, 1},
		"googleDNS": {8, 8, 8, 8},
	}
	for name, ip := range hosts {
		fmt.Printf("%v: %v\n", name, ip)
	}
//输出
//loopback: 127.0.0.1
//googleDNS: 8.8.8.8
}

错误

从之前的练习中复制Sqrt函数,修改它使其返回error值。 Sqrt接受到一个负数时,应当返回一个非 nil 的错误值。复数同样也不被支持。 创建一个新的类型 type ErrNegativeSqrt float64 并为其实现

package main

import (
	"fmt"
)

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %g", float64(e))
}
//注意如果在 Error 方法内调用 fmt.Sprint(e) 会让程序陷入死循环

func Sqrt(x float64) (float64, error) {
	if x < 0 {
		return 0, ErrNegativeSqrt(x)
	}
	z:= float64(x / 2)
	for i := 0; i < 10; i ++ {
		if z < 1e-6 {
			break
		}
	z -= (z*z - x) / (2*z)
	}
	return z, nil
}

func main() {
	if result, err := Sqrt(-2); err != nil {
		fmt.Println("Error:", err)
	} else {
	fmt.Println(result)
	}
}

注意如果在 Error 方法内调用 fmt.Sprint(e) 会让程序陷入死循环
fmt.Sprint(e) → 调用 e.Error() → 再调用 fmt.Sprint(e) → 死循环。
所以需要在调用 fmt.Sprint 或类似函数之前将 e 转换为其底层类型,而不是直接使用 e 本身
转换后的值是一个基本类型(float64),不再是ErrNegativeSqrt类型。 基本类型没有实现error接口这避免了无限递归。

Readers