Packages
Go程序是由package组成。
包类似于其它语言里的库(libraries)或者模块(modules)。一个包由位于单个目录下的一个或多个.go源代码文件组成,目录定义包的作用。
Go的标准库提供了100多个包,以支持常见功能,如输入、输出、排序以及文本处理。比如fmt包,就含有格式化输出、接收输入的函数。Println是其中一个基础函数,可以打印以空格间隔的一个或多个值,并在最后添加一个换行符,从而输出一整行。
fmt.Println("Hello, 世界", "xxx")
程序开始于main package。但是不一定需要放到main目录下。
main包比较特殊。它定义了一个独立可执行的程序,而不是一个库。在main里的main 函数也很特殊,它是整个程序执行时的入口。
必须恰当导入需要的包,缺少了必要的包或者导入了不需要的包,程序都无法编译通过。
Import
通过import导入其他package。可以逐个导入,或者通过小括号()组合起来导入。注意,没有逗号。
// 逐个导入import "fmt"import "math"//组合导入import ("fmt""math")
Exported names
Exported names相当于public变量或函数。以大写字母开头即为exported names。
//此处math.Pi, Pi为exported namefunc main() {fmt.Println(math.Pi)}
Variables
标准格式
Go语言的变量声明的标准格式为:关键字 var 开头,后置变量类型,行尾无须分号。
var i int
批量格式
var (a intb stringc []float32d func() boole struct {x int})
或者
var c, python, java bool
或者声明时赋值
var i, j int = 1, 2
如果声明时赋值,可以不写类型。会根据值自动判断类型。
var c, python, java = true, false, "no!"
简短格式
名字 := 表达式
- 定义变量,同时显式初始化。
“:=”不能重新声明一个已经声明过的对象。但是如果使用“:=”同时为多个变量赋值,只要有一个变量是之前没有声明过的,即可通过编译。 ```go //以下是非法的 var i = 1 i := 2i := 1//等价于var i = 1
//以下的合法的,此处并没有重新声明i,只是对i进行了赋值 var i = 1 i, flag := 2, false
- 不能提供数据类型。- 只能用在函数内部。“:=”只能用在函数体中,一个通用的用法是,用在if,for,switch语句的初始化,使变量成为一个临时变量。```gofunc main() {for j := 3; j <= 5; j++ {fmt.Println(j) //这里可以正常输出}fmt.Println(j) //这里会报错}
默认赋值
如果变量在定义的时候没有初始值,会赋默认值。
var i int //0var f float64 //0var b bool //falsevar s string //""
获取变量类型
利用反射机制。reflect.TypeOf()
mySlice := []string{"aaa", "bbb"}fmt.Println(reflect.TypeOf(mySlice))
类型转换
通过T(v)的方式把变量v强制转换为类型T。
var i int = 42 //等价于 i:=42var f float64 = float64(i) //等价于 f:=float64(i)var u uint = uint(f)
必须通过这种方式显示进行类型转换,下面这种是非法的,会报错。
// 非法var f float64 = math.Sqrt(25)// 合法。因为Math.Sqrt()的参数是float类型,必须进行显示类型转换var f float64 = math.Sqrt(float(25))
常量
常量的定义以const开头。可以指定类型,也可以不指定。
常量的类型可以是character,string,boolean,或者numeric values。
常量不能用“:=”定义。
const Pi = 3.14const World = "世界"const Truth = trueconst b string = "abc"
Functions
go函数的返回值可以是多个。函数名后两个括号,一个是参数,一个是返回值。
func swap(x, y string) (string, string) {return y, x}func main() {a, b := swap("hello", "world")fmt.Println(a, b)}
函数返回时,如果在返回值定义类型时定义了返回的变量,可以不指定返回的值。如下例,返回的变量就是x和y。
运行结果是:“7 10”。
func split(sum int) (x, y int) {x = sum * 4 / 9y = sum - xreturn}func main() {fmt.Println(split(17))}
For循环
Go语言只有一个循环结构,就是for循环。初始化语句,条件表达式和后置语句部分不加“()”,循环体必须加“{}”。
循环变量是局部变量,scope仅在for循环中。使用“:=”赋值。
即使循环体只有一行语句,也要加“{ }”。
func main() {sum := 0for i := 0; i < 10; i++ {sum += i}}
可以省略初始化语句和后置语句。
func main() {sum := 1for ; sum < 1000; {sum += sum}}
去掉分号也可以使用for。相当于其他语言中的while语句。
func main() {sum := 1for sum < 1000 {sum += sum}}
无限循环
for{}
用于遍历。
每次循环迭代,range产生一对值;索引以及在该索引处的元素值。这个例子不需要索引,但range的语法要求,要处理元素,必须处理索引。可使用用空标识符(blank identifier),即_(也就是下划线),丢弃不需要的循环索引,并保留元素值。
for _, arg := range os.Args[1:] {s += sep + argsep = " "}
If
if语句的判断条件不加“()”,执行部分必须加“{}”。
可以在条件表达式前以简短格式定义变量,scope是if和else之内。
if v := math.Pow(x, n); v < lim {return v} else {fmt.Printf("%g >= %g\n", v, lim)}
switch
switch语句与其他语言相同,但是case语句不需要break,匹配后会自动break。如果需要让其匹配后继续执行default分支,加fallthrough。
switch os := runtime.GOOS; os {case "darwin":fmt.Println("OS X.")case "linux":fmt.Println("Linux.")fallthrough //加了fallthrough,所以default也会执行default:// freebsd, openbsd,// plan9, windows...fmt.Printf("%s.\n", os)}
switch可以不加条件使用,相当于switch true。
switch {case t.Hour() < 12:fmt.Println("Good morning!")case t.Hour() < 17:fmt.Println("Good afternoon.")default:fmt.Println("Good evening.")}
defer语句
defer语句会将函数推迟到外层函数返回后执行。
它的参数会立即求值,然后压栈。等外层函数返回后,再按照先进后出的顺序执行。
下例的执行结果是,“done 9 8 7 6 5 4 3 2 1 0”。(此处用空格代替换行)
func main() {fmt.Println("counting")for i := 0; i < 10; i++ {defer fmt.Println(i)}fmt.Println("done")}
指针
指针中存储了变量的内存地址。
与C不同的是,Go没有指针运算。
*T指向T类型的变量,其零值为nil。
var p *int
&操作符会生成一个指向其操作数的指针。
i := 42p = &i
*操作符表示指针实际指向的内存地址的值。这就是常说的“间接引用”或“重定向”。
fmt.Println(*p) //通过指针p读取i*p = 21 //通过指针p设置i
结构体
一个结构体就是一组字段。
结构体可以隐式给全部字段赋值,或者显式只给部分字段赋值。
结构体的字段用“.”来访问。
结构体可以通过指针进行间接访问。
type Vertex struct {X intY int}func main() {v := Vertex{1, 2} //创建一个Vertex类型的结构体v2 := Vertex{X: 1} //给指定结构体字段赋值p := &Vertex{3, 4} //创建一个*Vertext类型的结构体(指针)v.X = 4 //直接访问结构体字段(*p).X = 5 //指针间接访问结构体字段p.x = 5 //指针隐式间接访问结构体字段}
数组
类型n[T]表示拥有n个T类型值的数组。数组不能改变大小。
var a [10]int
slice
数组的长度是不能改变的,而slice则为数组元素提供了动态大小的视角。可以当作是在数组上的滑动窗口。
类型[]T表示元素类型为T的slice。
slice通过两个下标来界定。一个上界,一个下界,冒号分隔。
a[low: high] //左闭右开,low包含,high不包含
切片实际不存储数据,更改切片的元素,实际就是更改底层数组的元素。与之共享底层数组的其他切片会感知到变化。
names := [4]string{"John", "Paul", "George", "Ringo"}a := names[0:2]b := names[1:3]b[0] = "XXX"fmt.Println(a, b) //[John XXX] [XXX George]fmt.Println(names) //[John XXX George Ringo]
创建切片并初始化时,会同时创建一个底层数组。
q := []int{2, 3, 5, 7, 11, 13}r := []bool{true, false, true, true, false, true}s := []struct {i intb bool}{{2, true},{3, false},{5, true},{7, true},{11, false},{13, true},}
map
counts := make(map[string]int)input := bufio.NewScanner(os.Stdin)for input.Scan() {counts[input.Text()]++}// NOTE: ignoring potential errors from input.Err()for line, n := range counts {if n > 1 {fmt.Printf("%d\t%s\n", n, line)}}
当两次输入相同时,打印结果却不同。
map的迭代顺序并不确定,从实践来看,该顺序随机,每次运行都会变化。这种设计是有意为之的,因为能防止程序依赖特定遍历顺序。(可参考链接)
