diff --git a/chapter01/01.1.md b/chapter01/01.1.md index f2bf475..f606b52 100644 --- a/chapter01/01.1.md +++ b/chapter01/01.1.md @@ -76,7 +76,7 @@ type Writer interface { 在上个例子中,我们是自己实现一个函数接收一个 io.Reader 类型的参数。这里,我们通过标准库的例子来学习。 -在fmt标准库中,有一组函数:Fprint/Fprintf/Fprintln,它们接收一个 io.Wrtier 类型参数(第一个参数),也就是说它们将数据格式化输出到 io.Writer 中。那么,调用这组函数时,该如何传递这个参数呢? +在fmt标准库中,有一组函数:Fprint/Fprintf/Fprintln,它们接收一个 io.Writer 类型参数(第一个参数),也就是说它们将数据格式化输出到 io.Writer 中。那么,调用这组函数时,该如何传递这个参数呢? 我们以 fmt.Fprintln 为例,同时看一下 fmt.Println 函数的源码。 diff --git a/chapter01/01.3.md b/chapter01/01.3.md index ffda0c4..3fbb14a 100644 --- a/chapter01/01.3.md +++ b/chapter01/01.3.md @@ -211,7 +211,7 @@ Sprint/Sprintf/Sprintln 是格式化内容为 string 类型,而并不输出到 在这三组函数中,`S/F/Printf`函数通过指定的格式输出或格式化内容;`S/F/Print`函数只是使用默认的格式输出或格式化内容;`S/F/Println`函数使用默认的格式输出或格式化内容,同时会在最后加上"换行符"。 -Print 序列函数的最后一个参数都是 `a ...interface{}` 这种不定参数。对于`S/F/Printf`序列,这个不定参数的实参个数应该和`formt`参数的占位符个数一致,否则会出现格式化错误;而对于其他函数,当不定参数的实参个数为多个时,它们之间会直接(对于`S/F/Print`)或通过" "(空格)(对于`S/F/Println`)连接起来(注:对于`S/F/Print`,当两个参数都不是字符串时,会自动添加一个空格,否则不会加。感谢guoshanhe1983 反馈。[官方 effective_go](http://docs.studygolang.com/doc/effective_go.html#Printing) 也有说明)。利用这一点,我们可以做如下事情: +Print 序列函数的最后一个参数都是 `a ...interface{}` 这种不定参数。对于`S/F/Printf`序列,这个不定参数的实参个数应该和`format`参数的占位符个数一致,否则会出现格式化错误;而对于其他函数,当不定参数的实参个数为多个时,它们之间会直接(对于`S/F/Print`)或通过" "(空格)(对于`S/F/Println`)连接起来(注:对于`S/F/Print`,当两个参数都不是字符串时,会自动添加一个空格,否则不会加。感谢guoshanhe1983 反馈。[官方 effective_go](http://docs.studygolang.com/doc/effective_go.html#Printing) 也有说明)。利用这一点,我们可以做如下事情: result1 := fmt.Sprintln("studygolang.com", 2013) result2 := fmt.Sprint("studygolang.com", 2013) @@ -637,4 +637,4 @@ func (p *pp) printArg(arg interface{}, verb rune) { - [目录](/preface.md) - 上一节:[ioutil — 方便的IO操作函数集](01.2.md) -- 下一节:[bufio — 缓存IO](01.4.md) \ No newline at end of file +- 下一节:[bufio — 缓存IO](01.4.md) diff --git a/chapter01/01.4.md b/chapter01/01.4.md index 9386302..6757869 100644 --- a/chapter01/01.4.md +++ b/chapter01/01.4.md @@ -79,7 +79,7 @@ ReadSlice 从输入中读取,直到遇到第一个界定符(delim)为止 注意,这里的界定符可以是任意的字符,可以将上面代码中的'\n'改为'm'试试。同时,返回的结果是包含界定符本身的,上例中,输出结果有一空行就是'\n'本身(line携带一个'\n',printf又追加了一个'\n')。 -如果 ReadSlice 在找到界定符之前遇到了 error ,它就会返回缓存中所有的数据和错误本身(经常是 io.EOF)。如果在找到界定符之前缓存已经满了,ReadSlice 会返回 bufio.ErrBufferFull 错误。当且仅当返回的结果(line)没有以界定符结束的时候,ReadSlice 返回err != nil,也就是说,如果ReadSlice 返回的结果 line 不是以界定符 delim 结尾,那么返回的 er r也一定不等于 nil(可能是bufio.ErrBufferFull或io.EOF)。 +如果 ReadSlice 在找到界定符之前遇到了 error ,它就会返回缓存中所有的数据和错误本身(经常是 io.EOF)。如果在找到界定符之前缓存已经满了,ReadSlice 会返回 bufio.ErrBufferFull 错误。当且仅当返回的结果(line)没有以界定符结束的时候,ReadSlice 返回err != nil,也就是说,如果ReadSlice 返回的结果 line 不是以界定符 delim 结尾,那么返回的 err 也一定不等于 nil(可能是bufio.ErrBufferFull或io.EOF)。 例子代码: ```go reader := bufio.NewReaderSize(strings.NewReader("http://studygolang.com"),16) @@ -448,14 +448,14 @@ Writer 类型其他方法是一些实际的写方法: ## 1.4.4 ReadWriter 类型和实例化 ## ReadWriter 结构存储了 bufio.Reader 和 bufio.Writer 类型的指针(内嵌),它实现了 io.ReadWriter 结构。 -``` +```go type ReadWriter struct { *Reader *Writer } ``` ReadWriter 的实例化可以跟普通结构类型一样,也可以通过调用 bufio.NewReadWriter 函数来实现:只是简单的实例化 ReadWriter -``` +```go func NewReadWriter(r *Reader, w *Writer) *ReadWriter { return &ReadWriter{r, w} } @@ -464,4 +464,4 @@ ReadWriter 的实例化可以跟普通结构类型一样,也可以通过调用 - [目录](/preface.md) - 上一节:[fmt — 格式化IO](01.3.md) -- 下一节:[I/O 总结](01.5.md) +- 第二章:[文本](/chapter02/02.0.md) diff --git a/chapter02/02.1.md b/chapter02/02.1.md index 5773f72..108f087 100644 --- a/chapter02/02.1.md +++ b/chapter02/02.1.md @@ -293,7 +293,7 @@ func LastIndexAny(s, chars string) int func LastIndexFunc(s string, f func(rune) bool) int ``` -在 2.1.1 小节提到过,Contain 相关的函数内部调用的是响应的 Index 函数。 +在 2.1.1 小节提到过,Contain 相关的函数内部调用的是相应的 Index 函数。 这一序列函数,只举 IndexFunc 的例子: @@ -589,7 +589,7 @@ Hello, Gophers ## 2.1.12 Replacer 类型 ## -这是一个结构,没有导出任何字段,实例化通过 `func NewReplacer(oldnew ...string) *Replacer` 函数进行,其中不定参数 oldnew 是 old-new 对,即进行多个替换。如果 oldnew 长度与奇数,会导致 panic. +这是一个结构,没有导出任何字段,实例化通过 `func NewReplacer(oldnew ...string) *Replacer` 函数进行,其中不定参数 oldnew 是 old-new 对,即进行多个替换。如果 oldnew 长度为奇数,会导致 panic. 示例: ```go @@ -636,16 +636,16 @@ type Builder struct { buf []byte } ``` -该类型实现了 io 包下的 Writer, ByteWriter, StringWriter 等接口,可以向该对象内写入数据,Builder 没有实现 Reader 等接口,所以该类型不可读,但提供了 String 方法可以获取对象内的数据。 +该类型实现了 `io` 包下的 Writer, ByteWriter, StringWriter 等接口,可以向该对象内写入数据,Builder 没有实现 Reader 等接口,所以该类型不可读,但提供了 String 方法可以获取对象内的数据。 ```go -// 该方法向 b 写入一个字节 +// WriteByte 方法向 b 写入一个字节 func (b *Builder) WriteByte(c byte) error // WriteRune 方法向 b 写入一个字符 func (b *Builder) WriteRune(r rune) (int, error) -// WriteRune 方法向 b 写入字节数组 p +// Write 方法向 b 写入字节数组 p func (b *Builder) Write(p []byte) (int, error) -// WriteRune 方法向 b 写入字符串 s +// WriteString 方法向 b 写入字符串 s func (b *Builder) WriteString(s string) (int, error) // Len 方法返回 b 的数据长度。 func (b *Builder) Len() int @@ -653,12 +653,12 @@ func (b *Builder) Len() int func (b *Builder) Cap() int // Grow 方法将 b 的 cap 至少增加 n (可能会更多)。如果 n 为负数,会导致 panic。 func (b *Builder) Grow(n int) -// Reset 方法将 b 清空 b 的所有内容。 +// Reset 方法清空 b 的所有内容。 func (b *Builder) Reset() // String 方法将 b 的数据以 string 类型返回。 func (b *Builder) String() string ``` -Builder 有 4 个与写入相关的方法,这 4 个方法的 error 都总是为 nil. +Builder 有 4 个与写入相关的方法,这 4 个方法的 error 都总是为 nil。 Builder 的 cap 会自动增长,一般不需要手动调用 Grow 方法。 @@ -699,4 +699,4 @@ fmt.Println(b.String()) # 导航 # - [第二章 文本](/chapter02/02.0.md) -- 下一节:[bytes — byte slice 便利操作](02.2.md) \ No newline at end of file +- 下一节:[bytes — byte slice 便利操作](02.2.md) diff --git a/chapter02/02.2.md b/chapter02/02.2.md index 0849908..7ee5a3b 100644 --- a/chapter02/02.2.md +++ b/chapter02/02.2.md @@ -198,7 +198,7 @@ type Buffer struct { 对象可读取数据为 buf[off : len(buf)], off 表示进度下标,lastRead 表示最后读取的一个字符所占字节数,方便 Unread* 相关操作。 -Buffer 可以通过 3 中方法初始化对象: +Buffer 可以通过 3 种方法初始化对象: ```go a := bytes.NewBufferString("Hello World") b := bytes.NewBuffer([]byte("Hello World")) @@ -269,4 +269,4 @@ delim:N err: EOF # 导航 # - 上一节:[strings — 字符串操作](02.1.md) -- 下一节:[strconv — 字符串和基本数据类型之间转换](02.3.md) \ No newline at end of file +- 下一节:[strconv — 字符串和基本数据类型之间转换](02.3.md) diff --git a/chapter02/02.3.md b/chapter02/02.3.md index 64d02c8..40ed8b2 100644 --- a/chapter02/02.3.md +++ b/chapter02/02.3.md @@ -10,28 +10,34 @@ 然而,在返回错误的时候,不是直接将上面的变量值返回,而是通过构造一个 *NumError* 类型的 *error* 对象返回。*NumError* 结构的定义如下: - // A NumError records a failed conversion. - type NumError struct { - Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat) - Num string // the input - Err error // the reason the conversion failed (ErrRange, ErrSyntax) - } +```go +// A NumError records a failed conversion. +type NumError struct { + Func string // the failing function (ParseBool, ParseInt, ParseUint, ParseFloat) + Num string // the input + Err error // the reason the conversion failed (ErrRange, ErrSyntax) +} +``` 可见,该结构记录了转换过程中发生的错误信息。该结构不仅包含了一个 *error* 类型的成员,记录具体的错误信息,而且它自己也实现了 *error* 接口: - func (e *NumError) Error() string { - return "strconv." + e.Func + ": " + "parsing " + Quote(e.Num) + ": " + e.Err.Error() - } +```go +func (e *NumError) Error() string { + return "strconv." + e.Func + ": " + "parsing " + Quote(e.Num) + ": " + e.Err.Error() +} +``` 包的实现中,定义了两个便捷函数,用于构造 *NumError* 对象: - func syntaxError(fn, str string) *NumError { - return &NumError{fn, str, ErrSyntax} - } +```go +func syntaxError(fn, str string) *NumError { + return &NumError{fn, str, ErrSyntax} +} - func rangeError(fn, str string) *NumError { - return &NumError{fn, str, ErrRange} - } +func rangeError(fn, str string) *NumError { + return &NumError{fn, str, ErrRange} +} +``` 在遇到 *ErrSyntax* 或 *ErrRange* 错误时,通过上面的函数构造 *NumError* 对象。 @@ -41,9 +47,11 @@ 包括三个函数:ParseInt、ParseUint 和 Atoi,函数原型如下: - func ParseInt(s string, base int, bitSize int) (i int64, err error) - func ParseUint(s string, base int, bitSize int) (n uint64, err error) - func Atoi(s string) (i int, err error) +```go +func ParseInt(s string, base int, bitSize int) (i int64, err error) +func ParseUint(s string, base int, bitSize int) (n uint64, err error) +func Atoi(s string) (i int, err error) +``` 其中,Atoi 是 ParseInt 的便捷版,内部通过调用 *ParseInt(s, 10, 0)* 来实现的;ParseInt 转为有符号整型;ParseUint 转为无符号整型,着重介绍 ParseInt。 @@ -55,14 +63,18 @@ Go 中,int/uint 类型,不同系统能表示的范围是不一样的,目前的实现是,32 位系统占 4 个字节;64 位系统占 8 个字节。当 bitSize==0 时,应该表示 32 位还是 64 位呢?这里没有利用 *runtime.GOARCH* 之类的方式,而是巧妙的通过如下表达式确定 intSize: - const intSize = 32 << uint(^uint(0)>>63) - const IntSize = intSize // number of bits in int, uint (32 or 64) +```go +const intSize = 32 << uint(^uint(0)>>63) +const IntSize = intSize // number of bits in int, uint (32 or 64) +``` 主要是 *^uint(0)>>63* 这个表达式。操作符 *^* 在这里是一元操作符 按位取反,而不是 按位异或。更多解释可以参考:[Go 位运算:取反和异或](http://studygolang.com/topics/303)。 问题:下面的代码 n 和 err 的值分别是什么? - n, err := strconv.ParseInt("128", 10, 8) +```go +n, err := strconv.ParseInt("128", 10, 8) +``` 在 *ParseInt/ParseUint* 的实现中,如果字符串表示的整数超过了 bitSize 参数能够表示的范围,则会返回 ErrRange,同时会返回 bitSize 能够表示的最大或最小值。因此,这里的 n 是 127。 @@ -70,11 +82,13 @@ Go 中,int/uint 类型,不同系统能表示的范围是不一样的,目 转换的基本原理(以 "128" 转 为 10 进制 int 为例): - s := "128" - n := 0 - for i := 0; i < len(s); i++ { - n *= 10 + s[i] // base - } +```go +s := "128" +n := 0 +for i := 0; i < len(s); i++ { + n *= 10 + s[i] // base +} +``` 在循环处理的过程中,会检查数据的有效性和是否越界等。 @@ -82,27 +96,31 @@ Go 中,int/uint 类型,不同系统能表示的范围是不一样的,目 实际应用中,我们经常会遇到需要将字符串和整型连接起来,在 Java 中,可以通过操作符 "+" 做到。不过,在 Go 语言中,你需要将整型转为字符串类型,然后才能进行连接。这个时候,*strconv* 包中的整型转字符串的相关函数就派上用场了。这些函数签名如下: - func FormatUint(i uint64, base int) string // 无符号整型转字符串 - func FormatInt(i int64, base int) string // 有符号整型转字符串 - func Itoa(i int) string +```go +func FormatUint(i uint64, base int) string // 无符号整型转字符串 +func FormatInt(i int64, base int) string // 有符号整型转字符串 +func Itoa(i int) string +``` 其中,*Itoa* 内部直接调用 *FormatInt(i, 10)* 实现的。base 参数可以取 2~36(0-9,a-z)。 转换的基本原理(以 10 进制的 127 转 string 为例) : - const digits = "0123456789abcdefghijklmnopqrstuvwxyz" - u := uint64(127) - var a [65]byte - i := len(a) - b := uint64(base) - for u >= b { - i-- - a[i] = digits[uintptr(u%b)] - u /= b - } +```go +const digits = "0123456789abcdefghijklmnopqrstuvwxyz" +u := uint64(127) +var a [65]byte +i := len(a) +b := uint64(base) +for u >= b { i-- - a[i] = digits[uintptr(u)] - return string(a[1:]) + a[i] = digits[uintptr(u%b)] + u /= b +} +i-- +a[i] = digits[uintptr(u)] +return string(a[1:]) +``` 即将整数每一位数字对应到相应的字符,存入字符数组中,最后字符数组转为字符串即为结果。 @@ -112,21 +130,25 @@ Go 中,int/uint 类型,不同系统能表示的范围是不一样的,目 除了使用上述方法将整数转为字符串外,经常见到有人使用 *fmt* 包来做这件事。如: - fmt.Sprintf("%d", 127) +```go +fmt.Sprintf("%d", 127) +``` 那么,这两种方式我们该怎么选择呢?我们主要来考察一下性能。 - startTime := time.Now() - for i := 0; i < 10000; i++ { - fmt.Sprintf("%d", i) - } - fmt.Println(time.Now().Sub(startTime)) +```go +startTime := time.Now() +for i := 0; i < 10000; i++ { + fmt.Sprintf("%d", i) +} +fmt.Println(time.Now().Sub(startTime)) - startTime := time.Now() - for i := 0; i < 10000; i++ { - strconv.Itoa(i) - } - fmt.Println(time.Now().Sub(startTime)) +startTime := time.Now() +for i := 0; i < 10000; i++ { + strconv.Itoa(i) +} +fmt.Println(time.Now().Sub(startTime)) +``` 我们分别循环转换了 10000 次。*Sprintf* 的时间是 3.549761ms,而 *Itoa* 的时间是 848.208us,相差 4 倍多。 @@ -134,9 +156,7 @@ Go 中,int/uint 类型,不同系统能表示的范围是不一样的,目 注意:别想着通过 string(65) 这种方式将整数转为字符串,这样实际上得到的会是 ASCCII 值为 65 的字符,即 'A'。 -思考: - - 给定一个 40 以内的正整数,如何快速判断其是否是 2 的幂次方? +思考:给定一个 40 以内的正整数,如何快速判断其是否是 2 的幂次方? *提示:在 strconv 包源码 itoa.go 文件中找答案* @@ -144,34 +164,42 @@ Go 中,int/uint 类型,不同系统能表示的范围是不一样的,目 Go 中字符串和布尔值之间的转换比较简单,主要有三个函数: - // 接受 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False 等字符串; - // 其他形式的字符串会返回错误 - func ParseBool(str string) (value bool, err error) - // 直接返回 "true" 或 "false" - func FormatBool(b bool) string - // 将 "true" 或 "false" append 到 dst 中 - // 这里用了一个 append 函数对于字符串的特殊形式:append(dst, "true"...) - func AppendBool(dst []byte, b bool) +```go +// 接受 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False 等字符串; +// 其他形式的字符串会返回错误 +func ParseBool(str string) (value bool, err error) +// 直接返回 "true" 或 "false" +func FormatBool(b bool) string +// 将 "true" 或 "false" append 到 dst 中 +// 这里用了一个 append 函数对于字符串的特殊形式:append(dst, "true"...) +func AppendBool(dst []byte, b bool) +``` ## 2.3.4 字符串和浮点数之间的转换 ## 类似的,包含三个函数: - func ParseFloat(s string, bitSize int) (f float64, err error) - func FormatFloat(f float64, fmt byte, prec, bitSize int) string - func AppendFloat(dst []byte, f float64, fmt byte, prec int, bitSize int) +```go +func ParseFloat(s string, bitSize int) (f float64, err error) +func FormatFloat(f float64, fmt byte, prec, bitSize int) string +func AppendFloat(dst []byte, f float64, fmt byte, prec int, bitSize int) +``` 函数的命名和作用跟上面讲解的其他类型一致。 关于 *FormatFloat* 的 *fmt* 参数, 在第一章第三节[格式化 IO](/chapter01/01.3.md) 中有详细介绍。而 *prec* 表示有效数字(对 *fmt='b'* 无效),对于 'e', 'E' 和 'f',有效数字用于小数点之后的位数;对于 'g' 和 'G',则是所有的有效数字。例如: - strconv.FormatFloat(1223.13252, 'e', 3, 32) // 结果:1.223e+03 - strconv.FormatFloat(1223.13252, 'g', 3, 32) // 结果:1.22e+03 +```go +strconv.FormatFloat(1223.13252, 'e', 3, 32) // 结果:1.223e+03 +strconv.FormatFloat(1223.13252, 'g', 3, 32) // 结果:1.22e+03 +``` 由于浮点数有精度的问题,精度不一样,ParseFloat 和 FormatFloat 可能达不到互逆的效果。如: - s := strconv.FormatFloat(1234.5678, 'g', 6, 64) - strconv.ParseFloat(s, 64) +```go +s := strconv.FormatFloat(1234.5678, 'g', 6, 64) +strconv.ParseFloat(s, 64) +``` 另外,fmt='b' 时,得到的字符串是无法通过 *ParseFloat* 还原的。 @@ -185,18 +213,23 @@ Go 中字符串和布尔值之间的转换比较简单,主要有三个函数 So easy: - fmt.Println(`This is "studygolang.com" website`) +```go +fmt.Println(`This is "studygolang.com" website`) +``` 如果没有 *``* 符号,该怎么做?转义: - fmt.Println("This is \"studygolang.com\" website") +```go +fmt.Println("This is \"studygolang.com\" website") +``` 除了这两种方法,*strconv* 包还提供了函数这做件事(Quote 函数)。我们称 "studygolang.com" 这种用双引号引起来的字符串为 Go 语言字面值字符串(Go string literal)。 上面的一句话可以这么做: - fmt.Println("This is", strconv.Quote("studygolang.com"), "website") - +```go +fmt.Println("This is", strconv.Quote("studygolang.com"), "website") +``` # 导航 # diff --git a/chapter06/06.2.md b/chapter06/06.2.md index b064fe0..7865731 100644 --- a/chapter06/06.2.md +++ b/chapter06/06.2.md @@ -14,7 +14,7 @@ func Base(path string) string ``` `Dir` 返回路径中除去最后一个路径元素的部分,即该路径最后一个元素所在的目录。在使用 `Split` 去掉最后一个元素后,会简化路径并去掉末尾的斜杠。如果路径是空字符串,会返回 ".";如果路径由 1 到多个斜杠后跟 0 到多个非斜杠字符组成,会返回 "/";其他任何情况下都不会返回以斜杠结尾的路径。 -`Base` 函数返回路径的最后一个元素。在提取元素前会去掉末尾的斜杠。如果路径是 "",会返回 ".";如果路径是只有一个斜杆构成的,会返回 "/"。 +`Base` 函数返回路径的最后一个元素。在提取元素前会去掉末尾的斜杠。如果路径是 "",会返回 ".";如果路径是只有一个斜杠构成的,会返回 "/"。 比如,给定路径名 `/home/polaris/studygolang.go`,`Dir` 返回 `/home/polaris`,而 `Base` 返回 `studygolang.go`。 @@ -30,7 +30,7 @@ func Base(path string) string ## 相对路径和绝对路径 -某个进程都会有当前工作目录(进程相关章节会详细介绍),一般的相对路径,就是针对进程当前工作目录而言的。当然,可以针对某个目录指定相对路径。 +每个进程都会有当前工作目录(进程相关章节会详细介绍),一般的相对路径,就是针对进程当前工作目录而言的。当然,可以针对某个目录指定相对路径。 绝对路径,在 Unix 中,以 `/` 开始;在 Windows 下以某个盘符开始,比如 `C:\Program Files`。 diff --git a/chapter10/10.2.md b/chapter10/10.2.md index 0afd542..2bbe9f8 100644 --- a/chapter10/10.2.md +++ b/chapter10/10.2.md @@ -156,7 +156,7 @@ func main() { ## 改变进程的根目录 -每个进程都有一个根目录,该目录是解释绝对路径(即那些以 / 开始的目录)时的起点。默认情况下,这是文件系统的真是根目录。新进程从其父进程处继承根目录。有时可能需要改变一个进程的根目录(比如 ftp 服务就是一个典型的例子)。系统调用 `chroot` 能改变一个进程的根目录,Go 中对应的封装在 `syscall.Chroot`。 +每个进程都有一个根目录,该目录是解释绝对路径(即那些以 / 开始的目录)时的起点。默认情况下,这是文件系统的真实根目录。新进程从其父进程处继承根目录。有时可能需要改变一个进程的根目录(比如 ftp 服务就是一个典型的例子)。系统调用 `chroot` 能改变一个进程的根目录,Go 中对应的封装在 `syscall.Chroot`。 除此之外,在 `fork` 子进程时,可以通过给 `syscall.SysProcAttr` 结构的 `Chroot` 字段指定一个路径,来初始化子进程的根目录。