首页 > 技术文章 > A Tour of Go

codeclock 2020-11-30 16:55 原文

官网:https://tour.golang.org/

Basics

Packages, variables, and functions

外部使用

对比Java,变量名大写可以看成是 public,小写是 private

函数返回多个结果

func swap(x,y int) (int, int){
    return y,x
}

func main(){
    a,b := swap(2,3)
    fmt.Println(a,b) // 3 2
}

指定返回值

// 返回值可以指定名称,可以直接return
// 函数返回值默认为 x, y
func split(sum int)(x,y int){
    x = sum * 2
    y = sum + 3
    return
}

func main(){
    a,b := split(2)
    fmt.Println(a,b) // 4 5
}

var

// 定义一串变了,类型在最后定义
var c,python,java string

func main(){
    var i int
    fmt.Println(i,c,python,java) 
    // 0 false false false
}

var 初始化

// var可以定义初始化值,此时类型可以省略,使用初始化的值的类型
var i,j int = 1,2

func main(){
    var c, python, java = true,false,"best!"
    fmt.Println(i,j,c,python,java)
    // 1 2 true false best!
}

:= 简短的变量声明

// 在函数内部,使用使用 := 来替代var类型隐式的声明,在函数外部由于每个声明必须以 var func开头,所以 := 不是在函数外使用
func main(){
    var i,j int = 1,2
    k := 3
    c, java, python := true,0,"no"
    
    fmt.Println(i,j,k,c,java,python)
    //1 2 3 true 0 no
}

基本类型

bool
string
int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr
byte // alias for uint8
rune // alias for int32
     // represents a Unicode code point
float32 float64
complex64 complex128
// The int, uint, and uintptr types are usually 32 bits wide on 32-bit systems and 64 bits wide on 64-bit systems. When you need an integer value you should use int unless you have a specific reason to use a sized or unsigned integer type
// 类似import,变量声明也可以分解成块
import(
	"fmt"
    "math/cmplx"
)

var(
	a bool = false
    b uint64 = 1<<64 -1
    c complex128 = cmplx.Sqrt(-5 + 12i)
    d string = "nonono"
)

func main(){
    // %T 表示类型,%v 表示值, \n 表示换行
    fmt.Printf("type:%T, value:%v\n", a, a)
    fmt.Printf("type:%T, value:%v\n", b, b)
    fmt.Printf("type:%T, value:%v\n", c, c)
    fmt.Printf("type:%T, value:%v\n", d, d)
    // type:bool, value:false
	// type:uint64, value:18446744073709551615
	// type:complex128, value:(2+3i)
	// type:string, value:nonono
}

零值(默认值)

// 变量声明没有指定值会给定默认值
// 数字类型:0
// 布尔类型:false
// 字符串类型:""

func main(){
    var i int
    var f float64
    var b bool
    var s string
    fmt.Printf("%v %v %v %q",i,f,b,s)
    // 0 0 false ""
}

类型转换

//在 Go 中,不同类型的项之间的赋值需要显示的转换
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
// 简化写法
func method(){
    i := 42
    f := float(i)
    u := uint(f)
}

// 示例
func main(){
    var x, y int = 3, 4
	var f float64 = math.Sqrt(float64(x*x + y*y))
    var z uint = uint(f)
    fmt.Println(x, y, z) // 3 4 5
}

类型推断

Go 会自动根据右边的值来推断类型

var i int
j := i // j is an int

i := 42           // int
f := 3.142        // float64
g := 0.867 + 0.5i // complex128

常量

// Constants are declared like variables, but with the const keyword.
// Constants can be character, string, boolean, or numeric values.
// Constants cannot be declared using the := syntax.

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)
}

数字常量

// Numeric constants are high-precision values.
// An untyped constant takes the type needed by its context.无类型常量接受其上下文所需的类型

const (
	// Create a huge number by shifting a 1 bit left 100 places.
	// In other words, the binary number that is 1 followed by 100 zeroes.
	Big = 1 << 100
	// Shift it right again 99 places, so we end up with 1<<1, or 2.
	Small = Big >> 99
)

