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