变量的声明:
Go 语言的变量声明方式与 C 和 C++ 语言有明显的不同。对于纯粹的变量声明, Go 语言引入了
关键字 var,而类型信息放在变量名之后,示例如下:
- var v1 int
- var v2 string
- var v3[10] int // 数组
- var v4[] int // 数组切片
- var v5 struct {
- f int
- }
- var v6 * int // 指针
- var v7 map[string] int // map, key为string类型, value为int类型
- var v8 func(a int) int
变量声明语句不需要使用分号作为结束符。与 C 语言相比, Go 语言摒弃了语句必须以分号作
为语句结束标记的习惯。
var 关键字的另一种用法是可以将若干个需要声明的变量放置在一起,免得程序员需要重复
写 var 关键字,如下所示:
- var (v1 int v2 string)
对于声明变量时需要进行初始化的场景, var 关键字可以保留,但不再是必要的元素,如下
所示:
- var v1 int = 10 // 正确的使用方式1
- var v2 = 10 // 正确的使用方式2,编译器可以自动推导出v2的类型
- v3: =10 // 正确的使用方式3,编译器可以自动推导出v3的类型
以上三种用法的效果是完全一样的。与第一种用法相比,第三种用法需要输入的字符数大大
减少,是懒程序员和聪明程序员的最佳选择。这里 Go 语言也引入了另一个 C 和 C++ 中没有的符号
(冒号和等号的组合:=),用于明确表达同时进行变量声明和初始化的工作。
指定类型已不再是必需的, Go 编译器可以从初始化表达式的右值推导出该变量应该声明为
哪种类型,这让 Go 语言看起来有点像动态类型语言,尽管 Go 语言实际上是不折不扣的强类型语
言(静态类型语言)。
当然,出现在:= 左侧的变量不应该是已经被声明过的,否则会导致编译错误,比如下面这个
写法:
var i int
i := 2
会导致类似如下的编译错误:
no new variables on left side of :=
:= 左侧的变量必须是没有声明过, 不然就会报错
在 Go 语法中,变量初始化和变量赋值是两个不同的概念。下面为声明一个变量之后的赋值
过程:
var v10 int
v10 = 123
Go 语言的变量赋值与多数语言一致,但 Go 语言中提供了 C/C++ 程序员期盼多年的多重赋值功
能,比如下面这个交换 i 和 j 变量的语句:
i, j = j, i
在不支持多重赋值的语言中,交互两个变量的内容需要引入一个中间变量:
t = i; i = j; j = t;
多重赋值的特性在 Go 语言库的实现中也被使用得相当充分,在介绍函数的多重返回值时,
将对其进行更加深入的介绍。总而言之,多重赋值功能让 Go 语言与 C/C++ 语言相比可以非常明显
地减少代码行数。多重赋值细节和原理参照官方文档
我们在使用传统的强类型语言编程时,经常会出现这种情况,即在调用函数时为了获取一个
值,却因为该函数返回多个值而不得不定义一堆没用的变量。在 Go 中这种情况可以通过结合使
用多重返回和匿名变量来避免这种丑陋的写法,让代码看起来更加优雅。
假 设 GetName() 函 数 的 定 义 如 下 , 它 返 回 3 个 值 , 分 别 为 firstName 、 lastName 和
nickName:
- func GetName()(firstName, lastName, nickName string) {
- return "May",
- "Chan",
- "Chibi Maruko"
- }
若只想获得 nickName,则函数调用语句可以用如下方式编写:
- _,
- _,
- nickName: =GetName()
这种用法可以让代码非常清晰,基本上屏蔽掉了可能混淆代码阅读者视线的内容,从而大幅
降低沟通的复杂度和代码维护的难度
在 Go 语言中,常量是指编译期间就已知且不可改变的值。常量可以是数值类型(包括整型、
浮点型和复数类型)、布尔类型、字符串类型等。
所谓字面常量(literal),是指程序中硬编码的常量,如:
-12
3.14159265358979323846 // 浮点类型的常量
3.2+12i // 复数类型的常量
true // 布尔类型的常量
"foo" // 字符串常量
在其他语言中,常量通常有特定的类型,比如 - 12 在 C 语言中会认为是一个 int 类型的常量。
如果要指定一个值为 - 12 的 long 类型常量,需要写成 - 12l,这有点违反人们的直观感觉。 Go 语言
的字面常量更接近我们自然语言中的常量概念,它是无类型的。只要这个常量在相应类型的值域
范围内,就可以作为该类型的常量,比如上面的常量 - 12,它可以赋值给 int、 uint、 int32、
int64、 float32、 float64、 complex64、 complex128 等类型的变量。
通过 const 关键字,你可以给字面常量指定一个友好的名字:
- const Pi float64 = 3.14159265358979323846 const zero = 0.0 // 无类型浮点常量
- const(size int64 = 1024 eof = -1 // 无类型整型常量
- ) const u,
- v float32 = 0,
- 3 // u = 0.0, v = 3.0,常量的多重赋值
- const a,
- b,
- c = 3,
- 4,
- "foo"
- // a = 3, b = 4, c = "foo", 无类型整型和字符串常量
Go 的常量定义可以限定常量类型,但不是必需的。如果定义常量时没有指定类型,那么它
与字面常量一样,是无类型常量。
常量定义的右值也可以是一个在编译期运算的常量表达式,比如
const mask = 1 << 3
由于常量的赋值是一个编译期行为,所以右值不能出现任何需要运行期才能得出结果的表达
式,比如试图以如下方式定义常量就会导致编译错误:
const Home = os.GetEnv("HOME")
原因很简单, os.GetEnv() 只有在运行期才能知道返回结果,在编译期并不能确定,所以
无法作为常量定义的右值。
2.2.3 预定义常量
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 后跟一对圆括号的方式定义一组常量,这种定义法在 Go 语言中通常用于定义
枚举值。 Go 语言并不支持众多其他语言明确支持的 enum 关键字。
下面是一个常规的枚举表示法,其中定义了一系列整型常量:
const (
Sunday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
numberOfDays // 这个常量没有导出
)
同 Go 语言的其他符号(symbol)一样,以大写字母开头的常量在包外可见。
以上例子中 numberOfDays 为包内私有,其他符号则可被其他包访问。
Go 语言内置以下这些基础类型:
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
整型是所有编程语言里最基础的数据类型。 Go 语言支持下表所示的这些整型类型。
类 型 | 长度(字节) | 值 范 围 |
---|---|---|
int8 | 1 | 128 ~ 127 |
uint8(即 byte) | 1 | 0 ~ 255 |
int16 | 2 | 32 768 ~ 32 767 |
uint16 | 2 | 0 ~ 65 535 |
int32 | 4 | 2 147 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 字节 |
1. 类型表示
需要注意的是, int 和 int32 在 Go 语言里被认为是两种不同的类型,编译器也不会帮你自动
做类型转换,比如以下的例子会有编译错误:
var value2 int32
value1 := 64 // value1 将会被自动推导为 int 类型
value2 = value1 // 编译错误
编译错误类似于:
cannot use value1 (type int) as type int32 in assignment。
使用强制类型转换可以解决这个编译错误:
value2 = int32(value1) // 编译通过
当然,开发者在做强制类型转换时,需要注意数据长度被截短而发生的数据精度损失(比如
将浮点数强制转为整数)和值溢出(值超过转换的目标类型的值范围时)问题。
2. 数值运算
Go 语言支持下面的常规整数运算: +、 、 *、 / 和 %。加减乘除就不详细解释了,需要说下的
是, % 和在 C 语言中一样是求余运算,比如:
5 % 3 // 结果为: 2
3. 比较运算
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.")
}
4. 位运算
Go 语言支持表 2-2 所示的位运算符。
运 算 | 含 义 | 样 例 |
---|---|---|
x << y | 左移 | 124 << 2 // 结果为 496 |
x >> y | 右移 | 124 >> 2 // 结果为 31 |
x ^ y | 异或 | 124 ^ 2 // 结果为 126 |
x & y | 与 | 124 & 2 // 结果为 0 |
x | y | 或 |
^x | 取反 | ^2 // 结果为 - 3 |
Go 语言的大多数位运算符与 C 语言都比较类似,除了取反在 C 语言中是~ x,而在 Go 语言中
是 ^x。
浮点型用于表示包含小数点的数据,比如 1.234 就是一个浮点型数据。 Go 语言中的浮点类型
采用 IEEE-754 标准的表达方式。
1. 浮点数表示
Go 语言定义了两个类型 float32 和 float64,其中 float32 等价于 C 语言的 float 类型,
float64 等价于 C 语言的 double 类型。
在 Go 语言里,定义一个浮点数变量的代码如下:
var fvalue1 float32
fvalue1 = 12
fvalue2 := 12.0 // 如果不加小数点, fvalue2 会被推导为整型而不是浮点型
对于以上例子中类型被自动推导的 fvalue2,需要注意的是其类型将被自动设为 float64,
而不管赋给它的数字是否是用 32 位长度表示的。因此,对于以上的例子,下面的赋值将导致编译
错误:
fvalue1 = fvalue2
而必须使用这样的强制类型转换:
fvalue1 = float32(fvalue2)
2. 浮点数比较
因为浮点数不是一种精确的表达方式,所以像整型那样直接用 == 来判断两个浮点数是否相等
是不可行的,这可能会导致不稳定的结果。
下面是一种推荐的替代方案:
import "math"
// p 为用户自定义的比较精度,比如 0.00001
func IsEqual(f1, f2, p float64) bool {
return math.Fdim(f1, f2) < p
}
复数实际上由两个实数(在计算机中用浮点数表示)构成,一个表示实部(real),一个表示
虚部(imag)。如果了解了数学上的复数是怎么回事,那么 Go 语言的复数就非常容易理解了。
1. 复数表示
复数表示的示例如下:
- var value1 complex64 // 由2个float32构成的复数类型
- value1 = 3.2 + 12i value2: =3.2 + 12i // value2是complex128类型
- value3: =complex(3.2, 12) // value3结果同 value2
2. 实部与虚部
对于一个复数 z = complex(x, y),就可以通过 Go 语言内置函数 real(z) 获得该复数的实
部,也就是 x,通过 imag(z) 获得该复数的虚部,也就是 y。
更多关于复数的函数,请查阅 math/cmplx 标准库的文档。
在 Go 语言中,字符串也是一种基本类型。相比之下, C/C++ 语言中并不存在原生的字符串
类型,通常使用字符数组来表示,并以字符指针来传递。
Go 语言中字符串的声明和初始化非常简单,举例如下:
var str string // 声明一个字符串变量
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]
在这个例子中我们使用了一个 Go 语言内置的函数 len() 来取字符串的长度。这个函数非常有
用,我们在实际开发过程中处理字符串、数组和切片时将会经常用到。
本节中我们还顺便示范了 Printf() 函数的用法。有 C 语言基础的读者会发现, Printf() 函
数的用法与 C 语言运行库中的 printf() 函数如出一辙。读者在以后学习更多的 Go 语言特性时,
可以配合使用 Println() 和 Printf() 来打印各种自己感兴趣的信息,从而让学习过程更加直
观、有趣。
Go 编译器支持 UTF-8 的源代码文件格式。这意味着源代码中的字符串可以包含非 ANSI 的字
符,比如 "Hello world. 你好,世界!" 可以出现在 Go 代码中。但需要注意的是,如果你的 Go 代
码需要包含非 ANSI 字符,保存源文件时请注意编码格式必须选择 UTF-8。特别是在 Windows 下一
般编辑器都默认存为本地编码,比如中国地区可能是 GBK 编码而不是 UTF-8,如果没注意这点在
编译和运行时就会出现一些意料之外的情况。
字符串的编码转换是处理文本文档(比如 TXT、 XML、 html 等)非常常见的需求,不过可
惜的是 Go 语言仅支持 UTF-8 和 Unicode 编码。对于其他编码, Go 语言标准库并没有内置的编码转
换支持。不过,所幸的是我们可以很容易基于 iconv 库用 Cgo 包装一个。这里有一个开源项目:
。
1. 字符串操作
平时常用的字符串操作如表所示。
运 算 | 含 义 | 样 例 |
---|---|---|
x + y | 字符串连接 | "Hello" + "123" // 结果为 Hello123 |
len(s) | 字符串长度 | len("Hello") // 结果为 5 |
s[i] | 取字符 | "Hello" [1] // 结果为'e' |
更多的字符串操作,请参考标准库 strings 包。
2. 字符串遍历
Go 语言支持两种方式遍历字符串。一种是以字节数组的方式遍历:
str := "Hello, 世界"
n := len(str)
for i := 0; i < n; i++ {
ch := str[i] // 依据下标取字符串中的字符,类型为 byte
fmt.Println(i, ch)
}
这个例子的输出结果为:
- 72 101 108 108 111 44 32 228 184 150 231 149 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
以 Unicode 字符方式遍历时,每个字符的类型是 rune(早期的 Go 语言用 int 类型表示 Unicode
字符),而不是 byte。
在 Go 语言中支持两个字符类型,一个是 byte(实际上是 uint8 的别名),代表 UTF-8 字符串
的单个字节的值;另一个是 rune,代表单个 Unicode 字符。
关于 rune 相关的操作,可查阅 Go 标准库的 unicode 包。另外 unicode/utf8 包也提供了
UTF8 和 Unicode 之间的转换。
出于简化语言的考虑, Go 语言的多数 API 都假设字符串为 UTF-8 编码。尽管 Unicode 字符在标
准库中有支持,但实际上较少使用。
数组是 Go 语言编程中最常用的数据结构之一。顾名思义,数组就是指一系列同一类型数据
的集合。数组中包含的每个数据被称为数组元素(element),一个数组包含的元素个数被称为数
组的长度。
以下为一些常规的数组声明方法:
[32]byte // 长度为 32 的数组,每个元素为一个字节
[2*N] struct {x, y int32} // 复杂类型数组
[1000]*float64 // 指针数组
[3][5]int // 二维数组
[2][2][2]float64 // 等同于
从以上类型也可以看出,数组可以是多维的,比如 [3][5]int 就表达了一个 3 行 5 列的二维整
型数组,总共可以存放 15 个整型元素。
在 Go 语言中,数组长度在定义后就不可更改,在声明时长度可以为一个常量或者一个常量
表达式(常量表达式是指在编译期即可计算结果的表达式)。数组的长度是该数组类型的一个内
置常量,可以用 Go 语言的内置函数 len() 来获取。下面是一个获取数组 arr 元素个数的写法:
arrLength := len(arr)
元素访问
可以使用数组下标来访问数组中的元素。与 C 语言相同,数组下标从 0 开始, len(array)-1
则表示最后一个元素的下标。下面的示例遍历整型数组并逐个打印元素内容:
for i := 0; i <len(array); i++ {
fmt.Println("Element", i, "of array is", array[i])
}
Go 语言还提供了一个关键字 range,用于便捷地遍历容器中的元素。当然,数组也是 range
的支持范围。上面的遍历过程可以简化为如下的写法:
for i, v := range array {
fmt.Println("Array element[", i, "]=", v)
}
在上面的例子里可以看到, range 具有两个返回值,第一个返回值是元素的数组下标,第二
个返回值是元素的值。
- 值类型
需要特别注意的是,在 Go 语言中数组是一个值类型(value type)。所有的值类型变量在赋值
和作为参数传递时都将产生一次复制动作。如果将数组作为函数的参数类型,则在函数调用时该
参数将发生数据复制。因此,在函数体中无法修改传入的数组的内容,因为函数内操作的只是所
传入数组的一个副本。
下面用例子来说明这一特点:
package main
import "fmt"
func modify(array [10]int) {
array[0] = 10 // 试图修改数组的第一个元素
fmt.Println("In modify(), array values:", array)
}
func main() {
array := [5]int{1,2,3,4,5} // 定义并初始化一个数组
modify(array) // 传递给一个函数,并试图在函数体内修改这个数组内容
fmt.Println("In main(), array values:", array)
}
该程序的执行结果为:
In modify(), array values: [10 2 3 4 5]
In main(), array values: [1 2 3 4 5]
从执行结果可以看出, 函数 modify() 内操作的那个数组跟 main() 中传入的数组是两个不同的实
例。那么,如何才能在函数内操作外部的数据结构呢?我们将在 2.3.6 节中详细介绍如何用数组切
片功能来达成这个目标。
在前一节里我们已经提过数组的特点:数组的长度在定义之后无法再次修改;数组是值类型,
每次传递都将产生一份副本。显然这种数据结构无法完全满足开发者的真实需求。
不用失望, Go 语言提供了数组切片(slice)这个非常酷的功能来弥补数组的不足。
初看起来,数组切片就像一个指向数组的指针,实际上它拥有自己的数据结构,而不仅仅是
个指针。数组切片的数据结构可以抽象为以下 3 个变量:
- 一个指向原生数组的指针;
- 数组切片中的元素个数;
- 数组切片已分配的存储空间。
从底层实现的角度来看,数组切片实际上仍然使用数组来管理元素,因此它们之间的关系让
C++ 程序员们很容易联想起 STL 中 std::vector 和数组的关系。基于数组,数组切片添加了一系
列管理功能,可以随时动态扩充存放空间,并且可以被随意传递而不会导致所管理的元素被重复
复制。
1. 创建数组切片
创建数组切片的方法主要有两种——基于数组和直接创建,下面我们来简要介绍一下这两种
方法。
基于数组
数组切片可以基于一个已存在的数组创建。数组切片可以只使用数组的一部分元素或者整个
数组来创建,甚至可以创建一个比所基于的数组还要大的数组切片。代码清单 2-1 演示了如何基
于一个数组的前 5 个元素创建一个数组切片。
slice.go
- package main import "fmt"func main() {
- // 先定义一个数组
- var myArray[10] int = [10] int {
- 1,
- 2,
- 3,
- 4,
- 5,
- 6,
- 7,
- 8,
- 9,
- 10
- }
- // 基于数组创建一个数组切片
- var mySlice[] int = myArray[: 5] fmt.Println("Elements of myArray: ") for _,
- v: =range myArray {
- fmt.Print(v, " ")
- }
- fmt.Println("\nElements of mySlice: ") for _,
- v: =range mySlice {
- fmt.Print(v, " ")
- }
- fmt.Println()
- }运行结果为:Elements of myArray: 1 2 3 4 5 6 7 8 9 10 Elements of mySlice: 1 2 3 4 5
看官大人已经注意到,Go 语言支持用 myArray[first:last] 这样的方式来基于数组生成一
个数组切片,而且这个用法还很灵活,比如下面几种都是合法的。
基于 myArray 的所有元素创建数组切片:
mySlice = myArray[:]
基于 myArray 的前 5 个元素创建数组切片:
mySlice = myArray[:5]
基于从第 5 个元素开始的所有元素创建数组切片:
mySlice = myArray[5:] 直接创建
并非一定要事先准备一个数组才能创建数组切片。 Go 语言提供的内置函数 make() 可以用于
灵活地创建数组切片。下面的例子示范了直接创建数组切片的各种方法。
创建一个初始元素个数为 5 的数组切片,元素初始值为 0:
mySlice1 := make([]int, 5)
创建一个初始元素个数为 5 的数组切片,元素初始值为 0,并预留 10 个元素的存储空间:
mySlice2 := make([]int, 5, 10)
直接创建并初始化包含 5 个元素的数组切片:
mySlice3 := []int{1, 2, 3, 4, 5}
当然,事实上还会有一个匿名数组被创建出来,只是不需要我们来操心而已。
2. 元素遍历
操作数组元素的所有方法都适用于数组切片,比如数组切片也可以按下标读写元素,用 len()
函数获取元素个数,并支持使用 range 关键字来快速遍历所有元素。
传统的元素遍历方法如下:
for i := 0; i
在 C++/Java 中, map 一般都以库的方式提供,比如在 C++ 中是 STL 的 std::map<>,在 C# 中是
Dictionary<>,在 Java 中是 Hashmap<>,在这些语言中,如果要使用 map,事先要引用相应的
库。而在 Go 中,使用 map 不需要引入任何库,并且用起来也更加方便。
map 是一堆键值对的未排序集合。比如以身份证号作为唯一键来标识一个人的信息,则这个
map 可以定义为代码清单 2-3 所示的方式。
代码清单 2-3 map1.go
package main
import "fmt"
// PersonInfo 是一个包含个人详细信息的类型
type PersonInfo struct {
ID string
Name string
Address string
}
func main() {
var personDB map[string] PersonInfo
personDB = make(map[string] PersonInfo)
// 往这个 map 里插入几条数据
personDB["12345"] = PersonInfo{"12345", "Tom", "Room 203,…"}
personDB["1"] = PersonInfo{"1", "Jack", "Room 101,…"}
// 从这个 map 查找键为 "1234" 的信息
person, ok := personDB["1234"]
// ok 是一个返回的 bool 型,返回 true 表示找到了对应的数据
if ok {
fmt.Println("Found person", person.Name, "with ID 1234.")
} else {
fmt.Println("Did not find person with ID 1234.")
}
}
上面这个简单的例子基本上已经覆盖了 map 的主要用法,下面对其中的关键点进行细述。
1. 变量声明
map 的声明基本上没有多余的元素,比如:
var myMap map[string] PersonInfo
其中, myMap 是声明的 map 变量名, string 是键的类型, PersonInfo 则是其中所存放的值类型。
2. 创建
我们可以使用 Go 语言内置的函数 make() 来创建一个新 map。下面的这个例子创建了一个键
类型为 string、值类型为 PersonInfo 的 map:
myMap = make(map[string] PersonInfo)
也可以选择是否在创建时指定该 map 的初始存储能力,下面的例子创建了一个初始存储能力
为 100 的 map:
myMap = make(map[string] PersonInfo, 100)
关于存储能力的说明,可以参见 2.3.6 节中的内容。
创建并初始化 map 的代码如下:
myMap = map[string] PersonInfo{
"1234": PersonInfo{"1", "Jack", "Room 101,…"},
}
3. 元素赋值
赋值过程非常简单明了,就是将键和值用下面的方式对应起来即可:
myMap["1234"] = PersonInfo{"1", "Jack", "Room 101,…"}
4. 元素删除
Go 语言提供了一个内置函数 delete(),用于删除容器内的元素。下面我们简单介绍一下如
何用 delete() 函数删除 map 内的元素:
delete(myMap,"1234")
上面的代码将从 myMap 中删除键为 "1234" 的键值对。如果 "1234" 这个键不存在,那么这个调
用将什么都不发生,也不会有什么副作用。但是如果传入的 map 变量的值是 nil,该调用将导致
程序抛出异常(panic)。
5. 元素查找
在 Go 语言中, map 的查找功能设计得比较精巧。而在其他语言中,我们要判断能否获取到一
个值不是件容易的事情。判断能否从 map 中获取一个值的常规做法是:
(1) 声明并初始化一个变量为空;
(2) 试图从 map 中获取相应键的值到该变量中;
(3) 判断该变量是否依旧为空,如果为空则表示 map 中没有包含该变量。
这种用法比较啰唆,而且判断变量是否为空这条语句并不能真正表意(是否成功取到对应的
值) ,从而影响代码的可读性和可维护性。有些库甚至会设计为因为一个键不存在而抛出异常,
让开发者用起来胆战心惊,不得不一层层嵌套 try-catch 语句,这更是不人性化的设计。在 Go
语言中,要从 map 中查找一个特定的键,可以通过下面的代码来实现:
value, ok := myMap["1234"]
if ok { // 找到了
// 处理找到的 value
}
判断是否成功找到特定的键,不需要检查取到的值是否为 nil,只需查看第二个返回值 ok,
这让表意清晰很多。配合:= 操作符,让你的代码没有多余成分,看起来非常清晰易懂。
来源: http://lib.csdn.net/article/go/38011