Go 编程实例 Go by Example
原文作者:Go 技术论坛文档:《Go 编程实例 Go by Example(2020)》
转自链接:https://learnku.com/docs/gobyexample/2020
1.HelloWorld
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
2.datatypes
package main
import "fmt"
func main() {
//字符串通过+号拼接
fmt.Println("hello"+"world")
// 整数和浮点数
fmt.Println("1+1 =", 1+1)
fmt.Println("7.0/3.0 =", 7.0/3.0)
// 布尔型,以及常见的布尔操作。
fmt.Println(true && false)
fmt.Println(true || false)
fmt.Println(!true)
}
3.variables
package main
import "fmt"
func main() {
// `var` 声明 1 个或者多个变量。
var a = "initial"
fmt.Println(a)
// 你可以一次性声明多个变量。
var b, c int = 1, 2
fmt.Println(b, c)
// Go 将自动推断已经初始化的变量类型。
var d = true
fmt.Println(d)
// 声明后却没有给出对应的初始值时,变量将会初始化为
// _零值_ 。例如,一个 `int` 的零值是 `0`。
var e int
fmt.Println(e)
// `:=` 语法是声明并初始化变量的简写,例如
// 这个例子中的 `var f string = "short"`。
f := "short"
fmt.Println(f)
}
4.const
package main
import "fmt"
import "math"
// `const` 用于声明一个常量。
const s string = "constant"
func main() {
fmt.Println(s)
// `const` 语句可以出现在任何 `var` 语句可以出现
// 的地方
const n = 500000000
// 常数表达式可以执行任意精度的运算
const d = 3e20 / n
fmt.Println(d)
// 数值型常量没有确定的类型,直到被给定
// ,比如一次显示的类型转化。
fmt.Println(int64(d))
// 当上下文需要时,比如变量赋值或者函数调用,
// 一个数可以被给定一个类型。举个例子,这里的 `math.Sin`
// 函数需要一个 `float64` 的参数。
fmt.Println(math.Sin(n))
}
5.for
package main
import "fmt"
func main() {
// 最基础的方式,单个循环条件。
i := 1
for i <= 3 {
fmt.Println(i)
i = i + 1
}
// 经典的初始/条件/后续 `for` 循环。
for j := 7; j <= 9; j++ {
fmt.Println(j)
}
// 不带条件的 `for` 循环将一直重复执行,直到在循环体内使用
// 了 `break` 或者 `return` 来跳出循环。
for {
fmt.Println("loop")
break
}
// 你也可以使用 `continue` 来跳到下一个循环迭代
for n := 0; n <= 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
}
6.if_else
package main
import "fmt"
func main() {
// 这里是一个基本的例子。
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
// 你可以不要 `else` 只用 `if` 语句。
if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}
// 在条件语句之前可以有一个声明语句;在这里声明的变量
// 可以在所有的条件分支中使用。
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
7.switch_case
package main
import "fmt"
import "time"
func main() {
// 一个基本的 `switch`。
i := 2
fmt.Print("write ", i, " as ")
switch i {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
}
// 在同一个 `case` 语句中,你可以使用逗号来分隔多个表
// 达式。在这个例子中,我们还使用了可选的
// `default` 分支。
switch time.Now().Weekday() {
case time.Saturday, time.Sunday:
fmt.Println("It's the weekend")
default:
fmt.Println("It's a weekday")
}
// 不带表达式的 `switch` 是实现 if/else 逻辑的另一种
// 方式。这里还展示了 `case` 表达式也可以不使用常量。
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
// 类型开关 (`type switch`) 比较类型而非值。可以用来发现一个接口值的
// 类型。在这个例子中,变量 `t` 在每个分支中会有相应的类型。
whatAmI := func(i interface{}) {
switch t := i.(type) {
case bool:
fmt.Println("I'm a bool")
case int:
fmt.Println("I'm an int")
default:
fmt.Printf("Don't know type %T\n", t)
}
}
whatAmI(true)
whatAmI(1)
whatAmI("hey")
}
8.array
package main
import "fmt"
func main() {
// 这里我们创建了一个数组 `a` 来存放刚好 5 个 `int`。
// 元素的类型和长度都是数组类型的一部分。数组默认是
// 零值的,对于 `int` 数组来说也就是 `0`。
var a [5]int
fmt.Println("emp:", a)
// 我们可以使用 `array[index] = value` 语法来设置数组
// 指定位置的值,或者用 `array[index]` 得到值。
a[4] = 100
fmt.Println("set:", a)
fmt.Println("get:", a[4])
// 使用内置函数 `len` 返回数组的长度。
fmt.Println("len:", len(a))
// 使用这个语法在一行内声明并初始化一个数组。
b := [5]int{1, 2, 3, 4, 5}
fmt.Println("dcl:", b)
// 数组类型是一维的,但是你可以组合
// 构造多维的数据结构。
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
}
9.slice
package main
import "fmt"
func main() {
// 与数组不同,slice 的类型仅由它所包含的元素决定(不需要
// 元素的个数)。要创建一个长度非零的空
// slice,需要使用内建的方法 `make`。这里我们创建了一
// 个长度为3的 `string` 类型 slice(初始化为零值)。
s := make([]string, 3)
fmt.Println("emp:", s)
// 我们可以和数组一样设置和得到值
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("set:", s)
fmt.Println("get:", s[2])
// `len` 返回 slice 的长度
fmt.Println("len:", len(s))
// 除了基本操作外,slice 支持比数组更丰富的操作。
// 其中一个是内建的 `append`,它返回一个包含了一个
// 或者多个新值的 slice。注意由于 `append` 可能返回
// 新的 slice,我们需要接受其返回值。
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println("apd:", s)
// Slice 也可以被 `copy`。这里我们创建一个空的和 `s` 有
// 相同长度的 slice `c`,并且将 `s` 复制给 `c`。
c := make([]string, len(s))
copy(c, s)
fmt.Println("cpy:", c)
// Slice 支持通过 `slice[low:high]` 语法进行“切片”操
// 作。例如,这里得到一个包含元素 `s[2]`, `s[3]`,
// `s[4]` 的 slice。
l := s[2:5]
fmt.Println("sl1:", l)
// 这个 slice 从 `s[0]` 切片到 `s[5]`(不包含)。
l = s[:5]
fmt.Println("sl2:", l)
// 这个 slice 从 `s[2]` (包含)开始切片。
l = s[2:]
fmt.Println("sl3:", l)
// 我们可以在一行代码中声明并初始化一个 slice 变量。
t := []string{"g", "h", "i"}
fmt.Println("dcl:", t)
// Slice 可以组成多维数据结构。内部的 slice 长度可以不
// 一致,这和多维数组不同。
twoD := make([][]int, 3)
for i := 0; i < 3; i++ {
innerLen := i + 1
twoD[i] = make([]int, innerLen)
for j := 0; j < innerLen; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
}
10.map
package main
import "fmt"
func main() {
// 要创建一个空 map,需要使用内建的 `make`:
// `make(map[key-type]val-type)`.
m := make(map[string]int)
// 使用典型的 `make[key] = val` 语法来设置键值对。
m["k1"] = 7
m["k2"] = 13
// 使用例如 `fmt.Println` 来打印一个 map 将会输出所有的
// 键值对。
fmt.Println("map:", m)
// 使用 `name[key]` 来获取一个键的值
v1 := m["k1"]
fmt.Println("v1: ", v1)
// 当对一个 map 调用内建的 `len` 时,返回的是键值对
// 数目
fmt.Println("len:", len(m))
// 内建的 `delete` 可以从一个 map 中移除键值对
delete(m, "k2")
fmt.Println("map:", m)
// 当从一个 map 中取值时,可选的第二返回值指示这个键
// 是否在这个 map 中。这可以用来消除键不存在和键有零值,
// 像 `0` 或者 `""` 而产生的歧义。这里我们不需要值,所以
// 用_空白标识符(blank identifier)_忽略。
_, prs := m["k2"]
fmt.Println("prs:", prs)
// 你也可以通过这个语法在同一行声明和初始化一个新的
// map。
n := map[string]int{"foo": 1, "bar": 2}
fmt.Println("map:", n)
}
11.range
package main
import "fmt"
func main() {
// 这里我们使用 `range` 来对 slice 中的元素求和。
// 对于数组也可以采用这种方法。
nums := []int{2, 3, 4}
sum := 0
for _, num := range nums {
sum += num
}
fmt.Println("sum:", sum)
// `range` 在数组和 slice 中提供对每项的索引和值的访问。
// 上面我们不需要索引,所以我们使用 _空白标识符_
// `_` 来忽略它。有时候我们实际上是需要这个索引的。
for i, num := range nums {
if num == 3 {
fmt.Println("index:", i)
}
}
// `range` 在 map 中迭代键值对。
kvs := map[string]string{"a": "apple", "b": "banana"}
for k, v := range kvs {
fmt.Printf("%s -> %s\n", k, v)
}
// `range` 也可以只遍历 map 的键。
for k := range kvs {
fmt.Println("key:", k)
}
// `range` 在字符串中迭代 unicode 码点(code point)。
// 第一个返回值是字符的起始字节位置,然后第二个是字符本身。
for i, c := range "go" {
fmt.Println(i, c)
}
}
12.function
package main
import "fmt"
// 这里是一个函数,接受两个 `int` 并且以 `int` 返回它们的和
func plus(a int, b int) int {
// Go 需要明确的返回,不会自动返回最
// 后一个表达式的值
return a + b
}
// 当多个连续的参数为同样类型时,最多可以仅声明最后一个参数类型
// 而忽略之前相同类型参数的类型声明。
func plusPlus(a, b, c int) int {
return a + b + c
}
func main() {
// 通过 `name(args)` 来调用函数,
res := plus(1, 2)
fmt.Println("1+2 =", res)
res = plusPlus(1, 2, 3)
fmt.Println("1+2+3 =", res)
}
13.多返回值func
package main
import "fmt"
// `(int, int)` 在这个函数中标志着这个函数返回 2 个 `int`。
func vals() (int, int) {
return 3, 7
}
func main() {
// 这里我们通过_多赋值_操作来使用这两个不同的返回值。
a, b := vals()
fmt.Println(a)
fmt.Println(b)
// 如果你仅仅需要返回值的一部分的话,你可以使用空白标识符`_`。
_, c := vals()
fmt.Println(c)
}
14.变参func
package main
import "fmt"
// 这个函数接受任意数目的 `int` 作为参数。
func sum(nums ...int) {
fmt.Print(nums, " ")
total := 0
for _, num := range nums {
total += num
}
fmt.Println(total)
}
func main() {
// 变参函数使用常规的调用方式,传入独立的参数。
sum(1, 2)
sum(1, 2, 3)
// 如果你有一个含有多个值的 slice,想把它们作为参数
// 使用,你要这样调用 `func(slice...)`。
nums := []int{1, 2, 3, 4}
sum(nums...)
}
15.闭包closures
package main
import "fmt"
// 这个 `intSeq` 函数返回另一个在 `intSeq` 函数体内定义的
// 匿名函数。这个返回的函数使用闭包的方式 _隐藏_ 变量 `i`。
func intSeq() func() int {
i := 0
return func() int {
i++
return i
}
}
func main() {
// 我们调用 `intSeq` 函数,将返回值(一个函数)赋给
// `nextInt`。这个函数的值包含了自己的值 `i`,这样在每
// 次调用 `nextInt` 时都会更新 `i` 的值。
nextInt := intSeq()
// 通过多次调用 `nextInt` 来看看闭包的效果。
fmt.Println(nextInt())
fmt.Println(nextInt())
fmt.Println(nextInt())
// 为了确认这个状态对于这个特定的函数是唯一的,我们
// 重新创建并测试一下。
newInts := intSeq()
fmt.Println(newInts())
}
16.递归recursion
package main
import "fmt"
// `fact` 函数在到达 `fact(0)` 前一直调用自身。
func fact(n int) int {
if n == 0 {
return 1
}
return n * fact(n-1)
}
func main() {
fmt.Println(fact(7))
}
17.pointer
package main
import "fmt"
// 我们将通过两个函数:`zeroval` 和 `zeroptr` 来比较指针和
// 值类型的不同。`zeroval` 有一个 `int` 型参数,所以使用值
// 传递。`zeroval` 将从调用它的那个函数中得到一个 `ival`
// 形参的拷贝。
func zeroval(ival int) {
ival = 0
}
// `zeroptr` 有一和上面不同的 `*int` 参数,意味着它用了一
// 个 `int`指针。函数体内的 `*iptr` 接着_解引用_这个指针,
// 从它内存地址得到这个地址对应的当前值。对一个解引用的指
// 针赋值将会改变这个指针引用的真实地址的值。
func zeroptr(iptr *int) {
*iptr = 0
}
func main() {
i := 1
fmt.Println("initial:", i)
zeroval(i)
fmt.Println("zeroval:", i)
// 通过 `&i` 语法来取得 `i` 的内存地址,即指向 `i` 的指针。
zeroptr(&i)
fmt.Println("zeroptr:", i)
// 指针也是可以被打印的。
fmt.Println("pointer:", &i)
}
18.struct
package main
import "fmt"
// 这里的 `person` 结构体包含了 `name` 和 `age` 两个字段。
type person struct {
name string
age int
}
func main() {
// 使用这个语法创建新的结构体元素。
fmt.Println(person{"Bob", 20})
// 你可以在初始化一个结构体元素时指定字段名字。
fmt.Println(person{name: "Alice", age: 30})
// 省略的字段将被初始化为零值。
fmt.Println(person{name: "Fred"})
// `&` 前缀生成一个结构体指针。
fmt.Println(&person{name: "Ann", age: 40})
// 使用`.`来访问结构体字段。
s := person{name: "Sean", age: 50}
fmt.Println(s.name)
// 也可以对结构体指针使用`.` - 指针会被自动解引用。
sp := &s
fmt.Println(sp.age)
// 结构体是可变(mutable)的。
sp.age = 51
fmt.Println(sp.age)
}
19.method
package main
import "fmt"
type rect struct {
width, height int
}
// 这里的 `area` 方法有一个_接收器(receiver)类型_ `rect`。
func (r *rect) area() int {
return r.width * r.height
}
// 可以为值类型或者指针类型的接收器定义方法。这里是一个
// 值类型接收器的例子。
func (r rect) perim() int {
return 2*r.width + 2*r.height
}
func main() {
r := rect{width: 10, height: 5}
// 这里我们调用上面为结构体定义的两个方法。
fmt.Println("area: ", r.area())
fmt.Println("perim:", r.perim())
// Go 自动处理方法调用时的值和指针之间的转化。你可以使
// 用指针来调用方法来避免在方法调用时产生一个拷贝,或者
// 让方法能够改变接受的结构体。
rp := &r
fmt.Println("area: ", rp.area())
fmt.Println("perim:", rp.perim())
}
20.interface
package main
import "fmt"
import "math"
// 这里是一个几何体的基本接口。
type geometry interface {
area() float64
perim() float64
}
// 在我们的例子中,我们将在类型 `rect` 和 `circle` 上实现
// 这个接口
type rect struct {
width, height float64
}
type circle struct {
radius float64
}
// 要在 Go 中实现一个接口,我们就需要实现接口中的所有
// 方法。这里我们在 `rect` 上实现了 `geometry` 接口。
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perim() float64 {
return 2*r.width + 2*r.height
}
// `circle` 的实现。
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
return 2 * math.Pi * c.radius
}
// 如果一个变量具有接口类型,那么我们可以调用指定接口中的
// 方法。这里有一个通用的 `measure` 函数,利用它来在任
// 何的 `geometry` 上工作。
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perim())
}
func main() {
r := rect{width: 3, height: 4}
c := circle{radius: 5}
// 结构体类型 `circle` 和 `rect` 都实现了 `geometry`
// 接口,所以我们可以使用它们的实例作为 `measure` 的参数。
measure(r)
measure(c)
}
21.error
package main
import "errors"
import "fmt"
// 按照惯例,错误通常是最后一个返回值并且是 `error` 类
// 型,一个内建的接口。
func f1(arg int) (int, error) {
if arg == 42 {
// `errors.New` 构造一个使用给定的错误信息的基本
// `error` 值。
return -1, errors.New("can't work with 42")
}
// 返回错误值为 nil 代表没有错误。
return arg + 3, nil
}
// 通过实现 `Error` 方法来自定义 `error` 类型是可以的。
// 这里使用自定义错误类型来表示上面例子中的参数错误。
type argError struct {
arg int
prob string
}
func (e *argError) Error() string {
return fmt.Sprintf("%d - %s", e.arg, e.prob)
}
func f2(arg int) (int, error) {
if arg == 42 {
// 在这个例子中,我们使用 `&argError` 语法来建立一个
// 新的结构体,并提供了 `arg` 和 `prob` 这两个字段
// 的值。
return -1, &argError{arg, "can't work with it"}
}
return arg + 3, nil
}
func main() {
// 下面的两个循环测试了各个返回错误的函数。注意在 `if`
// 行内的错误检查代码,在 Go 中是一个普遍的用法。
for _, i := range []int{7, 42} {
if r, e := f1(i); e != nil {
fmt.Println("f1 failed:", e)
} else {
fmt.Println("f1 worked:", r)
}
}
for _, i := range []int{7, 42} {
if r, e := f2(i); e != nil {
fmt.Println("f2 failed:", e)
} else {
fmt.Println("f2 worked:", r)
}
}
// 你如果想在程序中使用一个自定义错误类型中的数据,你
// 需要通过类型断言来得到这个错误类型的实例。
_, e := f2(42)
if ae, ok := e.(*argError); ok {
fmt.Println(ae.arg)
fmt.Println(ae.prob)
}
}