Pinvon's Blog

所见, 所闻, 所思, 所想

1 Go基础

每个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
)

数组

类型 [n]T 是一个有 n 个类型为 T 的值的数组.

如: var a 1int

使用: a2 = 1

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 来创建, 值为 nilmap 是空的, 并且不能赋值.

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

bbcc 只是写法不同, 实质上是一样的.

函数作为参数传递给另一个函数

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

关于接口的更详细内容, 有空再补

反射

并发

Footnotes:

1

DEFINITION NOT FOUND.

2

DEFINITION NOT FOUND.

Comments

使用 Disqus 评论
comments powered by Disqus