func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
	return x * 0.1
}

func main() {
	fmt.Println(needInt(Small)) // 21
	fmt.Println(needFloat(Small)) // 0.2
	fmt.Println(needFloat(Big)) 
    // 1.2676506002282295e+29
    
    // fmt.Println(needInt(Big)) 
    // --- result overflows int
}

Flow control statements: for, if, else, switch and defer

For

// Go中的 for循环没有 括号(), 但必须有 {}
func main(){
    sum := 0
    for i:=0; i<10; i++ {
        sum += i
    }
    fmt.Println(sum)
}

// 第1,3个语句是可选的
for ;i<10; {
    
}

For is Go's "while"

// 在Go中,去掉分号的 for就表示C语言中的while
func main(){
    sum := 1
    for sum<100 {
        sum += sum
    }
    fmt.Println(sum)
}

Forever

// 省略循环条件就是死循环
func main(){
    for {
        break;
    }
}

if

// Go 中的 if 类似 for循环,不需要(), 但是需要{}
func sqrt(x float64) string{
    if x<0 {
        return sqrt(-x) + "i"
    }
    return fmt.Sprint(math.Sqrt(x))
}
func main(){
    fmt.Println(sqrt(2), sqrt(-4))
    // 1.4142135623730951 2i
}

if with a short statement

// 类似于 for,if 也可以在条件之前执行一个短的语句
func pow(x,n, lim float64) float64 {
    // 声明一个v
    if v := math.Pow(x,n); v<lim {
        return v
    }
    return lim
}

if and else

// if 中定义的变量在else中也可以使用
func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {
		return v
	} else {
		fmt.Printf("%g >= %g\n", v, lim)
	}
	// can't use v here, though
	return lim
}

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

switch

// Go中的每个case不需要加break,每个case只会执行一次,go语言默认给每个case结尾都加上了break
// Go中的 case 不需要一定是常量,并且涉及的值不必须是整数
func main(){
    fmt.Print("Go runs on")
    // 可以在开始定义一个变量,也可以不定义
    switch os := runtime.GOOS; os {
    case "windows":
        fmt.Println("OS windows")
    case "Linux":
        fmt.Println("OS Linux")
    default:
        fmt.Printf("%s. \n", os)
    }
}

switch evaluation order

Time in the Go playground always appears to start at 2009-11-10 23:00:00 UTC, a value whose significance is left as an exercise for the reader.

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("When's Saturday?")
	today := time.Now().Weekday()
	fmt.Println(today)
	switch time.Saturday {
	case today + 0:
		fmt.Println("Today.")
	case today + 1:
		fmt.Println("Tomorrow.")
	case today + 2:
		fmt.Println("In two days.")
	case today + 3:
		fmt.Println("in three days")
	case today + 4:
		fmt.Println("in four days")
	case today + 5:
		fmt.Println("in five days")
	case today + 6:
		fmt.Println("in six days")
	case today + 7:
		fmt.Println("in seven days")
	default:
		fmt.Println("Too far away.")
	}
}

Defer

// defer 语句会在周围的函数返回结果之后才return
// defer 语句的值会立即计算,但是函数调用不会执行,直到周围函数调用结束
func getValue() string {
    fmt.Println("exce getValue")
    return "xx"
}

func main(){
    defer fmt.Println(getValue()+"world")
    fmt.Println("hello")
}
// 输出:
// exce getValue
// hello
// xxworld

Stacking defers

// defer 函数调用是压入到一个栈中,当函数return时,执行顺序是后进先出
func main(){
    fmt.Print(" counting ")
    
    for i:=0;i<10;i++ {
        defer fmt.Print(i)
    }
    
    fmt.Print(" done ")
}
// 输出: counting  done 9876543210

MoreTypes: Sturcts, slices, and maps

Pointers

指针保存的是一个值的内存地址

  • 类型 *T 定义一个类型为 T 的值的指针,默认是空 nil

    // 定义一个int型的指针
    var p *int
    
  • & 操作符定义一个指向其操作数的指针

    i := 42
    p = &i	// 定义一个指向i的指针
    
  • * 操作符表示指针指向的潜在的值

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

    被称为取消引用(dereferencing)或间接引用(indirecting)

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

