Go 语言的基本数据类型
0)变量声明
var 变量名字 类型 = 表达式
例:
var num int = 10
其中“类型”或“= 表达式”两个部分可以省略其中的一个。
- 1)根据初始化表达式来推导类型信息
- 2)默认值初始化为0。
例:
var num int // var num int = 0 var num = 10 // var num int = 10
1)整型
1.1)整型类型
类型名称 | 有无符号 | bit数 |
---|---|---|
int8 | Yes | 8 |
int16 | Yes | 16 |
int32 | Yes | 32 |
int64 | Yes | 64 |
uint8 | No | 8 |
uint16 | No | 16 |
uint32 | No | 32 |
uint64 | No | 64 |
int | Yes | 等于cpu位数 |
uint | No | 等于cpu位数 |
rune | Yes | 与 int32 等价 |
byte | No | 与 uint8 等价 |
uintptr | No | - |
rune 类型是 Unicode 字符类型,和 int32 类型等价,通常用于表示一个 Unicode 码点。rune 和 int32 可以互换使用。
byte 是uint8类型的等价类型,byte类型一般用于强调数值是一个原始的数据而不是 一个小的整数。
uintptr 是一种无符号的整数类型,没有指定具体的bit大小但是足以容纳指针。 uintptr类型只有在底层编程是才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。
不管它们的具体大小,int、uint和uintptr是不同类型的兄弟类型。其中int和int32也是 不同的类型, 即使int的大小也是32bit,在需要将int当作int32类型的地方需要一个显式 的类型转换操作,反之亦然。
有符号整数采用 2 的补码形式表示,也就是最高 bit 位用作表示符号位,一个 n bit 的有 符号数的值域是从 -2^{n-1} 到 2^{n-1}−1。例如,int8类型整数的值域是从-128 到 127, 而uint8类型整数的值域是从0到255。
1.2)整型运算
二元运算符:算术运算、逻辑运算和比较运算,运算符优先级从上到下递减顺序排列
* / % << >> & &^ + - | ^ == != < <= > >= && ||
在同一个优先级,使用左优先结合规则,但是使用括号可以明确优先顺序。
算术运算符+、-、*和/可以适用与于整数、浮点数和复数,但是取模运算符%仅用于整数间的运算。 % 取模运算符的符号和被取模数的符号总是一致的。除法运算符/的行为则依赖于操作数是否 全为整数,比如5.0/4.0的结果是1.25,但是5/4的结果是1,因为整数除法会向着0方向截断余数。
两个相同的整数类型可以使用下面的二元比较运算符进行比较;比较表达式的结果是布尔类型。
== equal to != not equal to < less than <= less than or equal to > greater than >= greater than or equal to
个算术运算的结果,无论有无符号,超出的高位的bit位部分将被丢弃。如果原始的数值是有符号类型,而且最左边 的bit为是1的话,那么最终结果可能是负的。
布尔型、数字类型和字符串等基本类型都是可比较的,也就是说两个相同类型的值可以用 == 和 != 进行比较。
一元的加法和减法运算符:
+ 一元加法 (无效果) - 负数
bit位操作运算符:
符号 | 操作 | 操作数是否区分符号 |
---|---|---|
& | 位运算 AND | No |
| | 位运算 OR | No |
^ | 位运算 XOR | No |
&^ | 位清空 (AND NOT) | No |
<< | 左移 | Yes |
>> | 右移 | Yes |
注意 位操作运算符^作为二元运算符时是按位异或(XOR),当用作一元运算符时表示按位取反。
位操作运算符&^用于按位置零(AND NOT):对于表达式z = x &^ y, 如果对应y中某位bit位为 0 的话,结果z的对应的bit位等于x相应的bit位的值,否则 z 对应的bit位为0。
x << n 和x >> n 的右操作数(n)必须为无符号数。
操作 | 含义 | -- |
---|---|---|
<< | 左移 | 左移运算用零填充右边空缺的bit位 |
>> | 右移 | 无符号数的右移运算用0填充左边空缺的bit位,有符号数的右移运算用符号位的值填充左边空缺的bit位 |
一般来说,需要一个显式的转换将一个值从一种类型转化位另一种类型,并且算术和逻辑运算的二元操 作中必须是相同的类型。虽然这偶尔会导致需要很长的表达式,但是它消除了所有和类型相关的问题, 而且也使得程序容易理解。
许多整形数之 间的相互转换并不会改变数值;它们只是告诉编译器如何解释这个值。但是对于将一个大尺寸的整数类 型转为一个小尺寸的整数类型,或者是将一个浮点数转为整数,可能会改变数值或丢失精度。 浮点数到整数的转换将丢失任何小数部分,然后向数轴零方向截断。
任何大小的整数字面值都可以用以0开始的八进制格式书写,例如0666;或用以0x或0X开头的十六进制格 式书写,例如0xdeadbeef。十六进制数字可以用大写或小写字母。
1.3)浮点数
Go语言提供了两种精度的浮点数,float32和float64。它们的算术规范由IEEE754浮点数国际标准定义, 该浮点数规范被所有现代的CPU支持。
这些浮点数类型的取值范围可以从很微小到很巨大。浮点数的范围极限值可以在math包找到。常量 math.MaxFloat32表示float32能表示的最大数值,大约是 3.4e38;对应的math.MaxFloat64常量大约是 1.8e308。它们分别能表示的最小值近似为1.4e-45和4.9e-324。
一个float32类型的浮点数可以提供大约6个十进制数的精度,而float64则可以提供约15个十进制数的精度。
函数math.IsNaN用于测试一个数是否是非数NaN,math.NaN则返回非数对应的值。虽然可以用math.NaN来 表示一个非法的结果,但是测试一个结果是否是非数NaN则是充满风险的,因为NaN和任何数都是不相等的。 如果一个函数返回的浮点数结果可能失败,最好的做法是用单独的标志报告失败。
nan := math.NaN() fmt.Println(nan == nan, nan < nan, nan > nan) // "false false false"
1.4)复数
Go语言提供了两种精度的复数类型:complex64和complex128,分别对应float32和float64两种浮点数精度。内置的complex函数用于构建复数,内建的real和imag函数分别返回复数的实部和虚部。
z := x + yi x = real(z) y = imag(z)
复数也可以用==和!=进行相等比较。只有两个复数的实部和虚部都相等的时候它们才是相等的。 math/cmplx包提供了复数处理的许多函数,例如求复数的平方根函数和求幂函数。
1.5)布尔型
一个布尔类型的值只有两种:true和false。if和for语句的条件部分都是布尔类型的值,并且==和<等比 较操作也会产生布尔型的值。一元操作符!对应逻辑非操作,因此!true的值为false。
布尔值可以和&&(AND)和||(OR)操作符结合,并且可能会有短路行为:如果运算符左边值已经可以确 定整个布尔表达式的值,那么运算符右边的值将不在被求值
布尔值并不会隐式转换为数字值0或1,反之亦然。必须使用一个显式的if语句辅助转换。
1.6)字符串
一个字符串是一个不可改变的字节序列。字符串可以包含任意的数据,包括byte值0,但是通常是用来包 含人类可读的文本。文本字符串通常被解释为采用UTF8编码的Unicode码点(rune)序列。
内置的len函数可以返回一个字符串中的字节数目(不是rune字符数目),索引操作s[i]返回第i个字节 的字节值,i必须满足0 <= i < len(s)条件约束。如果试图访问超出字符串索引范围的字节将会导致panic异常。
第i个字节并不一定是字符串的第i个字符,因为对于非ASCII字符的UTF8编码会要两个或多个字节。
子字符串 操作s[i:j]基于原始的s字符串的第i个字节开始到第j个字节(并不包含j本身)生成一个新字 符串。生成的新字符串将包含j-i个字节。不管i还是j都可能被忽略,当它们被忽略时将采用0作为开始位置,采用len(s)作为结束的位置。
其中+操作符将两个字符串链接构造一个新字符串。
字符串可以用==和<进行比较;比较通过逐个字节比较完成的,因此比较的结果是字符串自然编码的顺 序。
字符串值也可以用字符串面值方式编写,只要将一系列字节序列包含在双引号即可。
字符串的值是不可变的:一个字符串包含的字节序列永远不会被改变,当然我们也可以给一个字符串变 量分配一个新字符串值。
s := "left foot" t := s s += ", right foot"
这并不会导致原始的字符串值被改变,但是变量s将因为+=语句持有一个新的字符串值,但是t依然是包 含原先的字符串值。
在一个双引号包含的字符串面值中,可以用以反斜杠\开头的转义序列插入任意的数据。
符号 | 含义 |
---|---|
\a | 响铃 |
\b | 退格 |
\f | 换页 |
\n | 换行 |
\r | 回车 |
\t | 制表符 |
\v | 垂直制表符 |
\' | 单引号 (只用在 '\'' 形式的rune符号面值中) |
\" | 双引号 (只用在 "..." 形式的字符串面值中) |
\\ | 反斜杠 |
可以通过十六进制或八进制转义在字符串面值包含任意的字节。一个十六进制的转义形式是\xhh,其中两个h表示十六进制数字(大写或小写都可以)。一个八进制转义形式是\ooo,包含三个八进制的o数字(0到7),但是不能超过\377(\377为十进制的255)。每一个单一的字节表达一个特定的值。
原生字符串
一个原生的字符串面值形式是...,使用反引号代替双引号。在原生的字符串面值中,没有转义操作;全部的内容都是字面的意思,包含退格和换行,因此一个程序中的原生字符串面值可能跨越多行。唯一的特殊处理是会删除回车以保证在所有平台上的值都是一样的。
Unicode
Unicode( http://unicode.org )收集了这个世界上所有的符号系统,包括重音符号和其它变音符号,制表符和回车符,还有很多神秘的符号,每个符号都分配一个唯一的Unicode码点,Unicode码点对应Go语言中的rune整数类型。 通用的表示一个Unicode码点的数据类型是int32,也就是Go语言中rune对应的类型;它的同义词rune符文正是这个意思。
UTF-8
UTF8是一个将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) 110xxxxx 10xxxxxx 128-2047 (values <128 unused) 1110xxxx 10xxxxxx 10xxxxxx 2048-65535 (values <2048 unused) 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 65536-0x10ffff (other values unused)
Go语言的源文件采用UTF8编码,并且Go语言处理UTF8编码的文本也很出色。unicode包提供了诸多处理 rune字符相关功能的函数(比如区分字母和数组,或者是字母的大写和小写转换等),unicode/utf8包 则提供了用于rune字符序列的UTF8编码和解码的功能。
Go语言字符串面值中的Unicode转义字符让我们可以通过Unicode码点输入特殊的字符。有两种形式:\uhhhh对应16bit的码点值,\Uhhhhhhhh对应32bit的码点值,其中h是一个十六进制数字。每一个对应码点的UTF8编码。
例如:下面的字母串面值都表示相同的值:
"世界" "\xe4\xb8\x96\xe7\x95\x8c" "\u4e16\u754c" "\U00004e16\U0000754c"
Unicode转义也可以使用在rune字符中。下面三个字符是等价的: '世' '\u4e16' '\U00004e16'
对于小于256码点值可以写在一个十六进制转义字节中,例如'\x41'对应字符'A',但是对于更大的码点则必须使用\u或\U转义形式。因此,'\xe4\xb8\x96'并不是一个合法的rune字符,虽然这三个字节对应一个有效的UTF8编码的码点。
得益于UTF8编码优良的设计,诸多字符串操作都不需要解码操作。我们可以不用解码直接测试一个字符串是否是另一个字符串的前缀、是后缀、或者是包含子串测试。对于UTF8编码后文本的处理和原始的字节处理逻辑是一样的。
每一个UTF8字符解码,不管是显式地调用utf8.DecodeRuneInString解码或是在range循环中隐式地解码,如果遇到一个错误的UTF8编码输入,将生成一个特别的Unicode字符'\uFFFD',在印刷中这个符号通常是一个黑色六角或钻石形状,里面包含一个白色的问号(?)。
字符串和Byte切片
标准库中有四个包对字符串处理尤为重要:bytes、strings、strconv和unicode包。
strings包提供了许多如字符串的查询、替换、比较、截断、拆分和合并等功能。
bytes包也提供了很多类似功能的函数,但是针对和字符串有着相同结构的[]byte类型。 因为字符串是只读的,因此逐步构建字符串会导致很多分配和复制。在这种情况下,使用 bytes.Buffer 类型将会更有效。
strconv包提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换。
unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能。
每个函数有一个单一的rune类型的参数,然后返回一个布尔值。而像ToUpper和ToLower之类的转换函数将用于rune字符的大小写转换。所有的这些函数都是遵循Unicode标准定义的字母、数字等分类规范。 strings 包也有类似的函数,它们是ToUpper和ToLower,将原始字符串的每个字符都做相应的转换,然后返回新的字符串。
一个字符串是包含的只读字节数组,一旦创建,是不可变的。相比之下,一个字节slice的元素则可以自由地修改。
字符串和字节slice之间可以相互转换:
s := "abc" b := []byte(s) s2 := string(b)
将一个字节slice转到字符串的string(b)操作则是构造一个字符串拷贝,以确保s2字符串是只读的。
为了避免转换中不必要的内存分配,bytes包和strings同时提供了许多实用函数。下面是strings包中的 六个函数:
func Contains(s, substr string) bool func Count(s, sep string) int func Fields(s string) []string func HasPrefix(s, prefix string) bool func Index(s, sep string) int func Join(a []string, sep string) strings
bytes包中也对应的六个函数:
func Contains(b, subslice []byte) bool func Count(s, sep []byte) int func Fields(s []byte) [][]byte func HasPrefix(s, prefix []byte) bool func Index(s, sep []byte) int func Join(s [][]byte, sep []byte) []byte
它们之间唯一的区别是字符串类型参数被替换成了字节slice类型的参数。
bytes包还提供了Buffer类型用于字节slice的缓存。一个Buffer开始是空的,但是随着string、byte或[]byte等类型数据的写入可以动态增长,一个bytes.Buffer变量并不需要初始化,因为零值也是有效的。
当向bytes.Buffer添加任意字符的UTF8编码时,最好使用bytes.Buffer的WriteRune方法,但是 WriteByte方法对于写入类似'['和']'等ASCII字符效率会更高。
字符串和数字的转换
除了字符串、字符、字节之间的转换,字符串和数值之间的转换也比较常见。由strconv包提供这类转换功能。
几个常用函数:
函数名 | 功能 |
---|---|
strconv.Itoa() | 整数到ASCII |
strconv.FormatInt() | 用不同的进制格式化数字 |
strconv.FormatUint() | 用不同的进制格式化数字 |
strconv.Atoi() | 将一个字符串解析为整数 |
strconv.ParseInt() | 将一个字符串解析为整数 |
注:
ParseInt函数的第三个参数是用于指定整型数的大小;例如16表示int16,0则表示int。在任何情况下, 返回的结果y总是int64类型,你可以通过强制类型转换将它转为更小的整数类型。
有时候也会使用fmt.Scanf来解析输入的字符串和数字,特别是当字符串和数字混合在一行的时候,它可 以灵活处理不完整或不规则的输入。
1.7)常量
常量表达式的值在编译期计算,而不是在运行期。每种常量的潜在类型都是基础类型:boolean、string 或数字。 一个常量的声明语句定义了常量的名字,和变量的声明语法类似,常量的值不可修改,这样可以防止在 运行期被意外或恶意的修改。
和变量声明一样,可以批量声明多个常量;这比较适合声明一组相关的常量:
const ( e = 2.71828182845904523536028747135266249775724709369995957496696763 pi = 3.14159265358979323846264338327950288419716939937510582097494459 )
常量间的所有算术运算、逻辑运算和比较运算的结果也是常量,对常量的类型转换操作或以下函数调用 都是返回常量结果:len、cap、real、imag、complex和unsafe.Sizeof。
因为它们的值是在编译期就确定的,因此常量可以是构成类型的一部分,例如用于指定数组类型的长 度:
const IPv4Len = 4 // parseIPv4 parses an IPv4 address (d.d.d.d). func parseIPv4(s string) IP { var p [IPv4Len]byte // ... }
一个常量的声明也可以包含一个类型和一个值,但是如果没有显式指明类型,那么将从右边的表达式推断类型。
iota 常量生成器
iota常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都 写一遍初始化表达式。在一个const声明语句中,在第一个声明的常量所在的行,iota将会被置为0,然 后在每一个有常量声明的行加一。
type Weekday int const ( Sunday Weekday = iota Monday Tuesday Wednesday Thursday Friday Saturday )
或者下面的例子:
type Flags uint const ( FlagUp Flags = 1 << iota // is up FlagBroadcast // supports broadcast access capability FlagLoopback // is a loopback interface FlagPointToPoint // belongs to a point-to-point link FlagMulticast // supports multicast access capability )
随着iota的递增,每个常量对应表达式1 << iota,是连续的2的幂,分别对应一个bit位置。
无类型常量
许多常量并没有一个明确的基础类型。编译器为这些没有明确的基础类型的数字常量提供比基础类型更高精度的算术运算;你可以认为至少有256bit的运算精度。这里有六种未明确类型的常量类型,分别是无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串。
通过延迟明确常量的具体类型,无类型的常量不仅可以提供更高的运算精度,而且可以直接用于更多的 表达式而不需要显式的类型转换。
只有常量可以是无类型的。当一个无类型的常量被赋值给一个变量的时候,或者是语句中右边表达式含有明确类型的值,如果转换合法的话,无类型的常量将会被隐式转换为对应的类型。
无论是隐式或显式转换,将一种类型转换为另一种类型都要求目标可以表示原始值。对于浮点数和复数,可能会有舍入处理。
对于一个没有显式类型的变量声明语法(包括短变量声明语法),无类型的常量会被隐式转为默认的变量类型。
注意默认类型是规则的:无类型的整数常量默认转换为int,对应不确定的内存大小,但是浮点数和复数常量则默认转换为float64和complex128。Go语言本身并没有不确定内存大小的浮点数和复数类型,而且如果不知道浮点数类型的话将很难写出正确的数值算法。
如果要给变量一个不同的类型,我们必须显式地将无类型的常量转化为所需的类型,或给声明的变量指 定明确的类型。