本章讲解go语言的基础数据类型,在 Go 语言中,基础数据类型(或称为基本类型)是指内置的、预定义的数据类型,开发者可以直接使用它们。这些类型是构建其他复杂数据结构和自定义类型的基础。Go语言的数值类型包括几种不同大小的整数、浮点数和复数。每种数值类型都决定了对应的大小范围和是否支持正负符号。
Go语言同时提供了有符号和无符号类型的整数运算。这里有int8、int16、int32和int64四种截然不同大小的有符号整数类型,分别对应8、16、32、64bit大小的有符号整数,与此对应的是uint8、uint16、uint32和uint64四种无符号整数类型。
还有两种一般对应特定CPU平台机器字大小的有符号和无符号整数int和uint;其中int是应用最广泛的数值类型
还有一种无符号的整数类型uintptr,没有指定具体的bit大小但是足以容纳指针。uintptr类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。
下面是关于int、uint、uintptr的区别
int
int
是一个有符号整数类型,其大小依赖于平台(32 位或 64 位)。int
的范围是 -2,147,483,648
到 2,147,483,647
;在 64 位系统上,范围是 -9,223,372,036,854,775,808
到 9,223,372,036,854,775,807
。uint
uint
是一个无符号整数类型,大小同样依赖于平台。uint
的范围是 0
到 4,294,967,295
;在 64 位系统上,范围是 0
到 18,446,744,073,709,551,615
。uintptr
uintptr
是一个无符号整数类型,大小与指针的大小相同,适用于存储指针的整数表示。uintptr
不能安全地用作普通整数值,因为它的语义与指针相关。将指针转换为 uintptr
后,如果不谨慎,可能会导致安全问题。func main() {
var a int = -42
var b uint = 42
var c uintptr = uintptr(unsafe.Pointer(&a)) // 将指针转换为 uintptr
fmt.Println("int:", a) //-42
fmt.Println("uint:", b) //42
fmt.Println("uintptr:", c) //824633999064
}
下面是Go语言中关于算术运算、逻辑运算和比较运算的二元运算符,它们按照优先级递减的顺序排列:
* / % << >> & &^
+ - | ^
== != < <= > >=
&&
||
一个算术运算的结果,不管是有符号或者是无符号的,如果需要更多的bit位才能正确表示的话,就说明计算结果是溢出了。超出的高位的bit位部分将被丢弃。如果原始的数值是有符号类型,而且最左边的bit位是1的话,那么最终结果可能是负的,例如int8的例子:
var u uint8 = 255
fmt.Println(u, u+1, u*u) // "255 0 1"
var i int8 = 127
fmt.Println(i, i+1, i*i) // "127 -128 1"
u + 1
的情况u
的值为 255
,执行 u + 1
时,计算结果为 256
。uint8
的最大值是 255
,所以 256
超出了这个范围。0
。计算过程如下:
256 % 256 = 0
u + 1
的结果是 0
。u * u
的情况u
的值为 255
,执行 u * u
时,计算结果为 255 * 255 = 65025
。65025
超出了 uint8
的范围(最大为 255
),需要进行溢出处理。65025 % 256 = 1
(因为 256
是 uint8
的最大值)u * u
的结果是 1
。Go语言提供了两种精度的浮点数,float32和float64。它们的算术规范由IEEE754浮点数国际标准定义,该浮点数规范被所有现代的CPU支持。
var f float32 = 16777216 // 1 << 24
fmt.Println(f == f+1) // "true"!
f
是否等于 f + 1
。float32
只能精确表示一定范围内的整数。当 f
的值达到 16777216
时,float32
不能再精确表示比 16777216
大的整数,尤其是 16777216 + 1
。float32
在这个值之后的最小可表示增量大约是 2^24
,即 16777216
。因此,f + 1
的结果仍然是 16777216
,因为它不能精确表示 16777217
。 Go 语言支持复数类型,复数由实部和虚部组成,通常表示为 a + bi
的形式,其中 a
是实部,b
是虚部。Go 提供了内置的复数类型,支持复数的基本运算。
complex64
:由两个 float32
类型的值表示,分别为实部和虚部。complex128
:由两个 float64
类型的值表示,分别为实部和虚部。 var c1 complex64 = complex(1.0, 2.0) // 实部为 1.0,虚部为 2.0
var c2 complex128 = complex(3.0, 4.0) // 实部为 3.0,虚部为 4.0
fmt.Println("c1:", c1) //c1: (1+2i)
fmt.Println("c2:", c2) //c2: (3+4i)
一个布尔类型的值只有两种:true和false。if和for语句的条件部分都是布尔类型的值,并且==和<等比较操作也会产生布尔型的值。一元操作符!
对应逻辑非操作,因此!true
的值为false
,更罗嗦的说法是(!true==false)==true
,虽然表达方式不一样,不过我们一般会采用简洁的布尔表达式,就像用x来表示x==true
。
如果需要经常做类似的转换,包装成一个函数会更方便:
// btoi returns 1 if b is true and 0 if false.
func btoi(b bool) int {
if b {
return 1
}
return 0
}
一个字符串是一个不可改变的字节序列。字符串可以包含任意的数据,包括byte值0,但是通常是用来包含人类可读的文本。文本字符串通常被解释为采用UTF8编码的Unicode码点(rune)序列,我们稍后会详细讨论这个问题。
s := "hello, world"
fmt.Println(len(s)) // "12"
fmt.Println(s[0], s[7]) // "104 119" ('h' and 'w')
如果试图访问超出字符串索引范围的字节将会导致panic异常:
c := s[len(s)] // panic: index out of range
字符串可以通过下标去进行截取
fmt.Println(s[0:5]) // "hello"
不管i还是j都可能被忽略,当它们被忽略时将采用0作为开始位置,采用len(s)作为结束的位置。
fmt.Println(s[:5]) // "hello"
fmt.Println(s[7:]) // "world"
fmt.Println(s[:]) // "hello, world"
其中+操作符将两个字符串连接构造一个新字符串:
fmt.Println("goodbye" + s[5:]) // "goodbye, world"
字符串的值是不可变的:一个字符串包含的字节序列永远不会被改变,当然我们也可以给一个字符串变量分配一个新字符串值。可以像下面这样将一个字符串追加到另一个字符串:
s := "left foot"
t := s
s += ", right foot"
fmt.Println(s) //left foot, right foot
fmt.Println(t) //left foot
常量表达式的值在编译期计算,而不是在运行期。每种常量的潜在类型都是基础类型:boolean、string或数字。
一个常量的声明语句定义了常量的名字,和变量的声明语法类似,常量的值不可修改,这样可以防止在运行期被意外或恶意的修改。例如,常量比变量更适合用于表达像π之类的数学常数,因为它们的值不会发生变化:
//定义一个
const pi = 3.14159 // approximately; math.Pi is a better approximation
//定义多个常量
const (
e = 2.71828182845904523536028747135266249775724709369995957496696763
pi = 3.14159265358979323846264338327950288419716939937510582097494459
)
所有常量的运算都可以在编译期完成,这样可以减少运行时的工作,也方便其他编译优化。当操作数是常量时,一些运行时的错误也可以在编译时被发现,例如整数除零、字符串索引越界、任何导致无效浮点数的操作等。
如果是批量声明的常量,除了第一个外其它的常量右边的初始化表达式都可以省略,如果省略初始化表达式则表示使用前面常量的初始化表达式写法,对应的常量类型也一样的。例如:
const (
a = 1
b
c = 2
d
)
fmt.Println(a, b, c, d) // "1 1 2 2"
b
:
这里定义了一个常量 b
,但没有显式赋值。
如果在同一个常量组中,某个常量没有显式赋值,它会使用前一个常量的值。因此,b
的值将等于 a
的值,即 1
。
常量声明可以使用iota常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。在一个const声明语句中,在第一个声明的常量所在的行,iota将会被置为0,然后在每一个有常量声明的行加一。
下面是来自time包的例子,它首先定义了一个Weekday命名类型,然后为一周的每天定义了一个常量,从周日0开始。在其它编程语言中,这种类型一般被称为枚举类型。
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
func main() {
fmt.Println(Monday) // 1
fmt.Println(Tuesday) // 2
}
周日将对应0,周一为1,如此等等。