1 Go基础
Table of Contents
包
每个Go程序都是由包组成的, 程序运行的入口是包 main
.
package main import ( "fmt" "math/rand" ) func main() { fmt.Println("My favorite number is", rand.Intn(10)) }
这个程序使用了两个包, fmt & math/rand
. 包名与导入路径的最后一个目录一致, 如 math/rand
这个包, 在使用的时候, 包名就直接写成 rand
即可.
import ( ... )
中的圆括号表示把所有的导入语句打包到一起, 也可以分开写:
import "fmt" import "math/rand"
不过更推荐用括号将包组合导入.
在Go的规范中, 包里面的名字如果第一个字符大写, 则可以被其他模块引用, 如果是小写的则不行. 如, 在外部使用 math.pi
将会报错, 而使用 math.Pi
才是正确的.
函数
函数的形式: func add(x int, y int) int {...}
参数必须先于类型, 然后是返回类型.
如果有两个或多个连续的参数是同一个类型, 可以只写最后一个. 如, 上面的函数也可以写成: func add(x, y int) int {...}
.
函数可以返回任意数量的返回值, 用逗号分隔. 如:
func swap(x, y string) (string, string) { return y, x }
变量
Go里面的变量定义形式: var name type
, 其类型是在名称后面的. 如: var i int
, 或 var c, python, java bool
.
初始化
var i, j int = 1, 2 // 有类型声明 var c, python, java = true, false, "no!" // 无类型声明
如果初始化的值是表达式, 可以不写类型.
在函数内部, 可以使用 ":=" 来代替 var
. 但在函数外部不行.
func add(){ var c = true // 等价于 // c := true }
如果不指定初始化的值, 默认数值类型初始化为0, bool类型初始化为false, string初始化为空.
基本类型
- bool: true, false
- string
- int: 有符号整型, 根据系统架构自动判断是int8, int16, int32还是int64. 如果系统是64位的, 则为int64
- int8: -128~127
- int16: -32768~32767
- int32: \(2^{-31}\) ~ \(2^{31}-1\)
- int64: \(2^{-63}\) ~ \(2^{63}-1\)
- uint: 无符号整型, 根据系统架构自动判断是uint8, uint16, uint32还是uint64
- uint8: 0~255
- uint16: 0~65536
- uint32: 0 ~ \(2^{32}-1\)
- uint64: 0 ~ \(2^{64}-1\)
- uintptr: 无符号整型, 用于存放一个指针, 可以足够保存指针的值的范围. 和uint范围相同, 根据系统架构自动判断.
- byte: uint8的别名
- rune: int32的别名
- float32: 单精度浮点数
- float64: 双精度浮点数
- complex64: 复数
- complex128: 复数
在 fmt.Printf()
中, 使用 %v
, 可以打印变量的值, 使用 %T
, 可以打印变量的类型.
类型转换
无论是什么时候, Go的类型转换都需要显式转换.
var i int = 42 var f float64 = float64(i)
类型推导
在定义一个变量, 但不指定其类型时, 变量的类型由右值推导得出.
var i int j := i // j 也是一个int i := 42 // i为int f := 3.142 // f为float64 g := 0.867 + 0.5i // g为complex128
常量
常量使用 const
关键字定义, 不能使用 := 语法定义.
const World = "世界"
程序控制
for
for i := 0; i < 10; i++ {...} for ; sum < 1000; {...} for sum < 1000 {...} // 就像其他语言的while for {...} // 死循环
Go里面, for语句不能使用圆括号"()", "i := 0" 也不能写成 "var i int = 0".
if
if x < 0 {...} // 同样不能使用圆括号 if x < 0 {...} else {...} if x < 0 {...} else if x > 0 {...} else {...}
if语句中, 可以使用短声明, 如:
if v := math.Pow(x, n); x < lim {...}
v
只在 if
语句中有效.
switch
switch os := runtime.GOOS; os { case "darwin": ... case "linux": ... default: ... }
swith
还可以没有条件:
t := time.Now() switch { case t.Hour() < 12: ... case t.Hour() < 17: ... default: ... }
这样的结构可以代替很长的 if-then-else.
defer
defer
会延迟函数的执行, 直到上层函数返回. 但如果上层函数有 return
, 则在 return
之前要执行.
package main import "fmt" func main() { defer fmt.Println("world") fmt.Println("hello") } // 输出 hello world
延迟的函数调用被存储在栈中, 当函数返回时, 会按照后进先出的顺序调用被延迟的函数调用.
package main import "fmt" func main() { fmt.Println("counting") for i := 0; i < 10; i++ { defer fmt.Println(i) } fmt.Println("done") }
复杂类型
指针
指针保存了变量的内存地址.
*T
是指向类型 T
的值的指针. 如 var p *int
.
&
会生成一个指向其作用对象的指针.
i := 42 p = &i
*
表示指针指向的底层的值.
fmt.Println(*p) // 通过指针p读取i *p = 21 // 通过指针p设置i
结构体
struct
就是一个字段的集合. 结构体字段使用点号来访问.
package main import "fmt" type Vertex struct { X int Y int } func main() { v := Vertex{1, 2} v.X = 4 fmt.Println(v.X) }
也可以通过指针来访问结构体:
v := Vertex{1, 2} p := &v p.X = 1e9
结构体的声明与初始化
type Vertex struct { X, Y int } var ( v1 = Vertex{1, 2} // 类型为Vertex v2 = Vertex{X: 1} // Y: 0 被省略 v3 = Vertex{} // X: 0 和 Y: 0 p = &Vertex{1, 2} // 类型为*Vertex )
slice
slice会指向一个序列的值, 并且包含长度信息.
[]T
是一个元素类型为 T
的slice.
p := []int{2, 3, 5, 7, 11, 13} for i := 0; i < len(p); i++ { fmt.Println(p[i]) }
slice就像是对数组元素的引用.
在函数传递中, slice传递的是地址, array是值传递.
array是固定长度, 不能通过删除array元素来导致长度变化; slice是可变长度, 内存地址可扩展.
截取
可以对slice进行截取, 返回一个新的slice. 如:
p := []int{2, 3, 5, 7, 11, 13} fmt.Println(p[1:4])
构造
slice由函数 make
创建. 这会分配一个长度为0的数组, 并返回一个slice指向这个数组.
a := make([]int, 5) // len(a)=5
len() 可以用来查看数组或slice的长度 cap() 可以用来查看数组或slice的容量
对于数组, 由于长度固定不可变, 因此 len(arr) 和 cap(arr) 永远相同. 对于slice, len(slice) 表示可见元素的个数, cap(slice) 表示所有元素个数.
b := make([]int, 0, 5) // len(b) = 0, cap(b) = 5
添加元素
var a []int a = append(a, 0) a = append(a, 2, 3, 4)
range
for
循环的 range
格式可以对slice或map进行迭代循环.
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} for i, v := range pow { fmt.Println(i, v) }
map
map
在使用之前必须用 make
来创建, 值为 nil
的 map
是空的, 并且不能赋值.
m = make(map[key_type]value_type) // 如 var m map[string]int m = make(map[string]int) // 或 var m map[string]int = make(map[string]int) // 或 m := make(map[string]int)
修改
// 插入或修改 m[key] = elem // 获得元素 elem = m[key] // 删除元素 delete(m, key) // 检测元素是否存在 elem, ok = m[key] // 如果存在, ok为true; 如果不存在, ok为false
函数值
函数值在其他语言里叫函数指针, 常用于回调和闭包. 函数值可以作为函数的参数或返回值. 如:
package main import "fmt" func bb(x, y int) int { return x + y } func main() { cc := func(x, y int) int { return x + y } fmt.Printf("%T\n", bb) fmt.Printf("%T\n", cc) fmt.Println(bb(1, 2)) fmt.Println(cc(1, 2)) }
bb
和 cc
只是写法不同, 实质上是一样的.
函数作为参数传递给另一个函数
func dd(i func(int, int) int) int { fmt.Printf("i type: %T\n", i) return i(1, 2) } func main() { ee := func(x, y int) int { return x + y } fmt.Printf("ee type: %T\n", ee) fmt.Println(dd(ee)) } // 输出 ee type: func(int, int) int i type: func(int, int) int 3
回调函数
callback, 就是通过一个函数指针调用的函数. 如把函数A作为参数传递给函数B, 那么A就叫做回调函数, B就叫中间函数, 调用B的函数就叫起始函数.
回调函数:
package even //回调函数1 //生成一个2k形式的偶数 func Double(x int) int { return x * 2 } //回调函数2 //生成一个4k形式的偶数 func Quadruple(x int) int { return x * 4 }
main:
package main import ( "fmt" "github.com/cyent/golang/example/even" ) //中间函数 //接受一个生成偶数的函数作为参数 //返回一个奇数 func getOddNumber(k int, getEvenNumber func(int) int) int { return 1 + getEvenNumber(k) } //起始函数,这里是程序的主函数 func main() { k := 1 //当需要生成一个2k+1形式的奇数时 i := getOddNumber(k, even.Double) fmt.Println(i) //当需要一个4k+1形式的奇数时 i = getOddNumber(k, even.Quadruple) fmt.Println(i) }
闭包
由于变量存在作用域, 所以函数内部可以直接读取全局变量, 而函数外部无法读取函数内部声明的变量. 要想在函数外部获得函数内部的局部变量, 就要使用闭包.
package main import "fmt" func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } func main() { pos, neg := adder(), adder() for i := 0; i < 10; i++ { fmt.Println( pos(i), neg(-2*i), ) } }
闭包被绑定到了变量 sum
上面.
方法和接口
方法
Go中没有类, 但可以在结构体类型上定义方法, 这样效果和类很相似.
方法与函数的区别在于, 方法拥有receiver参数. receiver参数写在 func
和方法名中间, 表示该方法的所有者是谁. 内置类型不能作为receiver.
Go中面向对象的用法:
package main import "fmt" type Person struct { name string } func (p *Person) printName() { fmt.Println(p.name) } func main() { me := Person{name: "cyent"} me.printName() } // 输出都是 cyent
方法的所有者, 在 func
和 方法名中间.
继承
在Go中使用结构体的嵌套来模拟d继承.
package main import "fmt" type SchoolMember struct { name string age int } func (this SchoolMember) tell() { fmt.Printf("name:\"%s\", age:\"%d\"\n", this.name, this.age) } type Teacher struct { SchoolMember salary int } func (this Teacher) tell() { this.SchoolMember.tell() fmt.Printf("Salary: \"%d\"\n", this.salary) } type Student struct { SchoolMember marks int } func (this Student) tell() { this.SchoolMember.tell() fmt.Printf("Marks: \"%d\"\n", this.marks) } func main() { t := Teacher{} s := Student{} t.name = "Mrs. Shrividya" t.age = 40 t.salary = 30000 s.name = "Swaroop" s.age = 22 s.marks = 75 t.tell() s.tell() }
接口
如果说类是对数据和方法的抽象和封装, 那么接口就是对类的抽象.
接口类型是由一组方法定义的集合.
不使用接口的时候:
package main import "fmt" type MyStruct struct { X, Y int } func (a *MyStruct) add() int { return a.X + a.Y } func main() { s := MyStruct{3, 4} fmt.Println(s.add()) }
使用接口后:
package main import "fmt" type Adder interface { add() int } type MyStruct struct { X, Y int } func (a *MyStruct) add() int { return a.X + a.Y } func main() { var f Adder s := MyStruct{3, 4} f = &s fmt.Println(f.add()) }
Generated by Emacs 25.x(Org mode 8.x)
Copyright © 2014 - Pinvon - Powered by EGO