Go 语言基础(3) | 青训营笔记

1,361 阅读5分钟

有个疑惑,掘金的主题还有代码样式里哪个是支持显示代码行号的?然后为啥这里代码段一个 Tab 给我渲染了八个空格。

这次是语法专题,还是分开发,全都放一篇我自己复习的时候压力太大了。

Go 的变量类型

来个小例子:

package main

import (
	"fmt"
	"math"
)

func main() {

	var a = "initial"

	var b, c int = 1, 2

	var d = true

	var e float64

	f := float32(e)

	g := a + "foo"
	fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
	fmt.Println(g)                // initialapple

	const s string = "constant"
	const h = 500000000
	const i = 3e20 / h
	fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}

Go 也是强类型语言,在声明的时候有两种方式,第一种是 var name = value,这种情况下一般是根据给变量名赋的值自动推导变量类型,当然也可以显式的写出来变量类型。声明的时候不一定要初始化,所以规定好变量类型是有必要的。第二种方式是 name := value。声明时,变量的默认类型是:boolruneintfloat64complex128string 这些。

常量的声明就是把 var 改成 const,常量的类型根据上下文自动确定。

运行结果:

initial 1 2 true 0 0
initialfoo
constant 500000000 6e+11 -0.28470407323754404 0.7591864109375384

改一下带入正弦函数的输入,可以测试出来默认是弧度制。

Go 的 if 和 else

来个小例子:

package main

import "fmt"

func main() {

	if 7%2 == 0 {
		fmt.Println("7 is even")
	} else {
		fmt.Println("7 is odd")
	}

	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")
	}
}

ifelseC 里面的类似,但是后面接的条件语句没有括号包住。如果顺手就把括号写上了,保存文件的时候编辑器会把括号去掉。然后条件语句那个代码块必须用大括号包起来,不能像 c 或者 C++ 一样只有一行代码就不写大括号了。

运行结果如下:

7 is odd
8 is divisible by 4
9 has 1 digit

Go 的循环

来个小例子:

package main

import "fmt"

func main() {

	i := 1
	for {
		fmt.Println("loop")
		break
	}
	for j := 7; j < 9; j++ {
		fmt.Println(j)
	}

	for n := 0; n < 5; n++ {
		if n%2 == 0 {
			continue
		}
		fmt.Println(n)
	}
	for i <= 3 {
		fmt.Println(i)
		i = i + 1
	}
}

Go 只有 for 循环,其他的循环语法没有。啥条件都不写就是死循环,然后经典的 C 风格 for 循环,还是没有小括号把条件包住。分三段的条件可以省略其中任何一段,也不用写分号表示某一段被省略了,continue 继续循环和 break 跳出循环还是一样的。

运行结果如下:

loop
7
8
1
3
1
2
3

Go 的 switch

来个小例子:

package main

import (
	"fmt"
	"time"
)

func main() {

	a := 2
	switch a {
	case 1:
		fmt.Println("one")
	case 2:
		fmt.Println("two")
	case 3:
		fmt.Println("three")
	case 4, 5:
		fmt.Println("four or five")
	default:
		fmt.Println("other")
	}

	t := time.Now()
	switch {
	case t.Hour() < 12:
		fmt.Println("It's before noon")
	default:
		fmt.Println("It's after noon")
	}
}

C 类似的语法,条件变量不用小括号包上,每个情况结束以后不需要显式的写上 break,一个情况执行完也不会继续把下面的情况全部执行一遍,直接就跳出来了。Goswitch 可以把字符串,结构体当条件变量,还可以取代 if ... else ... 的嵌套,例子里面就有一个 switch 没加任何条件变量直接拿每个 case 来当 if ... else ... 写的。

运行结果:

two
It's after noon

我记得以前有个技巧是用卫语句改善条件分支嵌套,不知道这个技巧在 Go 代码里面是怎么实践的。

Go 的数组

来个小例子:

package main

import "fmt"

func main() {

	var a [5]int
	a[4] = 100
	fmt.Println("get:", a[2])
	fmt.Println("len:", len(a))

	b := [5]int{1, 2, 3, 4, 5}
	fmt.Println(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)
}

数组长度固定,空间开大了浪费开少了又要扩容,然后原来数组的内容要一个一个搬到新的数组里,麻烦,真实的业务代码里不是很常用,之后的切片才是更常用的。

运行结果:

get: 0
len: 5
[1 2 3 4 5]
2d:  [[0 1 2] [1 2 3]]

不过 Go 某些方面还是更方便一些,二维数组直接就打印出来了。

Go 的切片

来个小例子:

package main

import "fmt"

func main() {

	s := make([]string, 3)
	s[0] = "a"
	s[1] = "b"
	s[2] = "c"
	fmt.Println("get:", s[2])   // c
	fmt.Println("len:", len(s)) // 3

	s = append(s, "d")
	s = append(s, "e", "f")
	fmt.Println(s) // [a b c d e f]

	c := make([]string, len(s))
	copy(c, s)
	fmt.Println(c) // [a b c d e f]

	fmt.Println(s[2:5]) // [c d e]
	fmt.Println(s[:5])  // [a b c d e]
	fmt.Println(s[2:])  // [c d e f]

	good := []string{"g", "o", "o", "d"}
	fmt.Println(good) // [g o o d]
}

切片是可变长度的数组,用 make() 来创建切片。创建的时候要规定元素的数据类型以及长度。用 append() 来往切片后面添加新元素。在添加新元素的时候 append() 之后的结果是要重新赋给原切片变量的,因为 slice 底层原理是开一个长度预设容量计算好的数组和一个指向数组的指针,append() 操作之后原来的 slice 容量不够,扩容以后是一个新的 slice,于是赋值操作是必须的。copy() 用来在两个 slice 之间拷贝数据。

都叫切片了,这个数据结构也和 Python 里面同名的数据结构一样有方便的切片操作,但是不支持负数索引,所以还是需要一些数学运算。