GO语言基础学习——数据类型
整型
Go语言同时提供了有符号和无符号的整数类型,其中包括int8, int16, int32, int64四种大小截然不同的有符号整数类型,分别对应8,16,32,64 bit (二进制位)大小的有符号整数,与此对应的是uint8, uint16, uint32和unit64四种无符号整数类型。
此外还有两种整数类型int和unit,它们分别对应特定CPU平台的字长(机器字大小),其中int表示有符号整数,应用最为广泛,uint表示无符号整数,实际开发中由于编译器和计算机硬件的不同,int和unit所能表示的整数大小会在32bit和64bit之间变化。
大多数情况下,我们只需要int一种整型即可,它可以用于循环计数器(for循环中控制循环次数的变量)、数组和切片的索引,以及任何通用目的的整型运算符,通常int类型的处理速度也是最快的。
用来表示Unicode字符的rune类型和int32类型是等价的,通常用于表示一个Unicode码点,这两个名称可以互换使用。同样,byte和unit8也会等价类型,byte类型一般用于强调数值是一个原始的数据而不是一个小的整数。
尽管在某些特定的运行环境下int, uint和unitptr的大小可能相等,但是它们依然是不同的类型,比如int和int32,虽然int类型的大小也可能是32bit, 但是在需要把int类型当作int32类型使用的时候必须显示的对类型进行转换,反之亦然。
Go语言有符号整数采用2的补码形式表示,也就是最高bit位用来表示符号位,一个n-bit的有符号的取值范围从 -2(n-1) 到 2(n-1)-1。无符号整数的所有bit位都用于表示非负数,取值范围是0 到 2n-1。例如,int8 类型整数的取值范围是从 -128 到 127,而 uint8 类型整数的取值范围是从 0 到 255。
浮点类型
一个float类型的浮点数可以提供大约6个十进制的精度,而 float64 则可以提供15个十进制的精度,通常应该优先使用float64类型,因为float32类型的累积计算误差很容易扩散,并且float32能精确表示的正整数并不是很大。
复数
在计算机中,复数是由两个浮点数表示的,其中一个表示实部(real), 一个表示虚部(imag)。
Go语言中复数的类型有两种,分别是complex128(64位实数和虚数)和complex64(32位实数和虚数),其中complex128为复数的默认类型。
复数的值由三部分组成RE+IMi,其中RE是实数,IM是虚数部分,RE和IM均为float类型,而最后的i是虚数单位。
声明复数的语法格式如下所示:
var name complex128 = complex(x, y)
其中name为复数的变量名,complex128为复数的类型,“=”后面的complex为GO语言的内置函数用于为复数赋值,x, y分别表示构成该复数的两个float64类型的数值,x为实部,y为虚部。
上面的声明语句也可以简化为下面的形式:
name := complex(x,y)
对于一个复数z:=complex(x,y),可以通过GO语言的内置函数real(z)来获得该复数的实部,也就是x; 通过imag(z)获得该复数的虚部,也就是y
复数也可以用==和!=进行相等比较,只有两个复数的实部和虚部都相等的时候它们才是相等的。
Go语言内置的 math/cmplx 包中提供了很多操作复数的公共方法,实际操作中建议大家使用复数默认的 complex128 类型,因为这些内置的包中都使用 complex128 类型作为参数。
布尔型
一个布尔类型的值只有两种:true或false。if和for语句的条件部分都是布尔类型的值,并且==和<等比较操作也会产生布尔型的值。
Go语言对于值之间的比较有非常严格的限制,只有两个相同类型的值才可以进行比较,如果值的类型是接口(interface),那么它们也必须都实现了相同的接口。如果其中一个值是常量,那么另外一个可以不是常量,但是类型必须和该常量类型相同。如果以上条件都不满足,则必须将其中一个值的类型转换为和另外一个值的类型相同之后才可以进行比较。
布尔值可以和&&(AND)和||(OR)操作符结合,并且有短路行为,如果运算符左边的值已经可以确定整个布尔表达式的值,那么运算符右边的值不再被求值,因此下面的表达式总是安全的:
s != "" && s[0] == 'x'
其中s[0]操作如果应用于空字符串将会导致panic异常
因为&&的优先级比||高(&&对应逻辑乘法,||对应逻辑加法,乘法比加法的优先级高)。
布尔值并不会隐式转换为数字值0或1,反之亦然,必须使用if语句显示的进行转换器。
Go语言不允许将整型强制转换成布尔型,会编译错误。布尔值无法参与数值计算,也无法与其他类型进行转换。
Go语言字符串
一个字符串是一个不可改变的字节序列,字符串可以包含任意的数据,但是通常是用来包含可读的文本,字符串是UTF-8字符的一个序列(当字符为ASCII码表傻姑娘的字符时则占用一个字节,其他字符根据需要占用2-4个字节)。
UTF-8是一种被广泛使用的编码格式,是文本文件的标准编码,其中包括XML和JSON在内也都使用该编码。由于该编码对占用字节长度的不定性,在Go语言中字符串也可能根据需要占用1至4个字节。这与其他编程语言如C++,Java或者Python不同(Java始终使用2个字节)。Go语言这样做不仅减少了内存和磁盘空间占用,同时也不用像其他语言那样需要对使用UTF-8字符集的文本进行编码和解码。
字符串是一种值类型,且值不可变,即创建某个文本后无法再次修改这个文本的内容,更深入地讲,字符串是字节的定长数组。
1. 定义字符串
可以使用双引号""来定义字符串,字符串中可以使用转义字符来实现换行、缩进等效果,常用的转义字符包括:
\n : 换行符
\r :回车符
\t:tab键
\u或\U:Unicode字符
\ : 反斜杠自身
一般的比较运算符(==, !=, <, <=, >=, >)是通过在内存中按字节比较来实现字符串比较的,因此比较的结果是字符串自然编码的顺序。字符串所占的字节长度可以通过函数len()来获取,例如len(str)
字符串的内容(纯字节)可以通过标准索引法来获取,在方括号[]内引入索引,索引从0开始计数:
字符串stri的第一个字节:str[0]
第i个字节:str[i - 1]
最后1个字节:str[len(str) - 1]
需要注意的是,这种转换方案只对纯ASCII码的字符串有效。
注意:获取字符串中某个字节的地址属于非法行为,例如&str[i]
2. 字符串拼接符"+"
两个字符串s1和s2可以通过s := s1 + s2拼接在一起。将s2追加到s1尾部并生成一个新的字符串s.
str := "Beginning of the string " +
"second part of the string"
提示:因为编译器会在行尾自动补全分号,所以拼接字符串用的加号“+”必须放在第一行末尾。
s := "hel" + "lo,"
s += "world!"
3. 字符串实现基于UTF-8编码
Go语言中字符串的内部实现使用UTF-8编码,通过rune类型,可以方便地对每个UTF-8字符进行访问。当然,Go语言也支持按照传统的ASCII码方式逐字符进行访问。
4. 定义多行字符串
在Go语言中,使用双引号书写字符号的方式是字符串常见表达方式之一,被称为字符串字面量,这种双引号字面量不能跨行,如果想要在源码中嵌入一个多行字符串时,就必须使用`反引号。
const str = `第一行
第二行
第三行
\r\n`
fmt.Println(str)
两个反引号间的字符串将被原样赋值到str变量中。
在这种方式下,反引号间换行将被作为字符串中的换行,但是所有的转义字符均无效,文本将会被原样输出。
Go语言字符类型(byte和rune)
字符串中的每一个元素叫做“字符”,在遍历或单个获取字符串元素时可以获得字符。
Go语言的字符有以下两种:
一种是unit8类型,或者叫byte型,代表了ASCII码的一个字符。
另一种是runeL类型,代表一个UTF-8字符,当需要处理中文、日文或者其他符合字符时,则需要用到rune字符。rune类型等价于int32类型。
byte类型时unit8的别名,对于只占用1个字节的传统ASCII编码的字符来说,完全没有问题,例如var ch byte = 'A',字符使用单引号括起来。
UTF-8和Unicode有何区别?
Unicode与ASCII类似,都有一个字符集。
字符集为每个字符分配一个唯一的ID,我们使用到的所有字符在Unicode字符集中都有一个唯一的ID,例如上面例子中的a在Unicode与ACS II中编码都是97。汉字“你”在Unicode中的编码为20320, 在不同国家的字符集中,字符所对应的ID也会不同。而无论任何情况下,Unicode中的字符的ID都是不会变化的。
UTF-8是编码规则,将Unicode中字符的ID以某种方式进行编码,UTF-8的是一种变长编码规则,从1到4字节不等。编码规则如下:
0xxxxxx 表示文字符号 0~127,兼容 ASCII 字符集。
从 128 到 0x10ffff 表示其他字符。
根据这个规则,拉丁文语系的字符编码一般情况下每个字符占用一个字节,而中文每个字符占用3个字节。
广义的Unicode指的是一个标准。它定义了字符集及编码规则,即Unicode字符集和UTF-8,UTF-16编码等。
Go语言数据类型转换
由于Go语言不存在隐式类型转换,因此所有的类型转换都必须显示的声明:类型B的值 = 类型B(类型A的值)
a := 5.0
b := int(a)
类型转换只能定义正确的情况下转换成功,例如从一个取值范围较小的类型转换到一个取值范围较大的类型(将int16转换为int32)。当从一个取值范围较大的类型转换到取值范围较小的类型时(将int32转换为int16或将float32转换为int),会发生精度丢失(截断)的情况。
只有相同底层类型的变量之间可以进行相互转换,不同底层类型的变量相互转换时会引发编译错误(如将bool类型转换为int类型)。