以下资源来自A Tour of Go。
答案自己完成或参考完成
Go does not have classes. However, you can define methods on types.
A method is a function with a special receiver argument.
The receiver appears in its own argument list between the func
keyword and the method name.
In this example, the Abs
method has a receiver of type Vertex
named v
.
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())
}
Remember: a method is just a function with a receiver argument.
Here’s Abs
written as a regular function with no change in functionality.
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))
}
You can declare a method on non-struct types, too.
In this example we see a numeric type MyFloat with an Abs method.
You can only declare a method with a receiver whose type is defined in the same package as the method. You cannot declare a method with a receiver whose type is defined in another package (which includes the built-in types such as int).
只能声明返回值类型与方法再同一个包中的方法。
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())
}
Here we see the Abs
and Scale
methods rewritten as functions.
Again, try removing the *
from line 16. Can you see why the behavior changes? What else did you need to change for the example to compile?
此处和c语言是一样的,因为函数传值的问题。
(If you’re not sure, continue to the next page.)
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 Scale(v *Vertex, f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
Scale(&v, 10)
fmt.Println(Abs(v))
}
Comparing the previous two programs, you might notice that functions with a pointer argument must take a pointer:
var v Vertex
ScaleFunc(v, 5) // Compile error!
ScaleFunc(&v, 5) // OK
while methods with pointer receivers take either a value or a pointer as the receiver when they are called:
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK
For the statement v.Scale(5)
, even though v
is a value and not a pointer, the method with the pointer receiver is called automatically. That is, as a convenience, Go interprets the statement v.Scale(5)
as (&v).Scale(5)
since the Scale
method has a pointer receiver.
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)
}
The equivalent thing happens in the reverse direction.
Functions that take a value argument must take a value of that specific type:
var v Vertex
fmt.Println(AbsFunc(v)) // OK
fmt.Println(AbsFunc(&v)) // Compile error!
while methods with value receivers take either a value or a pointer as the receiver when they are called:
var v Vertex
fmt.Println(v.Abs()) // OK
p := &v
fmt.Println(p.Abs()) // OK
In this case, the method call p.Abs()
is interpreted as (*p).Abs()
.
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 AbsFunc(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
fmt.Println(AbsFunc(v))
p := &Vertex{4, 3}
fmt.Println(p.Abs())
fmt.Println(AbsFunc(*p))
}
There are two reasons to use a pointer receiver.
The first is so that the method can modify the value that its receiver points to.
The second is to avoid copying the value on each method call. This can be more efficient if the receiver is a large struct, for example.
In this example, both Scale
and Abs
are with receiver type *Vertex
, even though the Abs
method needn’t modify its receiver.
In general, all methods on a given type should have either value or pointer receivers, but not a mixture of both. (We’ll see why over the next few pages.)
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := &Vertex{3, 4}
fmt.Printf("Before scaling: %+v, Abs: %v\n", v, v.Abs())
v.Scale(5)
fmt.Printf("After scaling: %+v, Abs: %v\n", v, v.Abs())
}
An interface type is defined as a set of method signatures.
A value of interface type can hold any value that implements those methods.
Note: There is an error in the example code on line 22. Vertex
(the value type) doesn’t implement Abser
because the Abs
method is defined only on *Vertex
(the pointer type).
package main
import (
"fmt"
"math"
)
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat implements Abser
a = &v // a *Vertex implements Abser
// In the following line, v is a Vertex (not *Vertex)
// and does NOT implement Abser.
a = v
fmt.Println(a.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)
}
A type implements an interface by implementing its methods. There is no explicit declaration of intent, no “implements” keyword.
Implicit interfaces decouple the definition of an interface from its implementation, which could then appear in any package without prearrangement.
package main
import "fmt"
type I interface {
M()
}
type T struct {
S string
}
// This method means type T implements the interface I,
// but we don't need to explicitly declare that it does so.
func (t T) M() {
fmt.Println(t.S)
}
func main() {
var i I = T{"hello"}
i.M()
}
Under the hood, interface values can be thought of as a tuple of a value and a concrete type:
(value, type)
An interface value holds a value of a specific underlying concrete type.
Calling a method on an interface value executes the method of the same name on its underlying type.
package main
import (
"fmt"
"math"
)
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M() {
fmt.Println(t.S)
}
type F float64
func (f F) M() {
fmt.Println(f)
}
func main() {
var i I
i = &T{"Hello"}
describe(i)
i.M()
i = F(math.Pi)
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
If the concrete value inside the interface itself is nil, the method will be called with a nil receiver.
In some languages this would trigger a null pointer exception, but in Go it is common to write methods that gracefully handle being called with a nil receiver (as with the method M in this example.)
Note that an interface value that holds a nil concrete value is itself non-nil.
package main
import "fmt"
type I interface {
M()
}
type T struct {
S string
}
func (t *T) M() {
if t == nil {
fmt.Println("" )
return
}
fmt.Println(t.S)
}
func main() {
var i I
var t *T
i = t
describe(i)
i.M()
i = &T{"hello"}
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
A nil interface value holds neither value nor concrete type.
Calling a method on a nil interface is a run-time error because there is no type inside the interface tuple to indicate which concrete method to call.
package main
import "fmt"
type I interface {
M()
}
func main() {
var i I
describe(i)
i.M()
}
func describe(i I) {
fmt.Printf("(%v, %T)\n", i, i)
}
The interface type that specifies zero methods is known as the empty interface:
interface{}
An empty interface may hold values of any type. (Every type implements at least zero methods.)
Empty interfaces are used by code that handles values of unknown type. For example, fmt.Print takes any number of arguments of type interface{}.
package main
import "fmt"
func main() {
var i interface{}
describe(i)
i = 42
describe(i)
i = "hello"
describe(i)
}
func describe(i interface{}) {
fmt.Printf("(%v, %T)\n", i, i)
}
A type assertion provides access to an interface value’s underlying concrete value.
t := i.(T)
This statement asserts that the interface value i holds the concrete type T and assigns the underlying T value to the variable t.
If i does not hold a T, the statement will trigger a panic.
To test whether an interface value holds a specific type, a type assertion can return two values: the underlying value and a boolean value that reports whether the assertion succeeded.
t, ok := i.(T)
If i holds a T
, then t
will be the underlying value and ok
will be true.
If not, ok
will be false and t
will be the zero value of type T
, and no panic occurs.
Note the similarity between this syntax and that of reading from a map.
package main
import "fmt"
func main() {
var i interface{} = "hello"
s := i.(string)
fmt.Println(s)
s, ok := i.(string)
fmt.Println(s, ok)
f, ok := i.(float64)
fmt.Println(f, ok)
f = i.(float64) // panic
fmt.Println(f)
}
A type switch is a construct that permits several type assertions in series.
A type switch is like a regular switch statement, but the cases in a type switch specify types (not values), and those values are compared against the type of the value held by the given interface value.
switch v := i.(type) {
case T:
// here v has type T
case S:
// here v has type S
default:
// no match; here v has the same type as i
}
The declaration in a type switch has the same syntax as a type assertion i.(T), but the specific type T is replaced with the keyword type.
This switch statement tests whether the interface value i holds a value of type T or S. In each of the T and S cases, the variable v will be of type T or S respectively and hold the value held by i. In the default case (where there is no match), the variable v is of the same interface type and value as i.
package main
import "fmt"
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
func main() {
do(21)
do("hello")
do(true)
}
One of the most ubiquitous interfaces is Stringer defined by the fmt package.
type Stringer interface {
String() string
}
A Stringer is a type that can describe itself as a string. The fmt package (and many others) look for this interface to print values.
package main
import "fmt"
type Person struct {
Name string
Age int
}
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
func main() {
a := Person{"Arthur Dent", 42}
z := Person{"Zaphod Beeblebrox", 9001}
fmt.Println(a, z)
}
输出为:Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)
很神奇,在格式化输入输出的时候很有用。
Make the IPAddr type implement fmt.Stringer to print the address as a dotted quad.
For instance, IPAddr{1, 2, 3, 4} should print as “1.2.3.4”.
package main
import "fmt"
type IPAddr [4]byte
func (p IPAddr) String() string {
return fmt.Sprintf("%v.%v.%v.%v", p[0], p[1], p[2], p[3])
}
// TODO: Add a "String() string" method to IPAddr.
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)
}
}
Go programs express error state with error
values.
The error
type is a built-in interface similar to fmt.Stringer
:
type error interface {
Error() string
}
(As with fmt.Stringer
, the fmt
package looks for the error
interface when printing values.)
Functions often return an error
value, and calling code should handle errors by testing whether the error equals nil
.
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
return
}
fmt.Println("Converted integer:", i)
A nil error
denotes success; a non-nil error
denotes failure.
package main
import (
"fmt"
"time"
)
type MyError struct {
When time.Time
What string
}
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s",
e.When, e.What)
}
func run() error {
return &MyError{
time.Now(),
"it didn't work",
}
}
func main() {
if err := run(); err != nil {
fmt.Println(err)
}
}
Copy your Sqrt
function from the earlier exercise and modify it to return an error
value.
Sqrt
should return a non-nil error value when given a negative number, as it doesn’t support complex numbers.
Create a new type
type ErrNegativeSqrt float64
and make it an error
by giving it a
func (e ErrNegativeSqrt) Error() string
method such that ErrNegativeSqrt(-2).Error()
returns "cannot Sqrt negative number: -2"
.
Note: A call to fmt.Sprint(e)
inside the Error
method will send the program into an infinite loop. You can avoid this by converting e
first: fmt.Sprint(float64(e)). Why?
Change your Sqrt
function to return an ErrNegativeSqrt
value when given a negative number.
package main
import (
"fmt"
"math"
)
type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string{
return fmt.Sprintf("cannot Sqrt negative number: %v",
float64(e))
}
func Sqrt(x float64) (float64, error) {
if x < 0 {
return 0, ErrNegativeSqrt(x)
}
return math.Sqrt(x), nil
}
func main() {
fmt.Println(Sqrt(2))
fmt.Println(Sqrt(-2))
}
The io
package specifies the io.Reader
interface, which represents the read end of a stream of data.
The Go standard library contains many implementations of these interfaces, including files, network connections, compressors, ciphers, and others.
The io.Reader
interface has a Read method:
func (T) Read(b []byte) (n int, err error)
Read
populates the given byte slice with data and returns the number of bytes populated and an error value. It returns an io.EOF
error when the stream ends.
The example code creates a strings.Reader
and consumes its output 8 bytes at a time.
package main
import (
"fmt"
"io"
"strings"
)
func main() {
r := strings.NewReader("Hello, Reader!")
b := make([]byte, 8)
for {
n, err := r.Read(b)
fmt.Printf("n = %v err = %v b = %v\n", n, err, b)
fmt.Printf("b[:n] = %q\n", b[:n])
if err == io.EOF {
break
}
}
}
Implement a Reader type that emits an infinite stream of the ASCII character ‘A’.
package main
import "golang.org/x/tour/reader"
type MyReader struct{}
// TODO: Add a Read([]byte) (int, error) method to MyReader.
func (r MyReader) Read(b []byte) (int, error) {
// 赋值并返回
b[0] = 'A'
return 1, nil
}
func main() {
reader.Validate(MyReader{})
}
A common pattern is an io.Reader that wraps another io.Reader
, modifying the stream in some way.
For example, the gzip.NewReader function takes an io.Reader
(a stream of compressed data) and returns a *gzip.Reader
that also implements io.Reader
(a stream of the decompressed data).
Implement a rot13Reader
that implements io.Reader
and reads from an io.Reader
, modifying the stream by applying the rot13 substitution cipher to all alphabetical characters.
The rot13Reader
type is provided for you. Make it an io.Reader
by implementing its Read method.
package main
import (
"io"
"os"
"strings"
)
type rot13Reader struct {
r io.Reader
}
func rot13(b byte) byte {
switch {
case 'A' <= b && b <= 'M':
b = b + 13
case 'M' < b && b <= 'Z':
b = b - 13
case 'a' <= b && b <= 'm':
b = b + 13
case 'm' < b && b <= 'z':
b = b - 13
}
return b
}
func (mr rot13Reader) Read(b []byte) (int, error) {
n, e := mr.r.Read(b)
for i := 0; i < n; i++ {
b[i] = rot13(b[i])
}
return n, e
}
func main() {
s := strings.NewReader("Lbh penpxrq gur pbqr!")
r := rot13Reader{s}
io.Copy(os.Stdout, &r)
}
Package image defines the Image
interface:
package image
type Image interface {
ColorModel() color.Model
Bounds() Rectangle
At(x, y int) color.Color
}
Note: the Rectangle
return value of the Bounds
method is actually an image.Rectangle
, as the declaration is inside package image
.
(See the documentation for all the details.)
The color.Color
and color.Model
types are also interfaces, but we’ll ignore that by using the predefined implementations color.RGBA
and color.RGBAModel
. These interfaces and types are specified by the image/color package
package main
import (
"fmt"
"image"
)
func main() {
m := image.NewRGBA(image.Rect(0, 0, 100, 100))
fmt.Println(m.Bounds())
fmt.Println(m.At(0, 0).RGBA())
}
Remember the picture generator you wrote earlier? Let’s write another one, but this time it will return an implementation of image.Image
instead of a slice of data.
Define your own Image
type, implement the necessary methods, and call pic.ShowImage
.
Bounds
should return a image.Rectangle
, like image.Rect(0, 0, w, h)
.
ColorModel
should return color.RGBAModel
.
At
should return a color; the value v
in the last picture generator corresponds to color.RGBA{v, v, 255, 255}
in this one.
package main
import (
"golang.org/x/tour/pic"
"image"
"image/color"
)
type Image struct{
W int
H int
}
func (i Image) Bounds() image.Rectangle {
return image.Rect(0, 0, i.W, i.H)
}
func (i Image) ColorModel() color.Model {
return color.RGBAModel
}
func (self Image) At(x, y int) color.Color {
return color.RGBA{uint8(x), uint8(y), 255, 255}
}
func main() {
m := Image{300, 300}
pic.ShowImage(m)
}