func main() {
	i, j := 42, 28
    // 通过 := 直接定义p
	p := &i         // point to i
	fmt.Println(*p) // read i through the pointer
	*p = 21         // set i through the pointer
	fmt.Println(i)  // see the new value of i

	p = &j         // point to j
	*p = *p / 4   // divide j through the pointer
	fmt.Println(j) // see the new value of j
}

Structs

// Structs 是不同字段的集合
type Vertex struct {
    // 大写表示在外部包引用时也可以获取到
    X int
    Y int
}

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

Structs Fields

// 结构体中的字段可以通过 "." 来获取
type Vertex struct {
	X int
	Y int
}

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

Pointers to structs

// 结构体中的字段可以通过结构体的指针获取
// 为了获取字段 X,我们需要结构体的指针 p,通过这种写法获取:(*p).X
// 然而 (*p).X 这种写法是笨重的,Go语言运行我们通过 p.X 这种写法来表示,而不用写明确的间接引用

type Vertex struct {
	X int
	Y int
}

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

Struct Literals

// 可以通过 Vertex{name1:value1,name2:value2}的形式列出字段的子集,并且顺序是无关的

type Vertex struct{
    X, Y int
}

var (
    v1 = Vertex{1, 2}  // has type Vertex
    // &前缀返回指向结构体的指针
	p  = &Vertex{1, 2} // has type *Vertex
    // Y 的值默认为0
	v2 = Vertex{X: 1}  // Y:0 is implicit
    // X,Y的值都默认为0
	v3 = Vertex{}      // X:0 and Y:0
)

func main(){
    fmt.Println(v1,p,v2,v3)
    // {1 2} &{1 2} {1 0} {0 0}
}

Arrays

// 类型 [n]T 表示n个T类型元素组成的数组
// 例如:var a[10]int
// 数组的长度是其类型的一部分,因此不支持调整数组的大小
func main(){
    var a[2]string
    a[0] = "hello"
    a[1] = "world"
    fmt.Println(a[0],a[1]) // hello world
    fmt.Println(a) // [hello world]
    
    primes := [6]int{1,2,3,4,5,6}
    fmt.Println(primes) // [1 2 3 4 5 6]
}

Slices

// 类型 []T 表示T类型元素的 slice
// 一个 slice由两个下标组成,由 ":" 分隔
// 例如:a[low:hight], 它是一个半开区间,包含第一个元素,不包含最后一个元素
// a[1:4] 表示包含 a中 1 到 3 元素的slice

func main(){
    primes := [6]int{1,2,3,4,5,6}
    
    var s []int = primes[1,3]
    fmt.Println(s) // [2 3]
}

Slices are like references to arrays

// slice 类似于对 array的引用
// slice 不存储任何数据,仅描述底层数组的一部分
// 修改slice中的元素,底层的数据元素也会被修改,其他共享该数据的 slice也会看到这些修改

func main(){
    names := [4]string{
        "John",
        "Paul",
        "George",
        "Ringo"
    }
    fmt.Println(names)
    // [John Paul George Ringo]
    
    a := names[0,3]
    b := names[1,4]
    fmt.Println(a,b)
    // [John Paul] [Paul George]
    
    // 修改b中的第一个元素,对应names中的第二个元素
    b[0] = "XXX"
    fmt.Println(a,b) 
    // [John XXX] [XXX George]
    
    fmt.Println(names)
    // [John XXX George Ringo]
}

Slice literals

// slice 类似于没有长度的 array
[3]bool{true,true,false} // 这是array
[]bool{true,true,false} // 这是slice


func main(){
    // int类型slice
    q := []int{1,2,3,4,5}
    fmt.Println(q) // [1 2 3 4 5]
    
    // 布尔类型slice
    r := []bool{true,false,true,false}
    fmt.Println(r)
    // [true false true false]
    
    // 结构体类型slice
    s := []struct{
        i int
        b bool
    }{
        {2,true},
        {5,false},
        {3,true}, // 注意 ","结尾
    }
    fmt.Println(s)
    // [{2 true} {5 false} {3 true}]
}

