包package
每个 Go 程序都是由包组成的.
程序从 main
包开始运行.
本程序通过导入路径 "fmt"
and "math/rand"
来使用这两个包.
按照约定,包名与导入路径的最后一个元素相同。例如,"math/rand"
包中的源码均以package
rand` 语句开始.
注意: 执行这些程序的环境是确定性的 所以每次运行示例程序 rand.Intn
都会返回相同的数字.
package main
import (
"fmt"
"math/rand"
)
func main() {
fmt.Println("My favorite number is", rand.Intn(10))
}
Imports(导入)
此代码将导入分组到带括号的"factored(分解)"导入语句中.
您还可以编写多个导入语句,例如:
import "fmt"
import "math"
或者
import (
"fmt"
"math"
)
但是使用factored import语句是一种很好的风格.
基本数据类型
bool
布尔值是一组布尔值,true
和false
。
默认值:false
string
字符串是所有 8 位字节字符串的集合,通常必须表示 UTF-8 编码的文本。字符串可能为空,但 不是零。字符串类型的值是不可变的。
默认值:""
int
int 是大小至少为 32 位的有符号整数类型。这是一个非重复类型,而不是 int32 的别名。
默认值:0
int类型还有其他相关类型:
- int8:是所有有符号 8 位整数的集合。 范围:-2 ^8 到 2 ^8 - 1。
- int16:是所有有符号 16 位整数的集合。 范围:-2 ^16 到 2 ^16 - 1。
- int32:是所有有符号 32 位整数的集合。 范围:-2 ^32 到 2 ^32 - 1。
- int64:是所有有符号 64 位整数的集合。 范围:-2 ^64 到 2 ^64 - 1。
- uint:一种无符号整数类型,大小至少为32位。这是一个不同的类型,而不是 uint32 的别名。
- uint8:是所有无符号 8 位整数的集合。 范围:0 到 2 ^8 - 1。
- uint16:是所有无符号 16 位整数的集合。 范围:0 到 2 ^16 - 1。
- uint32:是所有无符号 32 位整数的集合。 范围:0 到 2 ^32 - 1。
- uint64:是所有无符号 64 位整数的集合。 范围:0 到 2 ^64 - 1。
- uintptr:是一个整数类型,它足够大,可以容纳任何指针。
注意: int, uint, and uintptr 在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽. 当你需要一个整数值时,你应该使用 int 除非你有特定的理由使用一个固定大小或无符号的整数类型。
byte
字节是 uint8 的别名,在所有方面都等效于 uint8。用于区分字节值和 8 位无符号 整数值。
rune
rune 是 Int32 的别名,在所有方面都等同于 Int32。用于区分字符值和整数值。
float
- float32:float32 是所有 IEEE-754 32 位浮点数的集合。
- float64:float64 是所有 IEEE-754 64 位浮点数的集合。
complex
复数是由两个浮点数表示的,其中一个表示实部(real),一个表示虚部(imag)。
- complex64:是所有复数的集合,float32 实数和虚部。
- complex128:是所有复数的集合,float64 实数和虚部。复数的默认类型。
变量
变量声明
var
语句用于声明一个变量列表,跟函数的参数列表一样,类型在最后。
var
语句可以出现在包或函数
级别。
语法:var i int
var
声明可以包含初始化程序,每个变量一个。如果存在初始化器,则可以省略类型;
在函数内部, 可以使用 :=
短赋值语句来代替具有隐式类型的 var
声明。
但是,在函数外部, 每个语句都以关键字 (var
, func
, 等) 因此 :=
结构不可用。
觉得每行都用 var 声明变量比较烦琐?没关系,还有一种为懒人提供的定义变量的方法:
var (
a int
b string
c []float32
d func() bool
e struct {
x int
}
)
使用关键字 var 和括号,可以将一组变量定义放在一起。
变量的初始化
可以这么写:var hp int = 100
也可以省略int直接这么写:var hp = 100
下边可以有为什么可以这么写的原因。还有短变量初始化操作: hp := 100
匿名变量
匿名变量的特点是一个下画线“”,“”本身就是一个特殊的标识符,被称为空白标识符。它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。使用匿名变量时,只需要在变量声明的地方使用下画线替换即可。例如:
func GetData() (int, int) {
return 100, 200
}
func main(){
a, _ := GetData()
_, b := GetData()
fmt.Println(a, b)
}
常量
常量像变量一样声明,但使用 const
关键字。常量可以是字符、字符串、布尔值或数字值。不能使用 :=
语法声明常量。
数字常量是高精度 值 。无类型常量采用其上下文所需的类型。(一个 int
最多可以存储 64 位整数,有时更少.)
和变量声明一样,可以批量声明多个常量:
const (
e = 2.7182818
pi = 3.1415926
)
iota 常量生成器
常量声明可以使用 iota 常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。在一个 const 声明语句中,在第一个声明的常量所在的行,iota 将会被置为 0,然后在每一个有常量声明的行加一。
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
for语句
for语法结构
Go 只有一个循环结构,即 for
循环.
基本 for
循环包含三个由分号分隔的组件:
- init 语句: 在第一次迭代之前执行
- 条件表达式:在每次迭代前求值
- post 语句:在每次迭代结束时执行
init语句通常是一个简短的变量声明,并且在那里声明的变量仅在该 for
语句的范围内可见.
一旦布尔条件评估为 false
,循环将停止迭代。init 和 post 语句是可选的;如果省略循环条件,它将永远循环,因此可以紧凑地表示无限循环.
注意: 与 C、Java 或 JavaScript 等其他语言不同,for
语句的三个组件周围没有括号,并且大括号 { }
始终需要。
package main
import "fmt"
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
for ; sum < 1000; {
sum += sum
}
fmt.Println(sum)
}
for 是Go中的while
C和Java 的 while
在 Go 中叫做 for
。
package main
import "fmt"
func main() {
sum := 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
if语句
Go 的 if
语句就像它的 for
循环;表达式不需要用括号 ( )
括起来,但需要大括号 { }
.
package main
import (
"fmt"
"math"
)
func sqrt(x float64) string {
if x < 0 {
return sqrt(-x) + "i"
}
return fmt.Sprint(math.Sqrt(x))
}
func main() {
fmt.Println(sqrt(2), sqrt(-4))
}
就像 for
, if
语句可以在条件之前以短语句执行。语句声明的变量作用域仅在 if
之内。
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
If 和 else
在 if
短语句中声明的变量也可以在任何 else
块中使用。
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// can't use v here, though
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
)
}
switch
switch
语句是编写一连串 if - else
语句的简便方法。它运行其值等于条件表达式的第一个 case。
Go 的 switch
类似于 C、C++、Java、JavaScript
和 PHP
中的 switch
,只是 Go 只运行选定的case
,而不是所有后续case。
实际上,Go 自动提供了在这些语言中每个 case
后面所需的 break
语句。另一个重要的区别是 Go 的 switch
的case
不需要是常量,所涉及的值也不需要是整数。
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.\n", os)
}
}
switch 的求值顺序
switch 的 case
语句从上到下顺次执行,直到匹配成功时停止。 如果 i==0
不调用 f
switch i {
case 0:
case f():
}
注意: Go playground 场中的时间总是从 2009-11-10 23:00:00 UTC 开始。
没有条件的switch
没有条件的 Switch 同 switch true
一样。这种构造可以是编写长 if-then-else 链的一种干净方式.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}
defer语句
defer 语句推迟函数的执行,直到周围的函数返回。
延迟调用的参数会立即计算,但直到周围的函数返回时才会执行函数调用。
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
Stacking defers(defer栈)
延迟的函数调用被推送到栈上。当函数返回时,其延迟调用将按后进先出顺序执行。
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
结果:
hello
world
go中关键字
Go语言中的关键字一共有 25 个:
break | default | func | interface | select |
---|---|---|---|---|
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthrough | if | range | type |
continue | for | import | return | var |
类型转换
在 Go 中不同类型的项之间的赋值需要显式转换。比如
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
或者
i := 42
f := float64(i)
u := uint(f)
类型推断
当声明一个变量而不指定显式类型 (通过使用 :=
syntax 或 var =
表达式语法), 变量的类型是从右侧的值推断出来的.
当右值声明了类型时,新变量的类型与其相同:
var i int
j := i // j is an int
但是,当右侧包含无类型的数字常量, 新的变量可以是 int
, float64
, 或 complex128
取决于常量的精度:
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
Go函数
一个函数可以接受零个或多个参数.
在本例中,add
接受两个 int
类型的参数.
请注意,类型在变量名 之后 .
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func main() {
fmt.Println(add(42, 13))
}
当两个或多个连续命名函数参数共享一个类型时,除了最后一个之外,可以省略所有类型.
在这个例子中,我们缩短
x int, y int
可以写成
x, y int
Multiple results(多值返回)
一个函数可以返回任意数量的结果。该 swap
函数返回两个字符串。
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
Named return values(命名的返回值)
Go 的返回值可以命名。如果是这样,它们将被视为在函数顶部定义的变量。
这些名称应用于记录返回值的含义。
不带参数的 return
语句返回命名的返回值,这被称为“裸”返回。
裸返回语句应该只在短函数中使用,就像这里显示的例子一样。它们会损害较长函数的可读性。
package main
import "fmt"
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
func main() {
fmt.Println(split(17))
}
函数值
函数也是值。它们可以像其他值一样传递,函数值可以用作函数参数和返回值
package main
import (
"fmt"
"math"
)
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(hypot(5, 12))
fmt.Println(compute(hypot))
fmt.Println(compute(math.Pow))
}
函数闭包
Go 函数可以是闭包。闭包是引用其主体外部的变量的函数值。该函数可以访问并分配给引用的变量;从这个意义上说,函数是“绑定”到变量的.
例如, adder
函数返回一个闭包。每个闭包都绑定到它自己的 sum
变量.
Go语言nil:空值/零值
在Go语言中,布尔类型的零值(初始值)为 false,数值类型的零值为 0,字符串类型的零值为空字符串"",而指针、切片、映射、通道、函数和接口的零值则是 nil。
nil 是Go语言中一个预定义好的标识符,有过其他编程语言开发经验的开发者也许会把 nil 看作其他语言中的 null(NULL),其实这并不是完全正确的,因为Go语言中的 nil 和其他语言中的 null 有很多不同点。
nil 标识符是不能比较的
这点和 python 等动态语言是不同的,在 python 中,两个 None 值永远相等。对于 nil 来说是一种未定义的操作。
nil 不是关键字或保留字
nil 并不是Go语言的关键字或者保留字,也就是说我们可以定义一个名称为 nil 的变量,比如下面这样:var nil = errors.New("my god")
虽然上面的声明语句可以通过编译,但是并不提倡这么做。
nil 没有默认类型
package main
import (
"fmt"
)
func main() {
fmt.Printf("%T", nil)
print(nil)
}
运行结果如下所示:
.\main.go:9:10: use of untyped nil
不同类型 nil 的指针是一样的
package main
import (
"fmt"
)
func main() {
var arr []int
var num *int
fmt.Printf("%p\n", arr)
fmt.Printf("%p", num)
}
结果:
0x0
0x0
通过运行结果可以看出 arr 和 num 的指针都是 0x0。
nil 是 map、slice、pointer、channel、func、interface 的零值
零值是Go语言中变量在声明之后但是未初始化被赋予的该类型的一个默认值。
不同类型的 nil 值占用的内存大小可能是不一样的
一个类型的所有的值的内存布局都是一样的,nil 也不例外,nil 的大小与同类型中的非 nil 类型的大小是一样的。但是不同类型的 nil 值的大小可能不同。
具体的大小取决于编译器和架构,上面打印的结果是在 64 位架构和标准编译器下完成的,对应 32 位的架构的,打印的大小将减半。