# 10.7 类型的 String() 方法和格式化描述符

当定义了一个有很多方法的类型时，十之八九你会使用 `String()` 方法来定制类型的字符串形式的输出，换句话说：一种可阅读性和打印性的输出。如果类型定义了 `String()` 方法，它会被用在 `fmt.Printf()` 中生成默认的输出：等同于使用格式化描述符 `%v` 产生的输出。还有 `fmt.Print()` 和 `fmt.Println()` 也会自动使用 `String()` 方法。

我们使用第 10.4 节中程序的类型来进行测试：

示例 10.22 method_string.go：

```go
package main

import (
	"fmt"
	"strconv"
)

type TwoInts struct {
	a int
	b int
}

func main() {
	two1 := new(TwoInts)
	two1.a = 12
	two1.b = 10
	fmt.Printf("two1 is: %v\n", two1)
	fmt.Println("two1 is:", two1)
	fmt.Printf("two1 is: %T\n", two1)
	fmt.Printf("two1 is: %#v\n", two1)
}

func (tn *TwoInts) String() string {
	return "(" + strconv.Itoa(tn.a) + "/" + strconv.Itoa(tn.b) + ")"
}
```

输出：

    two1 is: (12/10)
    two1 is: (12/10)
    two1 is: *main.TwoInts
    two1 is: &main.TwoInts{a:12, b:10}

当你广泛使用一个自定义类型时，最好为它定义 `String()`方法。从上面的例子也可以看到，格式化描述符 `%T` 会给出类型的完全规格，`%#v` 会给出实例的完整输出，包括它的字段（在程序自动生成 `Go` 代码时也很有用）。

**备注**

不要在 `String()` 方法里面调用涉及 `String()` 方法的方法，它会导致意料之外的错误，比如下面的例子，它导致了一个无限递归调用（`TT.String()` 调用 `fmt.Sprintf`，而 `fmt.Sprintf` 又会反过来调用 `TT.String()`...），很快就会导致内存溢出：

```go
type TT float64

func (t TT) String() string {
    return fmt.Sprintf("%v", t)
}
t.String()
```

**练习 10.12** type_string.go

给定结构体类型 T:

```go
type T struct {
    a int
    b float32
    c string
}
```

值 `t`: `t := &T{7, -2.35, "abc\tdef"}`。给 T 定义 `String()`，使得 `fmt.Printf("%v\n", t)` 输出：`7 / -2.350000 / "abc\tdef"`。

**练习 10.13** celsius.go

为 float64 定义一个别名类型 `Celsius`，并给它定义 `String()`，它输出一个十进制数和 °C 表示的温度值。

**练习 10.14** days.go

为 int 定义一个别名类型 `Day`，定义一个字符串数组它包含一周七天的名字，为类型 `Day` 定义 `String()` 方法，它输出星期几的名字。使用 `iota` 定义一个枚举常量用于表示一周的中每天（MO、TU...）。

**练习 10.15** timezones.go

为 int 定义别名类型 `TZ`，定义一些常量表示时区，比如 UTC，定义一个 map，它将时区的缩写映射为它的全称，比如：`UTC -> "Universal Greenwich time"`。为类型 `TZ` 定义 `String()` 方法，它输出时区的全称。

**练习 10.16** stack_arr.go/stack_struct.go

实现栈（stack）数据结构：

![](images/10.7_fig.jpg?raw=true)

它的格子包含数据，比如整数 i、j、k 和 l 等等，格子从底部（索引 0）至顶部（索引 n）来索引。这个例子中假定 `n=3`，那么一共有 4 个格子。

一个新栈中所有格子的值都是 0。

push 将一个新值放到栈的最顶部一个非空（非零）的格子中。

pop 获取栈的最顶部一个非空（非零）的格子的值。现在可以理解为什么栈是一个后进先出（LIFO）的结构了吧。

为栈定义一 `Stack` 类型，并为它定义一个 `Push` 和 `Pop` 方法，再为它定义 `String()` 方法（用于调试）它输出栈的内容，比如：`[0:i] [1:j] [2:k] [3:l]`。

1）stack_arr.go：使用长度为 4 的 int 数组作为底层数据结构。

2）stack_struct.go：使用包含一个索引和一个 int 数组的结构体作为底层数据结构，索引表示第一个空闲的位置。

3）使用常量 LIMIT 代替上面表示元素个数的 4 重新实现上面的 1）和 2），使它们更具有一般性。

## 链接

- [目录](directory.md)
- 上一节：[方法](10.6.md)
- 下一节：[垃圾回收和 SetFinalizer](10.8.md)