Slice defaults

// 使用slice时,如果省略高位或低位的值,会采用默认值,低位默认使用0,高位默认使用length
func main(){
    s := []int{1,2,3,4,5,6}
    
    s = s[1:4]
    fmt.Println(s) // [2 3 4]
    
    s = s[:2]
    fmt.Println(s) // [2 3]
    
    s = s[1:]
    fmt.Println(s) // [3]
}

Slice length and capacity

// slice 具有 length 和 capacity 属性
// length 表示slice中包含的元素个数
// capacity 表示底层数组中,从slice中第一个元素开始计算的个数
// slice s的 length 和 capacity 可以通过表达式 len(s) 和 cap(s) 来获得
// 如果 slice 有足够的容量,可以通过重新切片来延长切片的长度
package main
import "fmt"

func printSlice(s []int) {
    fmt.Printf("len=%d,cap=%d,v=%v",len(s),cap(s),s)
}
func main(){
    s := []int{1,2,3,4,5,6,7}
    printSlice(s) // 6,6,[1,2,3,4,5,6]
    
    s = s[:0]
    printSlice(s) // 0,6,[]
    
    s = s[:4]
    printSlice(s) // 4,6,[1,2,3,4]
    
    s = s[2:]
    printSlice(s) // 2,4,[3,4]
}

Nil slices

// slice 的默认值是 nil
// 空slice 的 length 和 capacity 都是0,并且没有底层数组

func main(){
    var s []int
    fmt.Println(s, len(s), cap(s)) // [],0,0
    if s == nil {
        fmt.Prinlt("nil") // nil
    }
}

Creating a slice with make

// slice 可以通过 make 函数创建,这就是创建动态数组(arrays) 的方法
// make 函数分配一个全为0值的数组,然后返回一个指向这个数组的slice
a := make([]int, 5) // len(a) = 5
// 可以通过 make 中的第三个参数指定 capacity
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

Slices of slices

// slice 可以包含任意类型,包括其他slice
import (
	"fmt"
    "strings"
)

func main(){
    board := [][]string{
        []string{"_","_","_"},
        []string{"_","_","_"},
        []string{"_","_","_"}, // 注意","不能省略
    }
    
    board[0][0] = "X"
	board[2][2] = "O"
	board[1][2] = "X"
	board[1][0] = "O"
	board[0][2] = "X"
    
    for i := 0; i < len(board); i++ {
		fmt.Printf("%s\n", strings.Join(board[i], " "))
	}
    // X _ X
	// O _ X
	// _ _ O
}

Appending to a Slice

// go 中通过函数 append向 slice中添加元素
func append(s []T, vs ...T) []T
// append 的第一个参数是一个类型为T的 slice,其余的附加参数都是要加到这个 slice 上的
// append的结果是一个包含了原始slice中所有元素和提供的元素的slice
// 如果 s 的后备数组太小不足以填充所有的数据,那么将会被分配一个更大的数组,返回的 slice将会指向这个新分配的数组

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

	// append works on nil slices.
	s = append(s, 0)
	printSlice(s) // 1 1 [0]

	// The slice grows as needed.
	s = append(s, 1)
	printSlice(s) // 2 2 [0 1]

	// We can add more than one element at a time.
	s = append(s, 2, 3, 4)
	printSlice(s) // 5 6 [0 1 2 3 4]
}

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

Range

// for循环的 range 在slice和map上迭代
// 当range覆盖 slice时,会返回两个值,第一个是当前的索引,第二个是当前索引对应的值的副本(copy)

var pow = []int{11,22,33,44}

func main(){
    for i,v := range pow {
        fmt.Printf("idx---%d,val_copy---%d\n",i,v)
    }
}
// idx---0,val_copy---11
// idx---1,val_copy---22
// idx---2,val_copy---33
// idx---3,val_copy---44

Range continued

// 可以通过分配 "_" 来跳过index或value
for i, _ := range pow
for _, v := range pow
// 如果只需要index,可以省略第二个变量
for i := range pow

