以下是一篇关于依据《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)/2、x*y、x^y、x*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)
}
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接口这避免了无限递归。