变量
Go 是静态类型语⾔,不能在运⾏期改变变量类型。
使⽤关键字 var 定义变量,⾃动初始化为零值。如果提供初始化值,可省略变量类型,由编译器⾃动推断。
var
声明变量的一般形式是使用 var 关键字: var identifier type 。
var x int var f float32 = 1.6 var s = "abc"
:=
在函数内部,可⽤更简略的 “:=” ⽅式定义变量,
func main() { x := 123 n, s := 0x1234, "Hello, World!" b := 123 b := 1234 //会出现错误no new variables on left side of := 。因为在上一行已经定义过变量 println(x, n, s, b) }
变量赋值
多变量赋值时,先计算所有相关值,然后再从左到右依次赋值。
data, i := [3]int{0, 1, 2}, 0 i, data[i] = 2, 100 // (i = 0) -> (i = 2), (data[0] = 100)
特殊只写变量 “_”,⽤于忽略值占位。
func test() (int, string) { return 1, "abc" } func main() { _, s := test() println(s) }
注意
在go语言中交换两个变量的值十分容易,不需要像c语言中定义一个中间量
i,j = j,i
匿名变量 “_”
我们在使用传统的强类型语言编程时,经常会出现这种情况,即在调用函数时为了获取一个值,却因为该函数返回多个值而不得不定义一堆没用的变量。在Go中这种情况可以通过结合使用多重返回和匿名变量来避免这种丑陋的写法,让代码看起来更加优雅。
而且,在go语言中编译器会将未使⽤的局部变量当做错误,为了避免这种情况必须用匿名变量进行占位。
var s string // 全局变量没问题。 func GetName() (firstName, lastName, nickName string) { return "May", "Chan", "Chibi Maruko" } func main() { //若只想获得nickName,则函数调用语句可以用如下方式编写: _, _, nickName := GetName() i := 0 // Error: i declared and not used。(可使⽤ "_ = i" 进行占位) }
注意重新赋值与定义新同名变量的区别
s := "abc" println(&s) s, y := "hello", 20 // 重新赋值: 与前 s 在同⼀层次的代码块中,且有新的变量被定义。 println(&s, y) // 通常函数多返回值 err 会被重复使⽤。 { s, z := 1000, 30 // 定义新同名变量: 不在同⼀层次代码块。 println(&s, z) }
输出:
0x2210230f30
0x2210230f30 20
0x2210230f18 30
常量
注意大写字母开头的常量在包外可见,否则只在本包内可见。
常量定义
常量值必须是编译期可确定的数字、字符串、布尔值。
const x, y int = 1, 2 // 多常量初始化 const s = "Hello, World!" // 类型推断 const ( // 常量组 a, b = 10, 100 c bool = false ) func main() { const x = "xxx" // 未使⽤局部常量不会引发编译错误。 }
在常量组中,如不提供类型和初始化值,那么视作与上⼀常量相同。
const ( s = "abc" x // x = "abc" )
常量值还可以是 len、cap、unsafe.Sizeof 等编译期可确定结果的函数返回值。
const ( a = "abc" b = len(a) c = unsafe.Sizeof(b) )
预定义常量
Go语言预定义了这些常量:true、false和iota。
iota比较特殊,可以被认为是一个可被编译器修改的常量,在每一个const关键字出现时被重置为0,然后在下一个const出现之前,每出现一次iota,其所代表的数字会自动增1。从以下的例子可以基本理解iota的用法:
const ( // iota被重设为0 c0 = iota // c0 == 0 c1 = iota // c1 == 1 c2 = iota // c2 == 2 ) const ( a = 1 << iota // a == 1 (iota在每个const开头被重设为0) b = 1 << iota // b == 2 c = 1 << iota // c == 4 ) const ( u = iota * 42 // u == 0 v float64 = iota * 42 // v == 42.0 w = iota * 42 // w == 84 ) const x = iota // x == 0 (因为iota又被重设为0了) const y = iota // y == 0 (同上)
如果两个const的赋值语句的表达式是一样的,那么可以省略后一个赋值表达式。因此,上
面的前两个const语句可简写为:
const ( // iota被重设为0 c0 = iota // c0 == 0 c1 // c1 == 1 c2 // c2 == 2 ) const ( a = 1 <<iota // a == 1 (iota在每个const开头被重设为0) b // b == 2 c // c == 4
枚举
const ( Sunday = iota // 0 Monday // 1,通常省略后续⾏表达式。 Tuesday // 2 Wednesday // 3 Thursday // 4 Friday // 5 Saturday // 6 )
可通过⾃定义类型来实现枚举类型限制。
type Color int const ( Black Color = iota Red Blue ) func test(c Color) {} func main() { c := Black test(c) x := 1 test(x) // Error: cannot use x (type int) as type Color in function argument test(1) // 常量会被编译器⾃动转换。 }
基本类型
Go语言内置以下这些基础类型:
布尔类型:bool。
整型:int8、byte、int16、int、uint、uintptr等。
浮点类型:float32、float64。
复数类型:complex64、complex128。
字符串:string。
字符类型:rune。
错误类型:error。
此外,Go语言也支持以下这些复合类型:
指针(pointer)
数组(array)
切片(slice)
字典(map)
通道(chan)
结构体(struct)
接口(interface)
在这些基础类型之上Go还封装了下面这几种类型:int、uint和uintptr等。这些类型的
特点在于使用方便,但使用者不能对这些类型的长度做任何假设。对于常规的开发来说,用int和uint就可以了,没必要用int8之类明确指定长度的类型,以免导致移植困难。
布尔类型
Go语言中的布尔类型与其他语言基本一致,关键字也为bool,可赋值为预定义的true和
false示例代码如下:
var v1 bool v1 = true v2 := (1 == 2) // v2也会被推导为bool类型
布尔类型不能接受其他类型的赋值,不支持自动或强制的类型转换。以下的示例是一些错误的用法,会导致编译错误:
var b bool b = 1 // 编译错误 b = bool(1) // 编译错误 //以下的用法才是正确的: var b bool b = (1!=0) // 编译正确 fmt.Println("Result:", b) // 打印结果为Result: true
整型
整型是所有编程语言里最基础的数据类型。
类 型 | 长度(字节) | 值 范 围 |
int8 | 1 | 128 ~ 127 |
uint8(即byte) | 1 | 0 ~ 255 |
int16 | 2 | 32 768 ~ 32 767 |
uint16 | 2 | 0 ~ 65 535 |
int32 | 4 | -2147 483 648 ~ 2 147 483 647 |
uint32 | 4 | 0 ~ 4 294 967 295 |
int64 | 8 | 9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807 |
uint64 | 8 | 0 ~ 18 446 744 073 709 551 615 |
int | 平台相关 | 平台相关 |
uint | 平台相关 | 平台相关 |
uintptr | 同指针 | 在32位平台下为4字节,64位平台下为8字节 |
需要注意的是,int和int32在Go语言里被认为是两种不同的类型,编译器也不会帮你自动
做类型转换,比如以下的例子会有编译错误:
var value2 int32 value1 := 64 // value1将会被自动推导为int类型 value2 = value1 // 编译错误
编译错误类似于:
cannot use value1 (type int) as type int32 in assignment。 • 1
使用强制类型转换可以解决这个编译错误:
value2 = int32(value1) // 编译通过
当然,在做强制类型转换时,需要注意数据长度被截短而发生的数据精度损失(比如
将浮点数强制转为整数)和值溢出(值超过转换的目标类型的值范围时)问题。
数值运算
Go语言支持下面的常规整数运算:+、-、*、/和%。加减乘除就不详细解释了,需要说下的是,% 和在C语言中一样是求余运算,比如:
5 % 3 // 结果为:2
- 比较运算
Go语言支持以下的几种比较运算符:>、<、==、>=、<=和!=。这一点与大多数其他语言相同,与C语言完全一致。
下面为条件判断语句的例子:
i, j := 1, 2 if i == j { fmt.Println("i and j are equal.") }
两个不同类型的整型数不能直接比较,比如int8类型的数和int类型的数不能直接比较,但
各种类型的整型变量都可以直接与字面常量(literal)进行比较,比如:
var i int32 var j int64 i, j = 1, 2 if i == j { // 编译错误 fmt.Println("i and j are equal.") } if i == 1 || j == 2 { // 编译通过 fmt.Println("i and j are equal.") }
位运算
运 算 | 含 义 | 样 例 |
x << y | 左移 | 124 << 2 // 结果为496 |
x >> y | 右移 | 124 >> 2 // 结果为31 |
x ^ y | 异或 | 124 ^ 2 // 结果为126 |
x & y | 与 124 & 2 // 结果为0 | |
x | y | 或 | 124 |
^x | 取反 | ^2 // 结果为-3 |
Go语言的大多数位运算符与C语言都比较类似,除了取反在C语言中是~x,而在Go语言中是^x。
浮点型
浮点型用于表示包含小数点的数据,比如1.234就是一个浮点型数据。
浮点数表示
Go语言定义了两个类型float32和float64,其中float32等价于C语言的float类型,
float64等价于C语言的double类型。
在Go语言里,定义一个浮点数变量的代码如下:
var fvalue1 float32 fvalue1 = 12 fvalue2 := 12.0 // 如果不加小数点,fvalue2会被推导为整型而不是浮点型
浮点数比较
因为浮点数不是一种精确的表达方式,所以像整型那样直接用==来判断两个浮点数是否相等是不可行的,这可能会导致不稳定的结果。
下面是一种推荐的替代方案:
import "math" // p为用户自定义的比较精度,比如0.00001 func IsEqual(f1, f2, p float64) bool { return math.Fdim(f1, f2) < p }
复数类型
复数实际上由两个实数(在计算机中用浮点数表示)构成,一个表示实部(real),一个表示虚部(imag)。如
复数表示
复数表示的示例如下:
var value1 complex64 // 由2个float32构成的复数类型 value1 = 3.2 + 12i value2 := 3.2 + 12i // value2是complex128类型 value3 := complex(3.2, 12) // value3结果同 value2
实部与虚部
对于一个复数z = complex(x, y),就可以通过Go语言内置函数real(z)获得该复数的实
部,也就是x,通过imag(z)获得该复数的虚部,也就是y。
字符串
在Go语言中,字符串也是一种基本类型。相比之下, C/C++语言中并不存在原生的字符串类型,通常使用字符数组来表示,并以字符指针来传递。
Go语言中字符串的声明和初始化非常简单,举例如下:
str = "Hello world" // 字符串赋值 ch := str[0] // 取字符串的第一个字符 fmt.Printf("The length of \"%s\" is %d \n", str, len(str)) fmt.Printf("The first character of \"%s\" is %c.\n", str, ch)
输出结果为:
The length of “Hello world” is 11
The first character of “Hello world” is H.
字符串的内容可以用类似于数组下标的方式获取,但与数组不同,字符串的内容不能在初始化后被修改,比如以下的例子:
str := "Hello world" // 字符串也支持声明时进行初始化的做法 str[0] = 'X' // 编译错误
编译器会报类似如下的错误:
cannot assign to str[0]
保存源文件时请注意编码格式必须选择UTF-8。特别是在Windows下一般编辑器都默认存为本地编码,比如中国地区可能是GBK编码而不是UTF-8,如果没注意这点在编译和运行时就会出现一些意料之外的情况。
字符串操作
运 算 | 含 义 | 样 例 |
x + y | 字符串连接 | “Hello” + “qwe” // 结果为Helloqwe |
len(s) 字符串长度 | len(“Hello”) // 结果为5 | |
s[i] | 取字符 “Hello” [1] | // 结果为’e’ |
字符串遍历
Go语言支持两种方式遍历字符串。一种是以字节数组的方式遍历:
str := "Hello,世界" n := len(str) for i := 0; i < n; i++ { ch := str[i] // 依据下标取字符串中的字符,类型为byte fmt.Println(i, ch) }
这个例子的输出结果为:
0 72
1 101
2 108
3 108
4 111
5 44
6 32
7 228
8 184
9 150
10 231
11 149
12 140
可以看出,这个字符串长度为13。尽管从直观上来说,这个字符串应该只有9个字符。这是因为每个中文字符在UTF-8中占3个字节,而不是1个字节。
另一种是以Unicode字符遍历:
str := "Hello,世界" for i, ch := range str { fmt.Println(i, ch)//ch的类型为rune }
输出结果为:
0 72
1 101
2 108
3 108
4 111
5 44
6 32
7 19990
10 30028
。
字符类型
在Go语言中支持两个字符类型,一个是byte(实际上是uint8的别名),代表UTF-8字符串的单个字节的值
var ch int = '\u0041' var ch2 int = '\u03B2' var ch3 int = '\U00101234' fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point