// --------------------------------------
func main(){
    pow := make([]int, 10)
    // 忽略第二个参数,i表示下标
    for i := range pow {
        pow[i] = 1<<uint(i) // == 2**i
    }
    // 不使用下标,可以用"_"符合跳过
    for _, value := range pow {
        fmt.printf("%d\n",value)
    }
}
// 打印:
1
2
4
8
16
32
64
128
256
512

Exercise: Slices

Implement Pic. It should return a slice of length dy, each element of which is a slice of dx 8-bit unsigned integers. When you run the program, it will display your picture, interpreting the integers as grayscale (well, bluescale) values.

The choice of image is up to you. Interesting functions include (x+y)/2, x*y, and x^y.

(You need to use a loop to allocate each []uint8 inside the [][]uint8.)

(Use uint8(intValue) to convert between types.)

package main

import "golang.org/x/tour/pic"

func Pic(dx, dy int) [][]uint8 {
	// 初始化一个二维数组
	picture := make([][]uint8,dy)
	for i:=0;i<dy;i++ {
		// 初始化一个一维数组
		p := make([]uint8,dx)
		for j:=0; j<dx;j++ {
			uin := (i+j)/2
			// 一维数组赋值
			p[j] = uint8(uin)
		}
		// 二维数组赋值
		picture[i] = p
	}
	// 随便再赋一个值
	picture[0][0] = uint8(dx*dy)
	return picture;
}

func main() {
    // 调用函数绘图
	pic.Show(Pic)
}

Maps

// map的零值是nil, nil 的 map没有键(key),也不能添加键

package main

import "fmt"
// 定义一个结构体
type Vertex struct {
	Lat, Long float64
}
// 定义一个 map[key]value,默认是nil,没有建也不能添加键
var m map[string]Vertex

func main() {
    // make函数会初始化一个给定类型的map
	m = make(map[string]Vertex)
    // 如果没有上一步重新给 m 赋值,那么这一步就会报错
	m["Bell Labs"] = Vertex{
		40.68433, -74.39967,
	}
    // 打印 Bell Labs 对应的value
	fmt.Println(m["Bell Labs"])
    // {40.68433 -74.39967}
}

Map literals

// Map literals are like struct literals, but the keys are required.
import "fmt"

type Vertex struct {
	Lat, Long float64
}

// 初始化一个map
var m = map[string]Vertex{
	"Bell Labs": Vertex{
		40.68433, -74.39967,
	},
	"Google": Vertex{
		37.42202, -122.08408,
	},
}

func main() {
    // 打印map
	fmt.Println(m)
    // map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
}

Map literals continued

// 如果顶级类型只是一个类型名,则可以从文本的元素中省略它。
import "fmt"

type Vertex struct {
	Lat, Long float64
}

// value 中可以省略 "Vertex"
var m = map[string]Vertex{
	"Bell Labs": {40.68433, -74.39967},
	"Google":    {37.42202, -122.08408},
}

func main() {
	fmt.Println(m)
}

Mutating Maps

// 插入或更新map
m[key] = elem
// 取map中的元素
elem = m[key]
// 删除map中的元素
delete(m,key)
// 测试键是否存在,返回两个值
elem, ok = m[key]
// 如果上面的key在m中,ok的值为true,否则为false
// 如果上面的key不在m中,elem是一个其类型的零值
// 如果上面的 elem ok 都没有声明,可以剪短的声明: 
// elem, ok := m[key]

import "fmt"

func main() {
    // 初始化map
	m := make(map[string]int)
	// map赋值
	m["Answer"] = 42
	fmt.Println("The value:", m["Answer"])// 42
	// 重新赋值
	m["Answer"] = 48
	fmt.Println("The value:", m["Answer"])// 48
	// 删除key
	delete(m, "Answer")
	fmt.Println("The value:", m["Answer"])// 0
    // v:map中key对应的value,ok:是否存在这个key
	v, ok := m["Answer"]
	fmt.Println("The value:", v, "Present?", ok)
    // 0 false
}

Exercise: Maps

Implement WordCount. It should return a map of the counts of each “word” in the string s. The wc.Test function runs a test suite against the provided function and prints success or failure.

