mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-12-24 22:08:57 +00:00
change escape of ch7
This commit is contained in:
parent
9c768334f2
commit
3da62d2c3f
@ -20,7 +20,7 @@ func Sprintf(format string, args ...interface{}) string {
|
||||
}
|
||||
```
|
||||
|
||||
Fprintf的前缀F表示文件(File)也表明格式化输出结果应该被写入第一个参数提供的文件中。在Printf函数中的第一个参数os.Stdout是*os.File类型;在Sprintf函数中的第一个参数&buf是一个指向可以写入字节的内存缓冲区,然而它
|
||||
Fprintf的前缀F表示文件(File)也表明格式化输出结果应该被写入第一个参数提供的文件中。在Printf函数中的第一个参数os.Stdout是`*os.File`类型;在Sprintf函数中的第一个参数&buf是一个指向可以写入字节的内存缓冲区,然而它
|
||||
并不是一个文件类型尽管它在某种意义上和文件类型相似。
|
||||
|
||||
即使Fprintf函数中的第一个参数也不是一个文件类型。它是io.Writer类型这是一个接口类型定义如下:
|
||||
@ -41,11 +41,11 @@ type Writer interface {
|
||||
}
|
||||
```
|
||||
|
||||
io.Writer类型定义了函数Fprintf和这个函数调用者之间的约定。一方面这个约定需要调用者提供具体类型的值就像\*os.File和\*bytes.Buffer,这些类型都有一个特定签名和行为的Write的函数。另一方面这个约定保证了Fprintf接受任何满足io.Writer接口的值都可以工作。Fprintf函数可能没有假定写入的是一个文件或是一段内存,而是写入一个可以调用Write函数的值。
|
||||
io.Writer类型定义了函数Fprintf和这个函数调用者之间的约定。一方面这个约定需要调用者提供具体类型的值就像`*os.File`和`*bytes.Buffer`,这些类型都有一个特定签名和行为的Write的函数。另一方面这个约定保证了Fprintf接受任何满足io.Writer接口的值都可以工作。Fprintf函数可能没有假定写入的是一个文件或是一段内存,而是写入一个可以调用Write函数的值。
|
||||
|
||||
因为fmt.Fprintf函数没有对具体操作的值做任何假设而是仅仅通过io.Writer接口的约定来保证行为,所以第一个参数可以安全地传入一个任何具体类型的值只需要满足io.Writer接口。一个类型可以自由的使用另一个满足相同接口的类型来进行替换被称作可替换性(LSP里氏替换)。这是一个面向对象的特征。
|
||||
|
||||
让我们通过一个新的类型来进行校验,下面\*ByteCounter类型里的Write方法,仅仅在丢失写向它的字节前统计它们的长度。(在这个+=赋值语句中,让len(p)的类型和\*c的类型匹配的转换是必须的。)
|
||||
让我们通过一个新的类型来进行校验,下面`*ByteCounter`类型里的Write方法,仅仅在丢失写向它的字节前统计它们的长度。(在这个+=赋值语句中,让len(p)的类型和`*c`的类型匹配的转换是必须的。)
|
||||
|
||||
<u><i>gopl.io/ch7/bytecounter</i></u>
|
||||
```go
|
||||
|
@ -74,7 +74,7 @@ func (f *celsiusFlag) Set(s string) error {
|
||||
|
||||
调用fmt.Sscanf函数从输入s中解析一个浮点数(value)和一个字符串(unit)。虽然通常必须检查Sscanf的错误返回,但是在这个例子中我们不需要因为如果有错误发生,就没有switch case会匹配到。
|
||||
|
||||
下面的CelsiusFlag函数将所有逻辑都封装在一起。它返回一个内嵌在celsiusFlag变量f中的Celsius指针给调用者。Celsius字段是一个会通过Set方法在标记处理的过程中更新的变量。调用Var方法将标记加入应用的命令行标记集合中,有异常复杂命令行接口的全局变量flag.CommandLine.Programs可能有几个这个类型的变量。调用Var方法将一个*celsiusFlag参数赋值给一个flag.Value参数,导致编译器去检查*celsiusFlag是否有必须的方法。
|
||||
下面的CelsiusFlag函数将所有逻辑都封装在一起。它返回一个内嵌在celsiusFlag变量f中的Celsius指针给调用者。Celsius字段是一个会通过Set方法在标记处理的过程中更新的变量。调用Var方法将标记加入应用的命令行标记集合中,有异常复杂命令行接口的全局变量flag.CommandLine.Programs可能有几个这个类型的变量。调用Var方法将一个`*celsiusFlag`参数赋值给一个flag.Value参数,导致编译器去检查`*celsiusFlag`是否有必须的方法。
|
||||
|
||||
```go
|
||||
// CelsiusFlag defines a Celsius flag with the specified name,
|
||||
|
@ -26,17 +26,17 @@ var w io.Writer
|
||||
w.Write([]byte("hello")) // panic: nil pointer dereference
|
||||
```
|
||||
|
||||
第二个语句将一个*os.File类型的值赋给变量w:
|
||||
第二个语句将一个`*os.File`类型的值赋给变量w:
|
||||
|
||||
```go
|
||||
w = os.Stdout
|
||||
```
|
||||
|
||||
这个赋值过程调用了一个具体类型到接口类型的隐式转换,这和显式的使用io.Writer(os.Stdout)是等价的。这类转换不管是显式的还是隐式的,都会刻画出操作到的类型和值。这个接口值的动态类型被设为*os.Stdout指针的类型描述符,它的动态值持有os.Stdout的拷贝;这是一个代表处理标准输出的os.File类型变量的指针(图7.2)。
|
||||
这个赋值过程调用了一个具体类型到接口类型的隐式转换,这和显式的使用io.Writer(os.Stdout)是等价的。这类转换不管是显式的还是隐式的,都会刻画出操作到的类型和值。这个接口值的动态类型被设为`*os.Stdout`指针的类型描述符,它的动态值持有os.Stdout的拷贝;这是一个代表处理标准输出的os.File类型变量的指针(图7.2)。
|
||||
|
||||
![](../images/ch7-02.png)
|
||||
|
||||
调用一个包含\*os.File类型指针的接口值的Write方法,使得(\*os.File).Write方法被调用。这个调用输出“hello”。
|
||||
调用一个包含`*os.File`类型指针的接口值的Write方法,使得`(*os.File).Write`方法被调用。这个调用输出“hello”。
|
||||
|
||||
```go
|
||||
w.Write([]byte("hello")) // "hello"
|
||||
|
@ -66,7 +66,7 @@ func length(s string) time.Duration {
|
||||
}
|
||||
```
|
||||
|
||||
printTracks函数将播放列表打印成一个表格。一个图形化的展示可能会更好点,但是这个小程序使用text/tabwriter包来生成一个列是整齐对齐和隔开的表格,像下面展示的这样。注意到*tabwriter.Writer是满足io.Writer接口的。它会收集每一片写向它的数据;它的Flush方法会格式化整个表格并且将它写向os.Stdout(标准输出)。
|
||||
printTracks函数将播放列表打印成一个表格。一个图形化的展示可能会更好点,但是这个小程序使用text/tabwriter包来生成一个列是整齐对齐和隔开的表格,像下面展示的这样。注意到`*tabwriter.Writer`是满足io.Writer接口的。它会收集每一片写向它的数据;它的Flush方法会格式化整个表格并且将它写向os.Stdout(标准输出)。
|
||||
|
||||
```go
|
||||
func printTracks(tracks []*Track) {
|
||||
|
@ -20,7 +20,7 @@ type errorString struct { text string }
|
||||
func (e *errorString) Error() string { return e.text }
|
||||
```
|
||||
|
||||
承载errorString的类型是一个结构体而非一个字符串,这是为了保护它表示的错误避免粗心(或有意)的更新。并且因为是指针类型*errorString满足error接口而非errorString类型,所以每个New函数的调用都分配了一个独特的和其他错误不相同的实例。我们也不想要重要的error例如io.EOF和一个刚好有相同错误消息的error比较后相等。
|
||||
承载errorString的类型是一个结构体而非一个字符串,这是为了保护它表示的错误避免粗心(或有意)的更新。并且因为是指针类型`*errorString`满足error接口而非errorString类型,所以每个New函数的调用都分配了一个独特的和其他错误不相同的实例。我们也不想要重要的error例如io.EOF和一个刚好有相同错误消息的error比较后相等。
|
||||
|
||||
```go
|
||||
fmt.Println(errors.New("EOF") == errors.New("EOF")) // "false"
|
||||
@ -38,7 +38,7 @@ func Errorf(format string, args ...interface{}) error {
|
||||
}
|
||||
```
|
||||
|
||||
虽然*errorString可能是最简单的错误类型,但远非只有它一个。例如,syscall包提供了Go语言底层系统调用API。在多个平台上,它定义一个实现error接口的数字类型Errno,并且在Unix平台上,Errno的Error方法会从一个字符串表中查找错误消息,如下面展示的这样:
|
||||
虽然`*errorString`可能是最简单的错误类型,但远非只有它一个。例如,syscall包提供了Go语言底层系统调用API。在多个平台上,它定义一个实现error接口的数字类型Errno,并且在Unix平台上,Errno的Error方法会从一个字符串表中查找错误消息,如下面展示的这样:
|
||||
|
||||
```go
|
||||
package syscall
|
||||
|
@ -13,7 +13,7 @@ c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer
|
||||
|
||||
第二种,如果相反断言的类型T是一个接口类型,然后类型断言检查是否x的动态类型满足T。如果这个检查成功了,动态值没有获取到;这个结果仍然是一个有相同类型和值部分的接口值,但是结果有类型T。换句话说,对一个接口类型的类型断言改变了类型的表述方式,改变了可以获取的方法集合(通常更大),但是它保护了接口值内部的动态类型和值的部分。
|
||||
|
||||
在下面的第一个类型断言后,w和rw都持有os.Stdout因此它们每个有一个动态类型*os.File,但是变量w是一个io.Writer类型只对外公开出文件的Write方法,然而rw变量也只公开它的Read方法。
|
||||
在下面的第一个类型断言后,w和rw都持有os.Stdout因此它们每个有一个动态类型`*os.File`,但是变量w是一个io.Writer类型只对外公开出文件的Write方法,然而rw变量也只公开它的Read方法。
|
||||
|
||||
```go
|
||||
var w io.Writer
|
||||
@ -38,7 +38,7 @@ f, ok := w.(*os.File) // success: ok, f == os.Stdout
|
||||
b, ok := w.(*bytes.Buffer) // failure: !ok, b == nil
|
||||
```
|
||||
|
||||
第二个结果常规地赋值给一个命名为ok的变量。如果这个操作失败了,那么ok就是false值,第一个结果等于被断言类型的零值,在这个例子中就是一个nil的*bytes.Buffer类型。
|
||||
第二个结果常规地赋值给一个命名为ok的变量。如果这个操作失败了,那么ok就是false值,第一个结果等于被断言类型的零值,在这个例子中就是一个nil的`*bytes.Buffer`类型。
|
||||
|
||||
这个ok结果经常立即用于决定程序下面做什么。if语句的扩展格式让这个变的很简洁:
|
||||
|
||||
|
@ -48,7 +48,7 @@ fmt.Printf("%#v\n", err)
|
||||
// &os.PathError{Op:"open", Path:"/no/such/file", Err:0x2}
|
||||
```
|
||||
|
||||
这就是三个帮助函数是怎么工作的。例如下面展示的IsNotExist,它会报出是否一个错误和syscall.ENOENT(§7.8)或者和有名的错误os.ErrNotExist相等(可以在§5.4.2中找到io.EOF);或者是一个*PathError,它内部的错误是syscall.ENOENT和os.ErrNotExist其中之一。
|
||||
这就是三个帮助函数是怎么工作的。例如下面展示的IsNotExist,它会报出是否一个错误和syscall.ENOENT(§7.8)或者和有名的错误os.ErrNotExist相等(可以在§5.4.2中找到io.EOF);或者是一个`*PathError`,它内部的错误是syscall.ENOENT和os.ErrNotExist其中之一。
|
||||
|
||||
```go
|
||||
import (
|
||||
|
@ -16,7 +16,7 @@ func writeHeader(w io.Writer, contentType string) error {
|
||||
|
||||
因为Write方法需要传入一个byte切片而我们希望写入的值是一个字符串,所以我们需要使用[]byte(...)进行转换。这个转换分配内存并且做一个拷贝,但是这个拷贝在转换后几乎立马就被丢弃掉。让我们假装这是一个web服务器的核心部分并且我们的性能分析表示这个内存分配使服务器的速度变慢。这里我们可以避免掉内存分配么?
|
||||
|
||||
这个io.Writer接口告诉我们关于w持有的具体类型的唯一东西:就是可以向它写入字节切片。如果我们回顾net/http包中的内幕,我们知道在这个程序中的w变量持有的动态类型也有一个允许字符串高效写入的WriteString方法;这个方法会避免去分配一个临时的拷贝。(这可能像在黑夜中射击一样,但是许多满足io.Writer接口的重要类型同时也有WriteString方法,包括\*bytes.Buffer,\*os.File和\*bufio.Writer。)
|
||||
这个io.Writer接口告诉我们关于w持有的具体类型的唯一东西:就是可以向它写入字节切片。如果我们回顾net/http包中的内幕,我们知道在这个程序中的w变量持有的动态类型也有一个允许字符串高效写入的WriteString方法;这个方法会避免去分配一个临时的拷贝。(这可能像在黑夜中射击一样,但是许多满足io.Writer接口的重要类型同时也有WriteString方法,包括`*bytes.Buffer`,`*os.File`和`*bufio.Writer`。)
|
||||
|
||||
我们不能对任意io.Writer类型的变量w,假设它也拥有WriteString方法。但是我们可以定义一个只有这个方法的新接口并且使用类型断言来检测是否w的动态类型满足这个新接口。
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user