mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-12-26 06:46:27 +00:00
第3章,部分字词修订。
This commit is contained in:
parent
9f90d30fa7
commit
725acf091c
@ -28,7 +28,7 @@ Unicode字符rune类型是和int32等价的类型,通常用于表示一个Unic
|
|||||||
|
|
||||||
对于上表中前两行的运算符,例如+运算符还有一个与赋值相结合的对应运算符+=,可以用于简化赋值语句。
|
对于上表中前两行的运算符,例如+运算符还有一个与赋值相结合的对应运算符+=,可以用于简化赋值语句。
|
||||||
|
|
||||||
算术运算符`+`、`-`、`*`和`/`可以适用于整数、浮点数和复数,但是取模运算符%仅用于整数间的运算。对于不同编程语言,%取模运算的行为可能并不相同。在Go语言中,%取模运算符的符号和被取模数的符号总是一致的,因此`-5%3`和`-5%-3`结果都是-2。除法运算符`/`的行为则依赖于操作数是否为全为整数,比如`5.0/4.0`的结果是1.25,但是5/4的结果是1,因为整数除法会向着0方向截断余数。
|
算术运算符`+`、`-`、`*`和`/`可以适用于整数、浮点数和复数,但是取模运算符%仅用于整数间的运算。对于不同编程语言,%取模运算的行为可能并不相同。在Go语言中,%取模运算符的符号和被取模数的符号总是一致的,因此`-5%3`和`-5%-3`结果都是-2。除法运算符`/`的行为则依赖于操作数是否全为整数,比如`5.0/4.0`的结果是1.25,但是5/4的结果是1,因为整数除法会向着0方向截断余数。
|
||||||
|
|
||||||
一个算术运算的结果,不管是有符号或者是无符号的,如果需要更多的bit位才能正确表示的话,就说明计算结果是溢出了。超出的高位的bit位部分将被丢弃。如果原始的数值是有符号类型,而且最左边的bit位是1的话,那么最终结果可能是负的,例如int8的例子:
|
一个算术运算的结果,不管是有符号或者是无符号的,如果需要更多的bit位才能正确表示的话,就说明计算结果是溢出了。超出的高位的bit位部分将被丢弃。如果原始的数值是有符号类型,而且最左边的bit位是1的话,那么最终结果可能是负的,例如int8的例子:
|
||||||
|
|
||||||
@ -101,11 +101,11 @@ fmt.Printf("%08b\n", x>>1) // "00010001", the set {0, 4}
|
|||||||
|
|
||||||
(6.5节给出了一个可以远大于一个字节的整数集的实现。)
|
(6.5节给出了一个可以远大于一个字节的整数集的实现。)
|
||||||
|
|
||||||
在`x<<n`和`x>>n`移位运算中,决定了移位操作bit数部分必须是无符号数;被操作的x数可以是有符号或无符号数。算术上,一个`x<<n`左移运算等价于乘以$2^n$,一个`x>>n`右移运算等价于除以$2^n$。
|
在`x<<n`和`x>>n`移位运算中,决定了移位操作的bit数部分必须是无符号数;被操作的x可以是有符号数或无符号数。算术上,一个`x<<n`左移运算等价于乘以$2^n$,一个`x>>n`右移运算等价于除以$2^n$。
|
||||||
|
|
||||||
左移运算用零填充右边空缺的bit位,无符号数的右移运算也是用0填充左边空缺的bit位,但是有符号数的右移运算会用符号位的值填充左边空缺的bit位。因为这个原因,最好用无符号运算,这样你可以将整数完全当作一个bit位模式处理。
|
左移运算用零填充右边空缺的bit位,无符号数的右移运算也是用0填充左边空缺的bit位,但是有符号数的右移运算会用符号位的值填充左边空缺的bit位。因为这个原因,最好用无符号运算,这样你可以将整数完全当作一个bit位模式处理。
|
||||||
|
|
||||||
尽管Go语言提供了无符号数和运算,即使数值本身不可能出现负数我们还是倾向于使用有符号的int类型,就像数组的长度那样,虽然使用uint无符号类型似乎是一个更合理的选择。事实上,内置的len函数返回一个有符号的int,我们可以像下面例子那样处理逆序循环。
|
尽管Go语言提供了无符号数的运算,但即使数值本身不可能出现负数,我们还是倾向于使用有符号的int类型,就像数组的长度那样,虽然使用uint无符号类型似乎是一个更合理的选择。事实上,内置的len函数返回一个有符号的int,我们可以像下面例子那样处理逆序循环。
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
medals := []string{"gold", "silver", "bronze"}
|
medals := []string{"gold", "silver", "bronze"}
|
||||||
@ -118,9 +118,9 @@ for i := len(medals) - 1; i >= 0; i-- {
|
|||||||
|
|
||||||
出于这个原因,无符号数往往只有在位运算或其它特殊的运算场景才会使用,就像bit集合、分析二进制文件格式或者是哈希和加密操作等。它们通常并不用于仅仅是表达非负数量的场合。
|
出于这个原因,无符号数往往只有在位运算或其它特殊的运算场景才会使用,就像bit集合、分析二进制文件格式或者是哈希和加密操作等。它们通常并不用于仅仅是表达非负数量的场合。
|
||||||
|
|
||||||
一般来说,需要一个显式的转换将一个值从一种类型转化位另一种类型,并且算术和逻辑运算的二元操作中必须是相同的类型。虽然这偶尔会导致需要很长的表达式,但是它消除了所有和类型相关的问题,而且也使得程序容易理解。
|
一般来说,需要一个显式的转换将一个值从一种类型转化为另一种类型,并且算术和逻辑运算的二元操作中必须是相同的类型。虽然这偶尔会导致需要很长的表达式,但是它消除了所有和类型相关的问题,而且也使得程序容易理解。
|
||||||
|
|
||||||
在很多场景,会遇到类似下面的代码通用的错误:
|
在很多场景,会遇到类似下面代码的常见的错误:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
var apples int32 = 1
|
var apples int32 = 1
|
||||||
@ -150,7 +150,7 @@ f = 1.99
|
|||||||
fmt.Println(int(f)) // "1"
|
fmt.Println(int(f)) // "1"
|
||||||
```
|
```
|
||||||
|
|
||||||
浮点数到整数的转换将丢失任何小数部分,然后向数轴零方向截断。你应该避免对可能会超出目标类型表示范围的数值类型转换,因为截断的行为可能依赖于具体的实现:
|
浮点数到整数的转换将丢失任何小数部分,然后向数轴零方向截断。你应该避免对可能会超出目标类型表示范围的数值做类型转换,因为截断的行为可能依赖于具体的实现:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
f := 1e100 // a float64
|
f := 1e100 // a float64
|
||||||
|
@ -135,9 +135,9 @@ func f(x, y float64) float64 {
|
|||||||
|
|
||||||
要注意的是corner函数返回了两个结果,分别对应每个网格顶点的坐标参数。
|
要注意的是corner函数返回了两个结果,分别对应每个网格顶点的坐标参数。
|
||||||
|
|
||||||
要解释这个程序是如何工作的需要一些基本的几何学知识,但是我们可以跳过几何学原理,因为程序的重点是演示浮点数运算。程序的本质是三个不同的坐标系中映射关系,如图3.2所示。第一个是100x100的二维网格,对应整数整数坐标(i,j),从远处的(0, 0)位置开始。我们从远处向前面绘制,因此远处先绘制的多边形有可能被前面后绘制的多边形覆盖。
|
要解释这个程序是如何工作的需要一些基本的几何学知识,但是我们可以跳过几何学原理,因为程序的重点是演示浮点数运算。程序的本质是三个不同的坐标系中映射关系,如图3.2所示。第一个是100x100的二维网格,对应整数坐标(i,j),从远处的(0, 0)位置开始。我们从远处向前面绘制,因此远处先绘制的多边形有可能被前面后绘制的多边形覆盖。
|
||||||
|
|
||||||
第二个坐标系是一个三维的网格浮点坐标(x,y,z),其中x和y是i和j的线性函数,通过平移转换位网格单元的中心,然后用xyrange系数缩放。高度z是函数f(x,y)的值。
|
第二个坐标系是一个三维的网格浮点坐标(x,y,z),其中x和y是i和j的线性函数,通过平移转换为网格单元的中心,然后用xyrange系数缩放。高度z是函数f(x,y)的值。
|
||||||
|
|
||||||
第三个坐标系是一个二维的画布,起点(0,0)在左上角。画布中点的坐标用(sx, sy)表示。我们使用等角投影将三维点
|
第三个坐标系是一个二维的画布,起点(0,0)在左上角。画布中点的坐标用(sx, sy)表示。我们使用等角投影将三维点
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ func mandelbrot(z complex128) color.Color {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
用于遍历1024x1024图像每个点的两个嵌套的循环对应-2到+2区间的复数平面。程序反复测试每个点对应复数值平方值加一个增量值对应的点是否超出半径为2的圆。如果超过了,通过根据预设置的逃逸迭代次数对应的灰度颜色来代替。如果不是,那么该点属于Mandelbrot集合,使用黑色颜色标记。最终程序将生成的PNG格式分形图像图像输出到标准输出,如图3.3所示。
|
用于遍历1024x1024图像每个点的两个嵌套的循环对应-2到+2区间的复数平面。程序反复测试每个点对应复数值平方值加一个增量值对应的点是否超出半径为2的圆。如果超过了,通过根据预设置的逃逸迭代次数对应的灰度颜色来代替。如果不是,那么该点属于Mandelbrot集合,使用黑色颜色标记。最终程序将生成的PNG格式分形图像输出到标准输出,如图3.3所示。
|
||||||
|
|
||||||
![](../images/ch3-03.png)
|
![](../images/ch3-03.png)
|
||||||
|
|
||||||
@ -93,4 +93,4 @@ func mandelbrot(z complex128) color.Color {
|
|||||||
|
|
||||||
**练习 3.8:** 通过提高精度来生成更多级别的分形。使用四种不同精度类型的数字实现相同的分形:complex64、complex128、big.Float和big.Rat。(后面两种类型在math/big包声明。Float是有指定限精度的浮点数;Rat是无限精度的有理数。)它们间的性能和内存使用对比如何?当渲染图可见时缩放的级别是多少?
|
**练习 3.8:** 通过提高精度来生成更多级别的分形。使用四种不同精度类型的数字实现相同的分形:complex64、complex128、big.Float和big.Rat。(后面两种类型在math/big包声明。Float是有指定限精度的浮点数;Rat是无限精度的有理数。)它们间的性能和内存使用对比如何?当渲染图可见时缩放的级别是多少?
|
||||||
|
|
||||||
**练习 3.9:** 编写一个web服务器,用于给客户端生成分形的图像。运行客户端用过HTTP参数参数指定x,y和zoom参数。
|
**练习 3.9:** 编写一个web服务器,用于给客户端生成分形的图像。运行客户端通过HTTP参数指定x,y和zoom参数。
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
### 3.5.1. 字符串面值
|
### 3.5.1. 字符串面值
|
||||||
|
|
||||||
字符串值也可以用字符串面值方式编写,只要将一系列字节序列包含在双引号即可:
|
字符串值也可以用字符串面值方式编写,只要将一系列字节序列包含在双引号内即可:
|
||||||
|
|
||||||
```
|
```
|
||||||
"Hello, 世界"
|
"Hello, 世界"
|
||||||
@ -25,9 +25,9 @@
|
|||||||
\\ 反斜杠
|
\\ 反斜杠
|
||||||
```
|
```
|
||||||
|
|
||||||
可以通过十六进制或八进制转义在字符串面值包含任意的字节。一个十六进制的转义形式是`\xhh`,其中两个h表示十六进制数字(大写或小写都可以)。一个八进制转义形式是`\ooo`,包含三个八进制的o数字(0到7),但是不能超过`\377`(译注:对应一个字节的范围,十进制为255)。每一个单一的字节表达一个特定的值。稍后我们将看到如何将一个Unicode码点写到字符串面值中。
|
可以通过十六进制或八进制转义在字符串面值中包含任意的字节。一个十六进制的转义形式是`\xhh`,其中两个h表示十六进制数字(大写或小写都可以)。一个八进制转义形式是`\ooo`,包含三个八进制的o数字(0到7),但是不能超过`\377`(译注:对应一个字节的范围,十进制为255)。每一个单一的字节表达一个特定的值。稍后我们将看到如何将一个Unicode码点写到字符串面值中。
|
||||||
|
|
||||||
一个原生的字符串面值形式是\`...\`,使用反引号代替双引号。在原生的字符串面值中,没有转义操作;全部的内容都是字面的意思,包含退格和换行,因此一个程序中的原生字符串面值可能跨越多行(译注:在原生字符串面值内部是无法直接写\`字符的,可以用八进制或十六进制转义或+"\`"链接字符串常量完成)。唯一的特殊处理是会删除回车以保证在所有平台上的值都是一样的,包括那些把回车也放入文本文件的系统(译注:Windows系统会把回车和换行一起放入文本文件中)。
|
一个原生的字符串面值形式是\`...\`,使用反引号代替双引号。在原生的字符串面值中,没有转义操作;全部的内容都是字面的意思,包含退格和换行,因此一个程序中的原生字符串面值可能跨越多行(译注:在原生字符串面值内部是无法直接写\`字符的,可以用八进制或十六进制转义或+"\`"连接字符串常量完成)。唯一的特殊处理是会删除回车以保证在所有平台上的值都是一样的,包括那些把回车也放入文本文件的系统(译注:Windows系统会把回车和换行一起放入文本文件中)。
|
||||||
|
|
||||||
原生字符串面值用于编写正则表达式会很方便,因为正则表达式往往会包含很多反斜杠。原生字符串面值同时被广泛应用于HTML模板、JSON面值、命令行提示信息以及那些需要扩展到多行的场景。
|
原生字符串面值用于编写正则表达式会很方便,因为正则表达式往往会包含很多反斜杠。原生字符串面值同时被广泛应用于HTML模板、JSON面值、命令行提示信息以及那些需要扩展到多行的场景。
|
||||||
|
|
||||||
|
@ -4,6 +4,6 @@
|
|||||||
|
|
||||||
答案就是使用Unicode( http://unicode.org ),它收集了这个世界上所有的符号系统,包括重音符号和其它变音符号,制表符和回车符,还有很多神秘的符号,每个符号都分配一个唯一的Unicode码点,Unicode码点对应Go语言中的rune整数类型(译注:rune是int32等价类型)。
|
答案就是使用Unicode( http://unicode.org ),它收集了这个世界上所有的符号系统,包括重音符号和其它变音符号,制表符和回车符,还有很多神秘的符号,每个符号都分配一个唯一的Unicode码点,Unicode码点对应Go语言中的rune整数类型(译注:rune是int32等价类型)。
|
||||||
|
|
||||||
在第八版本的Unicode标准收集了超过120,000个字符,涵盖超过100多种语言。这些在计算机程序和数据中是如何体现的呢?通用的表示一个Unicode码点的数据类型是int32,也就是Go语言中rune对应的类型;它的同义词rune符文正是这个意思。
|
在第八版本的Unicode标准里收集了超过120,000个字符,涵盖超过100多种语言。这些在计算机程序和数据中是如何体现的呢?通用的表示一个Unicode码点的数据类型是int32,也就是Go语言中rune对应的类型;它的同义词rune符文正是这个意思。
|
||||||
|
|
||||||
我们可以将一个符文序列表示为一个int32序列。这种编码方式叫UTF-32或UCS-4,每个Unicode码点都使用同样的大小32bit来表示。这种方式比较简单统一,但是它会浪费很多存储空间,因为大数据计算机可读的文本是ASCII字符,本来每个ASCII字符只需要8bit或1字节就能表示。而且即使是常用的字符也远少于65,536个,也就是说用16bit编码方式就能表达常用字符。但是,还有其它更好的编码方法吗?
|
我们可以将一个符文序列表示为一个int32序列。这种编码方式叫UTF-32或UCS-4,每个Unicode码点都使用同样大小的32bit来表示。这种方式比较简单统一,但是它会浪费很多存储空间,因为大多数计算机可读的文本是ASCII字符,本来每个ASCII字符只需要8bit或1字节就能表示。而且即使是常用的字符也远少于65,536个,也就是说用16bit编码方式就能表达常用字符。但是,还有其它更好的编码方法吗?
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
### 3.5.3. UTF-8
|
### 3.5.3. UTF-8
|
||||||
|
|
||||||
UTF8是一个将Unicode码点编码为字节序列的变长编码。UTF8编码由Go语言之父Ken Thompson和Rob Pike共同发明的,现在已经是Unicode的标准。UTF8编码使用1到4个字节来表示每个Unicode码点,ASCII部分字符只使用1个字节,常用字符部分使用2或3个字节表示。每个符号编码后第一个字节的高端bit位用于表示总共有多少编码个字节。如果第一个字节的高端bit为0,则表示对应7bit的ASCII字符,ASCII字符每个字符依然是一个字节,和传统的ASCII编码兼容。如果第一个字节的高端bit是110,则说明需要2个字节;后续的每个高端bit都以10开头。更大的Unicode码点也是采用类似的策略处理。
|
UTF8是一个将Unicode码点编码为字节序列的变长编码。UTF8编码是由Go语言之父Ken Thompson和Rob Pike共同发明的,现在已经是Unicode的标准。UTF8编码使用1到4个字节来表示每个Unicode码点,ASCII部分字符只使用1个字节,常用字符部分使用2或3个字节表示。每个符号编码后第一个字节的高端bit位用于表示编码总共有多少个字节。如果第一个字节的高端bit为0,则表示对应7bit的ASCII字符,ASCII字符每个字符依然是一个字节,和传统的ASCII编码兼容。如果第一个字节的高端bit是110,则说明需要2个字节;后续的每个高端bit都以10开头。更大的Unicode码点也是采用类似的策略处理。
|
||||||
|
|
||||||
```
|
```
|
||||||
0xxxxxxx runes 0-127 (ASCII)
|
0xxxxxxx runes 0-127 (ASCII)
|
||||||
@ -11,7 +11,7 @@ UTF8是一个将Unicode码点编码为字节序列的变长编码。UTF8编码
|
|||||||
|
|
||||||
变长的编码无法直接通过索引来访问第n个字符,但是UTF8编码获得了很多额外的优点。首先UTF8编码比较紧凑,完全兼容ASCII码,并且可以自动同步:它可以通过向前回朔最多2个字节就能确定当前字符编码的开始字节的位置。它也是一个前缀编码,所以当从左向右解码时不会有任何歧义也并不需要向前查看(译注:像GBK之类的编码,如果不知道起点位置则可能会出现歧义)。没有任何字符的编码是其它字符编码的子串,或是其它编码序列的字串,因此搜索一个字符时只要搜索它的字节编码序列即可,不用担心前后的上下文会对搜索结果产生干扰。同时UTF8编码的顺序和Unicode码点的顺序一致,因此可以直接排序UTF8编码序列。同时因为没有嵌入的NUL(0)字节,可以很好地兼容那些使用NUL作为字符串结尾的编程语言。
|
变长的编码无法直接通过索引来访问第n个字符,但是UTF8编码获得了很多额外的优点。首先UTF8编码比较紧凑,完全兼容ASCII码,并且可以自动同步:它可以通过向前回朔最多2个字节就能确定当前字符编码的开始字节的位置。它也是一个前缀编码,所以当从左向右解码时不会有任何歧义也并不需要向前查看(译注:像GBK之类的编码,如果不知道起点位置则可能会出现歧义)。没有任何字符的编码是其它字符编码的子串,或是其它编码序列的字串,因此搜索一个字符时只要搜索它的字节编码序列即可,不用担心前后的上下文会对搜索结果产生干扰。同时UTF8编码的顺序和Unicode码点的顺序一致,因此可以直接排序UTF8编码序列。同时因为没有嵌入的NUL(0)字节,可以很好地兼容那些使用NUL作为字符串结尾的编程语言。
|
||||||
|
|
||||||
Go语言的源文件采用UTF8编码,并且Go语言处理UTF8编码的文本也很出色。unicode包提供了诸多处理rune字符相关功能的函数(比如区分字母和数组,或者是字母的大写和小写转换等),unicode/utf8包则提供了用于rune字符序列的UTF8编码和解码的功能。
|
Go语言的源文件采用UTF8编码,并且Go语言处理UTF8编码的文本也很出色。unicode包提供了诸多处理rune字符相关功能的函数(比如区分字母和数字,或者是字母的大写和小写转换等),unicode/utf8包则提供了用于rune字符序列的UTF8编码和解码的功能。
|
||||||
|
|
||||||
有很多Unicode字符很难直接从键盘输入,并且还有很多字符有着相似的结构;有一些甚至是不可见的字符(译注:中文和日文就有很多相似但不同的字)。Go语言字符串面值中的Unicode转义字符让我们可以通过Unicode码点输入特殊的字符。有两种形式:`\uhhhh`对应16bit的码点值,`\Uhhhhhhhh`对应32bit的码点值,其中h是一个十六进制数字;一般很少需要使用32bit的形式。每一个对应码点的UTF8编码。例如:下面的字母串面值都表示相同的值:
|
有很多Unicode字符很难直接从键盘输入,并且还有很多字符有着相似的结构;有一些甚至是不可见的字符(译注:中文和日文就有很多相似但不同的字)。Go语言字符串面值中的Unicode转义字符让我们可以通过Unicode码点输入特殊的字符。有两种形式:`\uhhhh`对应16bit的码点值,`\Uhhhhhhhh`对应32bit的码点值,其中h是一个十六进制数字;一般很少需要使用32bit的形式。每一个对应码点的UTF8编码。例如:下面的字母串面值都表示相同的值:
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ Unicode转义也可以使用在rune字符中。下面三个字符是等价的:
|
|||||||
'世' '\u4e16' '\U00004e16'
|
'世' '\u4e16' '\U00004e16'
|
||||||
```
|
```
|
||||||
|
|
||||||
对于小于256码点值可以写在一个十六进制转义字节中,例如`\x41`对应字符'A',但是对于更大的码点则必须使用`\u`或`\U`转义形式。因此,`\xe4\xb8\x96`并不是一个合法的rune字符,虽然这三个字节对应一个有效的UTF8编码的码点。
|
对于小于256的码点值可以写在一个十六进制转义字节中,例如`\x41`对应字符'A',但是对于更大的码点则必须使用`\u`或`\U`转义形式。因此,`\xe4\xb8\x96`并不是一个合法的rune字符,虽然这三个字节对应一个有效的UTF8编码的码点。
|
||||||
|
|
||||||
得益于UTF8编码优良的设计,诸多字符串操作都不需要解码操作。我们可以不用解码直接测试一个字符串是否是另一个字符串的前缀:
|
得益于UTF8编码优良的设计,诸多字符串操作都不需要解码操作。我们可以不用解码直接测试一个字符串是否是另一个字符串的前缀:
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ func Contains(s, substr string) bool {
|
|||||||
|
|
||||||
对于UTF8编码后文本的处理和原始的字节处理逻辑是一样的。但是对应很多其它编码则并不是这样的。(上面的函数都来自strings字符串处理包,真实的代码包含了一个用哈希技术优化的Contains 实现。)
|
对于UTF8编码后文本的处理和原始的字节处理逻辑是一样的。但是对应很多其它编码则并不是这样的。(上面的函数都来自strings字符串处理包,真实的代码包含了一个用哈希技术优化的Contains 实现。)
|
||||||
|
|
||||||
另一方面,如果我们真的关心每个Unicode字符,我们可以使用其它处理方式。考虑前面的第一个例子中的字符串,它包混合了中西两种字符。图3.5展示了它的内存表示形式。字符串包含13个字节,以UTF8形式编码,但是只对应9个Unicode字符:
|
另一方面,如果我们真的关心每个Unicode字符,我们可以使用其它处理方式。考虑前面的第一个例子中的字符串,它混合了中西两种字符。图3.5展示了它的内存表示形式。字符串包含13个字节,以UTF8形式编码,但是只对应9个Unicode字符:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
import "unicode/utf8"
|
import "unicode/utf8"
|
||||||
@ -113,9 +113,9 @@ for range s {
|
|||||||
|
|
||||||
或者我们可以直接调用utf8.RuneCountInString(s)函数。
|
或者我们可以直接调用utf8.RuneCountInString(s)函数。
|
||||||
|
|
||||||
正如我们前面提到的,文本字符串采用UTF8编码只是一种惯例,但是对于循环的真正字符串并不是一个惯例,这是正确的。如果用于循环的字符串只是一个普通的二进制数据,或者是含有错误编码的UTF8数据,将会发送什么呢?
|
正如我们前面提到的,文本字符串采用UTF8编码只是一种惯例,但是对于循环的真正字符串并不是一个惯例,这是正确的。如果用于循环的字符串只是一个普通的二进制数据,或者是含有错误编码的UTF8数据,将会发生什么呢?
|
||||||
|
|
||||||
每一个UTF8字符解码,不管是显式地调用utf8.DecodeRuneInString解码或是在range循环中隐式地解码,如果遇到一个错误的UTF8编码输入,将生成一个特别的Unicode字符`\uFFFD`,在印刷中这个符号通常是一个黑色六角或钻石形状,里面包含一个白色的问号"<EFBFBD>"。当程序遇到这样的一个字符,通常是一个危险信号,说明输入并不是一个完美没有错误的UTF8字符串。
|
每一个UTF8字符解码,不管是显式地调用utf8.DecodeRuneInString解码或是在range循环中隐式地解码,如果遇到一个错误的UTF8编码输入,将生成一个特别的Unicode字符`\uFFFD`,在印刷中这个符号通常是一个黑色六角或钻石形状,里面包含一个白色的问号"?"。当程序遇到这样的一个字符,通常是一个危险信号,说明输入并不是一个完美没有错误的UTF8字符串。
|
||||||
|
|
||||||
UTF8字符串作为交换格式是非常方便的,但是在程序内部采用rune序列可能更方便,因为rune大小一致,支持数组索引和方便切割。
|
UTF8字符串作为交换格式是非常方便的,但是在程序内部采用rune序列可能更方便,因为rune大小一致,支持数组索引和方便切割。
|
||||||
|
|
||||||
@ -147,5 +147,5 @@ fmt.Println(string(0x4eac)) // "京"
|
|||||||
如果对应码点的字符是无效的,则用`\uFFFD`无效字符作为替换:
|
如果对应码点的字符是无效的,则用`\uFFFD`无效字符作为替换:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
fmt.Println(string(1234567)) // "<EFBFBD>"
|
fmt.Println(string(1234567)) // "?"
|
||||||
```
|
```
|
||||||
|
@ -8,7 +8,7 @@ strconv包提供了布尔型、整型数、浮点数和对应字符串的相互
|
|||||||
|
|
||||||
unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能,它们用于给字符分类。每个函数有一个单一的rune类型的参数,然后返回一个布尔值。而像ToUpper和ToLower之类的转换函数将用于rune字符的大小写转换。所有的这些函数都是遵循Unicode标准定义的字母、数字等分类规范。strings包也有类似的函数,它们是ToUpper和ToLower,将原始字符串的每个字符都做相应的转换,然后返回新的字符串。
|
unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能,它们用于给字符分类。每个函数有一个单一的rune类型的参数,然后返回一个布尔值。而像ToUpper和ToLower之类的转换函数将用于rune字符的大小写转换。所有的这些函数都是遵循Unicode标准定义的字母、数字等分类规范。strings包也有类似的函数,它们是ToUpper和ToLower,将原始字符串的每个字符都做相应的转换,然后返回新的字符串。
|
||||||
|
|
||||||
下面例子的basename函数灵感于Unix shell的同名工具。在我们实现的版本中,basename(s)将看起来像是系统路径的前缀删除,同时将看似文件类型的后缀名部分删除:
|
下面例子的basename函数灵感源于Unix shell的同名工具。在我们实现的版本中,basename(s)将看起来像是系统路径的前缀删除,同时将看似文件类型的后缀名部分删除:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
fmt.Println(basename("a/b/c.go")) // "c"
|
fmt.Println(basename("a/b/c.go")) // "c"
|
||||||
@ -41,7 +41,7 @@ func basename(s string) string {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
简化个版本使用了strings.LastIndex库函数:
|
这个简化版本使用了strings.LastIndex库函数:
|
||||||
|
|
||||||
<u><i>gopl.io/ch3/basename2</i></u>
|
<u><i>gopl.io/ch3/basename2</i></u>
|
||||||
```Go
|
```Go
|
||||||
@ -57,7 +57,7 @@ func basename(s string) string {
|
|||||||
|
|
||||||
path和path/filepath包提供了关于文件路径名更一般的函数操作。使用斜杠分隔路径可以在任何操作系统上工作。斜杠本身不应该用于文件名,但是在其他一些领域可能会用于文件名,例如URL路径组件。相比之下,path/filepath包则使用操作系统本身的路径规则,例如POSIX系统使用/foo/bar,而Microsoft Windows使用`c:\foo\bar`等。
|
path和path/filepath包提供了关于文件路径名更一般的函数操作。使用斜杠分隔路径可以在任何操作系统上工作。斜杠本身不应该用于文件名,但是在其他一些领域可能会用于文件名,例如URL路径组件。相比之下,path/filepath包则使用操作系统本身的路径规则,例如POSIX系统使用/foo/bar,而Microsoft Windows使用`c:\foo\bar`等。
|
||||||
|
|
||||||
让我们继续另一个字符串的例子。函数的功能是将一个表示整值的字符串,每隔三个字符插入一个逗号分隔符,例如“12345”处理后成为“12,345”。这个版本只适用于整数类型;支持浮点数类型的支持留作练习。
|
让我们继续另一个字符串的例子。函数的功能是将一个表示整数值的字符串,每隔三个字符插入一个逗号分隔符,例如“12345”处理后成为“12,345”。这个版本只适用于整数类型;支持浮点数类型的留作练习。
|
||||||
|
|
||||||
<u><i>gopl.io/ch3/comma</i></u>
|
<u><i>gopl.io/ch3/comma</i></u>
|
||||||
```Go
|
```Go
|
||||||
@ -71,9 +71,9 @@ func comma(s string) string {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
输入comma函数的参数是一个字符串。如果输入字符串的长度小于或等于3的话,则不需要插入逗分隔符。否则,comma函数将在最后三个字符前位置将字符串切割为两个两个子串并插入逗号分隔符,然后通过递归调用自身来出前面的子串。
|
输入comma函数的参数是一个字符串。如果输入字符串的长度小于或等于3的话,则不需要插入逗号分隔符。否则,comma函数将在最后三个字符前的位置将字符串切割为两个子串并插入逗号分隔符,然后通过递归调用自身来得出前面的子串。
|
||||||
|
|
||||||
一个字符串是包含的只读字节数组,一旦创建,是不可变的。相比之下,一个字节slice的元素则可以自由地修改。
|
一个字符串是包含只读字节的数组,一旦创建,是不可变的。相比之下,一个字节slice的元素则可以自由地修改。
|
||||||
|
|
||||||
字符串和字节slice之间可以相互转换:
|
字符串和字节slice之间可以相互转换:
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ b := []byte(s)
|
|||||||
s2 := string(b)
|
s2 := string(b)
|
||||||
```
|
```
|
||||||
|
|
||||||
从概念上讲,一个[]byte(s)转换是分配了一个新的字节数组用于保存字符串数据的拷贝,然后引用这个底层的字节数组。编译器的优化可以避免在一些场景下分配和复制字符串数据,但总的来说需要确保在变量b被修改的情况下,原始的s字符串也不会改变。将一个字节slice转到字符串的string(b)操作则是构造一个字符串拷贝,以确保s2字符串是只读的。
|
从概念上讲,一个[]byte(s)转换是分配了一个新的字节数组用于保存字符串数据的拷贝,然后引用这个底层的字节数组。编译器的优化可以避免在一些场景下分配和复制字符串数据,但总的来说需要确保在变量b被修改的情况下,原始的s字符串也不会改变。将一个字节slice转换到字符串的string(b)操作则是构造一个字符串拷贝,以确保s2字符串是只读的。
|
||||||
|
|
||||||
为了避免转换中不必要的内存分配,bytes包和strings同时提供了许多实用函数。下面是strings包中的六个函数:
|
为了避免转换中不必要的内存分配,bytes包和strings同时提供了许多实用函数。下面是strings包中的六个函数:
|
||||||
|
|
||||||
@ -140,4 +140,4 @@ bytes.Buffer类型有着很多实用的功能,我们在第七章讨论接口
|
|||||||
|
|
||||||
**练习 3.11:** 完善comma函数,以支持浮点数处理和一个可选的正负号的处理。
|
**练习 3.11:** 完善comma函数,以支持浮点数处理和一个可选的正负号的处理。
|
||||||
|
|
||||||
**练习 3.12:** 编写一个函数,判断两个字符串是否是是相互打乱的,也就是说它们有着相同的字符,但是对应不同的顺序。
|
**练习 3.12:** 编写一个函数,判断两个字符串是否是相互打乱的,也就是说它们有着相同的字符,但是对应不同的顺序。
|
||||||
|
@ -16,7 +16,7 @@ FormatInt和FormatUint函数可以用不同的进制来格式化数字:
|
|||||||
fmt.Println(strconv.FormatInt(int64(x), 2)) // "1111011"
|
fmt.Println(strconv.FormatInt(int64(x), 2)) // "1111011"
|
||||||
```
|
```
|
||||||
|
|
||||||
fmt.Printf函数的%b、%d、%o和%x等参数提供功能往往比strconv包的Format函数方便很多,特别是在需要包含附加额外信息的时候:
|
fmt.Printf函数的%b、%d、%o和%x等参数提供功能往往比strconv包的Format函数方便很多,特别是在需要包含有附加额外信息的时候:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
s := fmt.Sprintf("x=%b", x) // "x=1111011"
|
s := fmt.Sprintf("x=%b", x) // "x=1111011"
|
||||||
|
@ -34,7 +34,7 @@ fmt.Println(s[7:]) // "world"
|
|||||||
fmt.Println(s[:]) // "hello, world"
|
fmt.Println(s[:]) // "hello, world"
|
||||||
```
|
```
|
||||||
|
|
||||||
其中+操作符将两个字符串链接构造一个新字符串:
|
其中+操作符将两个字符串连接构造一个新字符串:
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
fmt.Println("goodbye" + s[5:]) // "goodbye, world"
|
fmt.Println("goodbye" + s[5:]) // "goodbye, world"
|
||||||
@ -63,7 +63,7 @@ fmt.Println(t) // "left foot"
|
|||||||
s[0] = 'L' // compile error: cannot assign to s[0]
|
s[0] = 'L' // compile error: cannot assign to s[0]
|
||||||
```
|
```
|
||||||
|
|
||||||
不变性意味如果两个字符串共享相同的底层数据的话也是安全的,这使得复制任何长度的字符串代价是低廉的。同样,一个字符串s和对应的子字符串切片s[7:]的操作也可以安全地共享相同的内存,因此字符串切片操作代价也是低廉的。在这两种情况下都没有必要分配新的内存。 图3.4演示了一个字符串和两个子串共享相同的底层数据。
|
不变性意味着如果两个字符串共享相同的底层数据的话也是安全的,这使得复制任何长度的字符串代价是低廉的。同样,一个字符串s和对应的子字符串切片s[7:]的操作也可以安全地共享相同的内存,因此字符串切片操作代价也是低廉的。在这两种情况下都没有必要分配新的内存。 图3.4演示了一个字符串和两个子串共享相同的底层数据。
|
||||||
|
|
||||||
|
|
||||||
{% include "./ch3-05-1.md" %}
|
{% include "./ch3-05-1.md" %}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
### 3.6.2. 无类型常量
|
### 3.6.2. 无类型常量
|
||||||
|
|
||||||
Go语言的常量有个不同寻常之处。虽然一个常量可以有任意有一个确定的基础类型,例如int或float64,或者是类似time.Duration这样命名的基础类型,但是许多常量并没有一个明确的基础类型。编译器为这些没有明确的基础类型的数字常量提供比基础类型更高精度的算术运算;你可以认为至少有256bit的运算精度。这里有六种未明确类型的常量类型,分别是无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串。
|
Go语言的常量有个不同寻常之处。虽然一个常量可以有任意一个确定的基础类型,例如int或float64,或者是类似time.Duration这样命名的基础类型,但是许多常量并没有一个明确的基础类型。编译器为这些没有明确基础类型的数字常量提供比基础类型更高精度的算术运算;你可以认为至少有256bit的运算精度。这里有六种未明确类型的常量类型,分别是无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串。
|
||||||
|
|
||||||
通过延迟明确常量的具体类型,无类型的常量不仅可以提供更高的运算精度,而且可以直接用于更多的表达式而不需要显式的类型转换。例如,例子中的ZiB和YiB的值已经超出任何Go语言中整数类型能表达的范围,但是它们依然是合法的常量,而且可以像下面常量表达式依然有效(译注:YiB/ZiB是在编译期计算出来的,并且结果常量是1024,是Go语言int变量能有效表示的):
|
通过延迟明确常量的具体类型,无类型的常量不仅可以提供更高的运算精度,而且可以直接用于更多的表达式而不需要显式的类型转换。例如,例子中的ZiB和YiB的值已经超出任何Go语言中整数类型能表达的范围,但是它们依然是合法的常量,而且像下面的常量表达式依然有效(译注:YiB/ZiB是在编译期计算出来的,并且结果常量是1024,是Go语言int变量能有效表示的):
|
||||||
|
|
||||||
```Go
|
```Go
|
||||||
fmt.Println(YiB/ZiB) // "1024"
|
fmt.Println(YiB/ZiB) // "1024"
|
||||||
|
Loading…
Reference in New Issue
Block a user