4.Go语言数据的使用
4.4类型转换
类型转换是把一个类型的值转换成为另一个类型的值。把这个值原来的类型称为源类型,而这个值被转换后的类型称为目标类型。
如果 T 是值 x 的目标类型,那么相应的类型转换表达式如下:
T(x) //x可以是一个表达式,不过这个表达式的结果值只能有一个
如果代表目标类型的字面量始于操作符 * 或 <- ,后者它是没有结果声明列表的函数类型,那么往往需要用圆括号括起来,以避免歧义的产生。例如:
*string(v) //等同于 *(string(v)),先将变量v代表的值转换为string类型的值,然后再获取指向它的指针类型值。
(*string)(v) //把变量v的值转换为指针类型*string的值。
<-chan int(v) //等同于 <-(chan int(v)),先将变量v代表的值转换为chan int类型的值,然后再从此通道类型值中接收一个int类型的值。
(<-chan int)(v)//把变量v的值转换为通道类型<-chan int的值。
func()(v)//Go语言理解为任何无参数声明但有一个结果声明的匿名函数。
(func())(v)//把变量v的值转换为函数类型func()的值。
func() int(v)//等同于(func() int)(v),把v的值转换成一个有结果声明的函数的类型。
对于常量 x,如果它能够被转换为类型 T 的值,那么它们符合如下情况:
x 可以被类型T的值代表。例如,iota 可以表示一个大于等于零的整数常量。他可以把 uint 类型的值代表。类型表达式 uint(iota) 是合法的,它的结果值会是一个 uint 类型的常量。
x 是一个浮点数常量,T 是一个浮点数类型,并且 x 在(根据IEEE-754标准中描述的向偶数舍入规则)被舍入之后可以被类型 T 的值代表。例如:
float32(0.49999998) //求值结果是一个float32类型的常量0.5
如果 x 是一个整数常量,并且 T 是一个 string 类型,那么将会遵循一套规则来决定类型转换的结果。它同样适合于非常量的值。这将在后面的与 string类型相关的转换处讲解。
对于非常量 x,它能够被转换为类型 T 的值,那么它们符合如下情况:
值 x 可以被赋值给类型 T 的变量。例如:type Computer interface { CpuType() string } type Laptop struct { cpuType string } func (self Laptop) CpuType() string { return self.cpuType }
类型转换表达式:
Computer(Laptop{cpuType: "Intel Core i5"})//合法,求值结果会是一个Computer类型的值。因为类型Laptop是接口类型Computer的一个实现类型
值 x 的类型和类型 T 的潜在类型是相等的。例如:
type MyString string //类型转换表达式 MyString("Huazie")//合法,类型MyString的潜在类型就是string类型。
值 x 的类型和类型 T 都是未命名的指针类型,并且它们的基本类型(指向那个值的类型)的潜在类型是相等的。例如:
var str1 string // 类型转换表达式 (*string)(&str1)//合法,求值结果是一个*MyString类型的值。
值 x 的类型和类型 T 都是整数类型或都是浮点数类型。例如:
var i32 uint32 var f32 folat32 //类型转换表达式 int64(i32)//合法。 float64(f32)//合法。
值 x 的类型和类型 T 都是复数类型。例如:
var comp64 complex64 //类型转换表达式 complex128(comp64)//合法。
值 x 是一个整数类型值或是一个元素类型为 byte 或 rune 的切片类型值,且 T 是一个 string 类型。例如:
string([]byte{'a'})//合法,求值结果是string类型值"a"。
值 x 是一个 string 类型值,且T是一个元素类型为 byte 或 rune 的切片类型。
[]rune("Huazie")//合法
特殊的类型转换规则:
(1)数值类型之间的转换
可以通过常量声明或者数据类型转换把一个int类型的变量,数值常量1024是无类型的,例如:
var number int = 1024
或把数值常量 1024 赋给一个 int 类型的变量:
int(1024)
对于非常量的数值类型值,规则如下:
当把一个整数类型值从需要较少二进制位表示的整数类型转换到需要较多二进制位表示的整数类型(比如从 int8 类型转换到 int16 类型)的时候 : 如果这个整数类型值是有符号的,那么该符号位上的(最左边的)那个二进制值将作为扩展项填充在转换过程中新增的那些二进制位上,否则将会把 0 作为扩展项进行填充。这种扩展方式是针对整数类型值的补码而言的。例如:int16 类型值 -32767 的十六进制表示是 0xffff 。它的补码是 0x8001 。此补码最左边的二级制位上的二级制值是 1。如果要把这个 int16 类型值转换为 int32 类型值,就需要用最左边的这个值 1 填充在高位一侧新增的那16个二进制位上。类型转换之后的补码是 0xffff8001 。在这个补码之上再求其补码以得其原码,即 0x80007fff 。此原码表示的就是十进制数 -32767 ,类型转换前的那个数值相等。
当把一个整数类型值从需要较多二级制位表示的整数类型转换到需要较少二级制位表示的整数类型的时候,需要把多余的若干个较高位置的二进制值裁掉,而只保留与目标类型所需二进制位数相当的若干个较低位置的二进制值。例如,int16 类型值 -32767,如果要把它转换为一个 int8 类型值,就需要对其补码 0x8001 截取较低 8 为的二进制值,得到 0x01。由于此值的最左边的二进制位上是 0,所以它本身就是类型转换总会得到一个有效的数值。但对于整数常量来说,这样的类型转换就会造成一个编译错误。例如,类型转换表达式 int8(-32767) 会使编译器报错,因为整数常量 -32767 超出了 int8 类型所能表示的数值范围。
当把一个浮点数类型值向整数类型值进行转换的时候,该浮点数类型值的小数部分将被抹去。例如,如果有一个 float32 类型的变量 f32 且其值为 -32767.345 ,那么类型表达式 int32(f32) 的求值结果为 -32767 。如果浮点数类型值在被抹去小数位之后超出了目标整数类型的表示范围,那么该值还会被截短。例如,在类型表达式 int8(f32) 被求值的过程中会首先 float32 类型值 -32767.345 的小数部分却去掉,然后再将其中较高的 24 位的二进制值截掉,最终得到结果 1 。
当把一个整数或浮点数转换为一个浮点数类型的值或者把一个复数转换为一个复数类型的值的时候,该值将会被依据目标类型的精度进行舍入操作。例如,在 float32 类型的变量 x 中存储的值可能会超出 IEEE-754 标准中规定的 32 位(二进制值代表的)浮点数的精度。但是,类型表达式 float32(x) 的求值结果一定会是 x 的值向32位浮点数的精度转化之后的值。算术表达式 x + 0.1 的结果值可能会超出32位浮点数的精度,但是类型转换表达式 float32(x + 0.1) 的求值结果却不会这样。
在非常量的浮点数类型值或复数类型值的类型转换中,当目标类型的精度不能够满足被转换的值的需要的时候,虽然转换会成功,但其结果将是不确定的,这依赖于不同平台的Go语言的具体实现。
(2)与string类型相关的转换
当把一个有符号整数值或无符号整数值向字符串类型转换的时候,将会产生出一个字符串类型值。被转换的整数值应该是一个有效的 Unicode 代码点的代表。在作为结果的字符串类型值中的就是那个 Unicode 代码点对应的字符。在底层,这个字符串类型值是由该 Unicode 代码点的 UTF-8 编码值表示的。如果被转换的整数值不能代表一个有效的 Unicode 代码点,那么转换结果将会是“\ufffd”,即 Unicode 字符“�”。例如:
string(0x4e2d)//求值结果为“中”,其UTF-8编码为\xe4\xb8\xad string('国') //求值结果值为“国”,其UTF-8编码为\xe5\x9b\xbd string(-1) //求值结果为“�”,整数值-1不能代表一个有效的Unicode代码点。
如果有一个目标类型是 string 类型的别名类型是 MyString,那么可以将它视同为 string 类型。例如:
MyString(0x4e2d)//等同于string(0x4e2d)
当把一个元素类型为 byte 的切片类型值向字符串类型转换时,将会产生出一个字符串类型值。这个字符串类型值实际上就是由被转换的切片类型值中的每个字节类型值依次组合而成的。如果切片类型值为 nil,那么类型转换的结果将会是“”。例如:
string([]byte{'g', '\x6f', '\x6c', '\x61', 'n', 'g'})//求值结果是"golang"
由于使用“\x”为前导并后跟两位十六进制数可以表示宽度为一个字节的值,因此一个字节类型的值也就可以由这种方法表示。如果源类型是一个 []byte 类型的别名类型,那么可以将它视同为 []byte 类型。
当把一个元素类型为 rune 的切片类型值向字符串类型转换时,将会产生出一个字符串类型值。这个字符串类型值实际上就是依次串联每个 rune 类型值后的结果。如果切片类型值为 nil,那么类型转换的结果将会是“”。例如:
string([]rune{ 0x4e2D, 0x56fd })//求值结果是"中国"
如果源类型是一个 []rune 类型的别名类型,那么我们可以将它视同为 []rune 类型。
当把一个字符串类型值向 []byte 类型转换时,其结果将会是把该字符串类型值按字节拆分后的结果。对于“”来说,转换后的结果一定是 []byte 类型的空值 nil 。例如:
[]byte("hello")//结果是[]byte{104, 101, 108, 108, 111}
在这个 []byte 类型值中的每个元素都是对应字符的ASCII编码值的十进制表示形式。如果目标类型是一个 []byte 类型的别名类型,那么可以将它视同为 []byte 类型。
当把一个字符串类型值向 []rune 类型转换时,其结果将会是把该字符串类型值按字符拆分后的结果。对于“”来说,转换后的结果一定是 []rune 类型的空值 nil 。
[]rune("中国") //结果是[]byte{20013, 22269}
在这个 []rune 类型值中的每个元素都是对应字符的 Unicode 代码点的十进制表示形式。如果目标类型是一个 []rune 类型的别名类型,那么可以将它视同为 []rune 类型。
UTF-8 这种编码方式会把一个字符编码为一个或多个字节。对于同一个字符串类型值来说,与它对应的字节序列和字符序列中的元素并不一定是一 一对应的。字节序列中的单个字节并不一定能代表一个完整的字符。例如,以字符串类型值“中国”为例:
[]byte{228, 184, 173, 229, 155, 189}//字节序列的前三个元素代表了字符'中'的UTF-8编码值,而后三个元素则代表了字符'国'的UTF-8编码值。
[]byte{20013, 22269}//这个字符序列中的第一个元素代表了字符'中'的Unicode代码点,而第二个元素则代表了字符'国'的Unicode代码点。
对于每一个ASCII编码可表示的字符来说,它的 Unicode 代码点和 UTF-8 编码值与其 ASCII 编码值都分别是一致的,且它们都可以由一个字节类型值代表。对于一个包含了 ASCII 编码可表示的字符的字符串类型值来说,与它对应的字节序列和字符序列中的元素值必定也是一一对应的。
byte 类型值和 rune 类型值都属于整数值的一种。所有整数值都可以由十进制字面量、八进制字面量和十六进制字面量来代表。可以把任意一种方式表示的 rune 字面量赋给任何整数类型的变量,只要该 rune 字面量对应的 Unicode 代码点不超出那个整数类型的表示范围。例如:
var nation int16 = '国' // '国' == 0x56fd == 22269
[]byte{ 'g', '\x6f', '0x6c', '\u0061', '\156', '\U00000067' } //求值结果是"golang"
(3)别名类型值之间的转换
类型是 MyString 是 string 类型的别名类型。如果一个整数值分别转换为这两个类型的值,将会得到相同的结果。把一个字符串字面量赋给 MyString 类型的变量:
var ms MyString = "中国"
在 MyString 类型的值之上应用切片操作:
ms[1]
在某个数据类型和它的别名类型之间以及同一个数据类型的多个别名类型之间的类型转换是合法的。并且,在这种类型转换的过程中并不会创造出新的值,而仅仅是变换了一下那个已存在的值的所属类型。
4.5内建函数
所谓内建函数,就是Go语言内部预定义的函数。调用它们的方式与调用普通函数并无差异,并且在使用它们之前也不需要导入任何代码包。这里并不能把内建函数当做值来使用。因为它们并不像普通函数那样有隶属的Go语言数据类型。
1.close函数
内建函数 close 只接受通道类型的值(简称通道)作为参数。例如:
ch := make(chan int, 1)
close(ch)
调用这个 close 函数之后,会使作为参数的通道无法再接受任何元素值。若试图关闭一个仅能接受元素值的通道,则会造成一个编译错误。在通道被关闭之后,再向它发送元素值或者试图再次关闭它的时候,都会引发一个运行时恐慌。试图关闭一个为 nil 的通道值也会引发一个运行时恐慌。
试图调用 close 函数关闭一个通道,并不会影响到在此调用之前已经发送的那些元素值,它们会被正常接收(如果存在接收操作的话)。但是,在此调用之后,所有的接收操作都会立即返回一个该通道的元素类型的零值。
2.len函数与cap函数
len函数的使用方法
参数类型 | 结果 | 备注 |
---|---|---|
string | string类型值的字节长度 | 无 |
[n]T或*[n]T | 数组类型值长度,它等于n | n代表了数组类型的长度,T代表了数组类型的元素类型 |
[]T | 切片类型值的长度 | T代表了切片类型的元素类型 |
map[K]T | 字典类型值的长度,其中已包含的键的数量 | K代表了字典类型的键类型,T代表了字典类型的元素类型 |
chan T | 通道类型值当前包含的元素的数量 | T代表了通道类型的元素类型 |
cap函数的使用方法
参数类型 | 结果 | 备注 |
---|---|---|
[n]T或*[n]T | 数组类型值长度,它等于n | n代表了数组类型的长度,T代表了数组类型的元素类型 |
[]T | 切片类型值的容量 | T代表了切片类型的元素类型 |
chan T | 通道类型值的容量 | T代表了通道类型的元素类型 |
对于一个切片类型值来说,它的长度和容量的关系:
0 <= len(s) <= cap(s)
一个切片值的容量就是它拥有的那个底层数组的长度。这个底层数组的长度必定不会小于该切片值的长度。
值为 nil 的切片类型值、字典类型和通道类型值的长度都是 0 。值为 nil 的切片类型值和通道类型值的容量也都是 0 。
如果 s 是一个 string 类型的常量,那么表达式 len(s) 和 cap(s) 也都等同于常量。len(s) 所代表的值在编译期间就会被计算出来。如果 s 是一个表达式,且其类型是数组类型或指向数组类型的指针类型,那么只要该表达式中不包含通道接收操作和函数调用操作,它就不会被求值。因为 s 的类型中已经包含了它的长度信息。在对表达式 len(s) 和 cap(s) 进行求值的时候并不需要求得 s 的结果值而只需要从 s 的类型中取得其长度即可。在这种情况下,这两个表达式也会等同于常量。
3.new函数和make函数
这个参考Go语言学习笔记5中所讲的即可。
4.append函数和copy函数
append函数和copy函数都被用于辅助在切片类型值之上的操作,这个参考Go语言学习笔记3中所讲的切片类型即可。
5.delete函数
内建函数 delete 专用于删除一个字典类型值中的某个键值对。它接受两个参数,第一个参数是作为目标的字典类型值,而第二个参数则是能够代表要删除的那个键值对的键。例如:
delete(m, k)
这里有两点需要注意:
(1)第二个参数 k 与 m 的键的类型之间必须满足赋值规则。
(2)当 m 的值是 nil 或者 k 所代表的键值对并不存在于 m 中的时候,delete(m, k) 不会做任何操作。在没有可删除的目标的时候,删除操作将被忽略。这种删除失败不会被反馈。
6.complex 函数、real 函数和 imag 函数
这3个内建函数都是专用于操作复数类型值的。
complex 函数被用于根据浮点数类型的实部和虚部来构造复数类型。例如:
var cplx128 complex128 = complex(2, -2)
内建函数 real 和 imag 则分别被用于从一个复数类型值中抽取浮点数类型的实部部分和浮点数类型的虚部部分。例如:
var im64 = imag(cplx128)
var r64 = real(cplx128)
对于 complex 函数来说,两个参数的类型必须是同一种浮点数类型,并且其结果类型与参数类型对应。在Go语言中,对于复数有一个恒等式:
z == complex(real(z), imag(z)) //z是一个复数类型的变量
注意: 如果 complex 函数的两个参数都没有显示的类型,那么该函数的结果的类型将会是 complex128 类型的。如果 complex 函数的参数都是常量,那么它的结果值也必是常量。
7.panic函数和recover函数
内建函数 panic 函数和 recover 函数分别被用于报告和处理运行时恐慌。
函数 panic 只接受一个参数。这个参数可以是任意类型的值。按照惯例,panic 函数的实际参数的类型常常是接口类型 error 的某个实现类型。panic 函数的参数都应该足以表示恐慌发生时的异常情况。
函数 recover 不接受任何参数,但是返回一个 interface{} 类型的结果值。interface{} 就是空接口。所有的数据类型都是它的实现类型。因此,recover 函数的结果值可能是任何类型的。这是与 panic 函数的那个唯一参数相对应的,它们都是 interface{} 类型的。如果运行时恐慌的报告是通过调用 panic 函数来进行的话,那么之后调用 recover 函数所得的结果值就应该是先前 panic 函数在被调用时接受的那个参数值。recover 函数的结果值也有可能 nil 。如果是 nil,属于以下的情况:
- 传递给panic函数的参数值就是nil。
- 运行时恐慌根本就没有发生。狭义的讲,panic函数没有被调用。
- 函数recover并没有在defer语句中被调用。
在任何情况下任何位置上调用 recover 函数都不会产生任何副作用。如果不用它来处理运行时恐慌,那么对它的调用也就没有任何意义了。panic 函数和 recover 函数之间肯定是存在着某种联系,在后面的博文中将对异常报告和处理的更多细节进行讲解。
8.print函数和println函数
函数 print 的作用是依次(即从左到右)打印出传递给它的参数值,每个参数值对应的打印内容都由它们的具体实现决定。而函数 println 函数则会在 print 函数打印的内容的基础上再在每个参数之间加入空格,并在最后加入换行符。例如:
print("A", 12.4, 'R', "C")
println("A", 12.4, 'R', "C")
调用表达式被求值之后,出现内容:
A+1.240000e+00182CA +1.240000e+001 82 C
对于上面的这两个函数,有以下需要注意:
它们接受的参数只能是有限的数据类型的值。并且,在这些受支持的数据类型当中。大部分都是Go语言的基础数据类型。
这两个函数针对于每种受支持的数据类型的打印格式都是固定的,无法自定义。
Go语言并不保证会在以后的版本中一直保留这两个函数。因此,尽量不要在程序中使用这两个函数,尤其是用于生产环境的程序。应该使用标准库代码包 fmt 中的函数 Print 和 Println 来替代它们。
至此,Go语言数据的使用就讲完了,下篇博文将要介绍Go语言流程控制方法。
最后附上知名的Go语言开源框架(每篇更新一个):
etcd: 一个高可用的键值存储系统。它可被用于建立共享配置系统和服务发现系统。它的灵感来自于Apache ZooKeeper。我们可以在https://github.com/coreos/etcd上找到它的源码。
有疑问加站长微信联系(非本文作者)