You might find strings.Fields helpful.

package main

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

func WordCount(s string) map[string]int {
	mapCount := make(map[string]int)

	strs := strings.Split(s," ")
	for i := 0; i < len(strs); i++ {
		v, ok := mapCount[strs[i]]
		if ok {
			mapCount[strs[i]] = v + 1
		} else {
			mapCount[strs[i]] = 1
		}
	}
	return mapCount
}

func main() {
	wc.Test(WordCount)
}

Function values

// 函数也可以作为值来传递
// 函数类型的值可以用来作为函数的参数和返回值
package main

import (
	"fmt"
	"math"
)
// 定义一个函数 compute,里面有一个fn类型的函数型参数,接受一个func(float64, float64)参数,float64类型返回值的函数
// 内部调用了这个传入的函数,并return这个函数的返回值
func compute(fn func(float64, float64) float64) float64 {
	return fn(3, 4)
}

func main() {
    // 定义一个函数类型的变量hypot
	hypot := func(x, y float64) float64 {
		return math.Sqrt(x*x + y*y)
	}
    // 直接传入变量(函数)
	fmt.Println(hypot(5, 12))

    // 打印compute函数值,参数为一个变量(函数)
	fmt.Println(compute(hypot))
    // 打印compute函数值,参数为math.Pow函数
	fmt.Println(compute(math.Pow))
}

Function closures

Go函数可以是闭包(Closure)。闭包是一个函数值,它从函数体外部引用变量。函数可以访问和赋值被引用的变量;在这个意义上,函数是“绑定”到变量上的。
例如,adder函数返回一个闭包。每个闭包都绑定到自己的sum变量。

package main

import "fmt"

func adder() func(int) int {
	sum := 0 // 这个sum被闭包关联,会一直变化
	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),
		)
	}
    fmt.Println(pos(1))// 注意这个输出,函数的中的sum的值被闭包关联一直在变化,即使重新输入参数1,sum也不会从0开始计算
}
// 打印:
0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90
46

Exercise:Fibonacci closure

Let's have some fun with functions.

Implement a fibonacci function that returns a function (a closure) that returns successive fibonacci numbers (0, 1, 1, 2, 3, 5, ...).

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
	pre := -1 // 闭包中关联的局部变量会一直变化
	next := 1 // 闭包中关联的局部变量会一直变化
	return func() int{
		sum := pre + next // 计算相加的结果
		pre = next // 更新 pre的值
		next = sum // 更新 next的值
		return sum
	}
}

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

Methods and Interfaces

Methods

// Go没有class概念,但是可以在类型上定义方法
// 方法是带有特殊接收器的函数,注意与函数的区别,多了一个接收器(在Java的角度来说这个接收器可以看成Class,但又有点不同,可以自行感受下)
// 接受器写在func关键字和方法名之间
// 如下:Abs方法有一个Vertex类型的接收器,在方法中用v表示
package main

import (
	"fmt"
    "math"
)

// 定义一个struct
type Vertex struct {
    X, Y float64
}

// 可以看成只有 Vertex类型才能调用这个方法
func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y * v.Y)
}

func main(){
    // 初始化一个Vertex
    v := Vertex{3,4}
    // 调用 Vertex的方法Abs
    fmt.Println(v.Abs()) // 5
}

Methods are functions

// 请记住:方法就是一个带了接收器参数的函数
// 这里是将 Abs写成一个常规的函数,功能上没有变化
package main

import (
	"fmt"
	"math"
)

type Vertex struct {
	X, Y float64
}
// 定义 Abs函数,所有人都能调用
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)) // 5
}

Methods continued

// 也可以在没有结构的类型中声明方法
// 下面的例子展示了在数字类型 MyFloat中使用 Abs方法
// 注意方法的接收器必须和方法在同一个包下才可以声明该方法
package main

import (
	"fmt"
	"math"
)

type MyFloat float64

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

func main() {
	f := MyFloat(-math.Sqrt2)
	fmt.Println(f.Abs())
}

// 待续 ... ...

推荐阅读