美文网首页
Golang编程基础(The Go Programming La

Golang编程基础(The Go Programming La

作者: 早安我的猫咪 | 来源:发表于2018-09-06 12:32 被阅读14次

    一、 基本类型:

    • 数值
      • 整型:intint8int16int32(runeUnicode); int64uintuint8(byte); uint16uint32uint64uintptr
        int, uint, uintptr 在 32 位系统上是 32 位,在 64位 系统上是 64位
      • 浮点型:float32float64
      • 复数型:complex64complex128
    • 字符串:使用双引号 "a"
    • 布尔: truefalse
    • 常量:三种基本类型
    const(
      kb = 1024
      e = 2.71828182845904523536028747135266249775724709369995957496696763
      F = false
    )
    

    if语句中,若检验条件为i >= 0,则i 的类型不宜为 uint 型(uint 数据始终 >= 0)。尽管内置函数 len() 返回值是非负整数,但它际返回 int 型,

    medals := []string{"gold", "silver", "bronze"}
    for i := len(medals)-1; i >= 0; i--{
      fmt.Println(medals[i]) // "bronze", "silver", "gold"
    }
    

    基本类型与操作符构成表达式

    • 取余 % 只用于整型;余数与被除数符号相同,5%3=2-5%3=-2
    • 5.0/4=1.255/4.0=1.255/4=1
    • && 若左边的表达式结果为 false,不检验右边的表达式;& 始终检验两边的表达式
    • 与或^ ; 一元前缀^

    二、 聚集类型 (aggregate types):

    1.数组

    var a [3]int
    r := [...]int{99: 1}    // 索引为99的元素,r[99], 等于 1,其他默认 0
    
    p := new([10]int)    // 将生成的数组的指针赋给 p, 为 *[10]int 类型
    p[0]=1    // 给 p 指向的数组的索引为0的元素赋值 1 
    

    数组的长度也是数组类型的一部分,因此 [3]int[4]int 是不同类型的数组,不能进行比较或赋值。

    2.结构

    (1)结构的字段(field)

    type person struct{      // 定义 person 类型
        gender string
        age int
    }
    func main(){
        student := person{}
        student.age = 16
        student.gender = "male"
        // or
        student := person{
           gender : "male", 
            age : 16,    //逗号不能省
        }
        // or
        student := person{"male", 16}
    
        teacher := &person{    //取指针
          gender : "female", 
           age : 30,    
        }
        teacher.age = 36    //指针 teacher 仍然可以进行点操作
    
    }
    

    指针也可以进行点操作。

    匿名结构,字段匿名

    student := struct{      // 匿名结构
        gender : string
        age : int
    }{
        gender : "male",
        age : "17",
    }
    
    type person struct{      
        string
        int
    }
    // 按照顺序初始化
    student := person{"male", 10}
    

    结构嵌套,

    type person struct{      
        gender string
        age int
        parents struct{      //  嵌套一个匿名结构
            dad, mom : string
        }
    }
    
    type address struct{
        state, city string
    }
    
    type person2 struct{      
        gender string
        age int
        address    // 嵌套你一个结构address
        }
    }
    
    
    func main(){
        student := person{gender : "female", age : 10}
        student.parents.dad = "Tom"
        student.parents.mom = "Lily"     
        
        student2 := person2{gender:"female", age:10, address : address{county:"LA" state:"California"}  } 
        student2.address.state = "Massachusetts"    // or
        student2.state = "Massachusetts"  
    }
    

    (2)结构的方法(method)
    函数与方法

    package main
    
    import (
        "fmt"
        "math"
    )
    
    type Point struct{ X, Y float64 }
    
    func Distance(p, q Point) float64 {     //函数
        return math.Hypot(q.X-p.X, q.Y-p.Y)
    }
    
    
    func (p Point) Distance(q Point) float64 {      // 方法,在函数名前增加一个形参(receiver) 类似于Java的this 和python的 self,
        return math.Hypot(q.X-p.X, q.Y-p.Y)             // 接收者的名称通常取它的类型名称的第一个字母
    }
    
    func main() {
        p := Point{1, 2}
        q := Point{4, 6}
        fmt.Println(Distance(p, q))     // 打印 5, 调用函数
        fmt.Println(p.Distance(q))      // 调用方法
    }
    

    当需要使用方法对值(value of type T,相对于方法来讲就是实参,argument) 的字段进行修改时,使用接收者为指针的方法或者叫指针方法

    func (p *Point) ScaleBy(factor float64) {      // 接收者参数p的类型是指针类型
        p.X *= factor      // p在这里是指针,等价于 (*p).X
        p.Y *= factor
    }           
    

    同一个 struct 的方法和字段占据相同的命名空间(name space),因此两者的名称不能重复;
    指针方法看作高权限方法。

    对方法的调用, 值(实参) 和 接收者(形参)类型要相同

    Point{1, 2}.Distance(q)     // Point  Point
    pptr.ScaleBy(2)       // *Point  *Point
    
    pptr.Distance(q)     // 隐含 (*pptr)
    p.ScaleBy(2)     // 隐含 (&p)
    
                 Point{1, 2}.ScaleBy(2)    //错误!!!
    (&Point{1, 2}).ScaleBy(2)
    

    不仅仅是 struct

    package main
    
    import (
        "fmt"
    )
    
    type INT int
    
    func main() {
        var a INT
        a = 1
        a.Print()    // 打印 2
    }
    
    func (a *INT) Print() {
        *a = 2
        fmt.Println(*a)
    }
    
    

    方法是与命名类型(named type)相关联的函数。

    三、引用类型

    1.指针

    var p *int
    i := 20
    p = &i
    *p = 10    // i 的值为 10
    

    2.切片(slice)

    var s []int    // 声明切片 s
    a := [5]int{1, 2, 3, 4, 5}
    s = a[:2]    // [1, 2], len(s)等于2,cap(s)等于5
    s = a[0:1]    // [1],len(s)等于1,cap(s)等于5
    s = a[3:]    // [4, 5],len(s)等于2,cap(s)等于2
    
    s := make(int[], 2, 4)    //make([]type, len, cap) ,切片长度为2,底层数组的长度为4
    s = append(s, 1)    // s 的地址不变
    s = append(s, 2, 3)    // 生成新的数组,地址改变,容量翻倍,也就是cap(s)等于4*2=8
    s1 := []int{1, 2, 3}
    s2 := []int{4, 5}
    copy(s1, s2)    //把 s2 复制到 s1,s1 为 [4, 5, 3]
    
    s1 = []int{1, 2, 3}
    copy(s2, s1)    // s2 为 [1, 2]
    

    切片的本质是对底层数组的引用;切片的容量(cap)是切片的始索引到底层数组的末索引的长度。

    3.映射(map)

    var m map[int]string    // key int 型;value string 型
    m = make(map[int]string)
    m2 := make(map[int]string)
    
    m[0] = "OK"
    delete(m, 0)    // 删除 m 中键为0的键值对
    

    嵌套,

    m := make(map[int]map[int]string)    // value  map型
    m[1] = make(map[int]string)
    m[1][2] = "YES" 
    

    4.函数

    func main(int, []string) int
    means, function main takes an int and a slice of strings and returns an int
    函数作为类型,

    var f func(func(int,int) int, int) func(int, int) int  
    

    不定长变参,闭包

    package main
    
    import (
        "fmt"
    )
    
    func main(){
      var_args(1)    
      var_args(1, 2, 3)    
    
      f := closure(10)
      fmt.Println(f(1))   
      fmt.Println(f(2))    
    }
    func var_args(args ...int){
      fmt.Println(args)
    }
    func closure(x int) func(int) int{    // 返回匿名函数
      return func(y int) int{
        return x + y
      }
    }
    
    输出:
    [1]
    [1 2 3]
    11
    12
    

    4.channel

    channel 是 goroutine 沟通的桥梁,通过 make 创建,close 关闭

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        c := make(chan bool)
    
        go func() {
            fmt.Println("I from goroutine !")
            c <- true
        }()
    
        <-c
    }
    

    channel 作为函数形参

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        c := make(chan bool)
    
        go Hello(c)
    
        <-c
    }
    
    func Hello(c chan bool) {
        fmt.Println("Hello, I from goroutine!")
        c <- true    
    }
    

    多个 goroutine,多个channel

    package main
    
    import (
        "fmt"
        "runtime"
    )
    
    func main() {
        runtime.GOMAXPROCS(runtime.NumCPU())    // 开启多核
        c := make(chan bool)
    
        for i := 0; i < 5; i++ {    // 启动多个 goroutin
            go Decomposition(c, i, 100000007)
        }
    
        for i := 0; i < 5; i++ {    // 多个 channel 阻塞
            <-c
        }
    }
    
    func Decomposition(c chan bool, index int, n int) {    // 质数分解
        for i := 2; i <= n; i++ {
            for n != i {
                if n%i == 0 {
                    fmt.Printf("%d*", i)
                    n = n / i
                } else {
                    break
                }
            }
        }
        fmt.Printf("%d: %d\n", index, n)
    
        c <- true
    }
    
    输出:   
    0: 100000007
    2: 100000007
    1: 100000007
    4: 100000007
    3: 100000007
    从输出结果的顺序可以看出 goroutine 并非先启动先执行
    

    使用同步包来代替 channel

    package main
    
    import (
        "fmt"
        "runtime"
        "sync"
    )
    
    func main() {
        runtime.GOMAXPROCS(runtime.NumCPU())
        wg := sync.WaitGroup{}
        wg.Add(5)    //  添加 5 个 任务(goroutine)
    
        for i := 0; i < 5; i++ {
            go Decomposition(&wg, i, 100000007)
        }
    
        wg.Wait()    // 等到任务数减到 0 
    }
    
    func Decomposition(wg *sync.WaitGroup, m int, n int) {
        for i := 2; i <= n; i++ {
            for n != i {
                if n%i == 0 {
                    fmt.Printf("%d*", i)
                    n = n / i
                } else {
                    break
                }
            }
        }
        fmt.Printf("%d: %d\n", m, n)
    
        wg.Done()    // 任务数减 1
    }
    
    输出:
    0: 100000007
    2: 100000007
    4: 100000007
    1: 100000007
    3: 100000007
    

    selec{}语句,
    如果有多个case 读取数据,select会随机选择一个case执行,其他不执行;
    如果没有case读取数据,就执行default;
    如果没有case读取数据,且没有default,select将阻塞,直到某个case可以执行。

    package main
    import (
        "fmt"
    )
    
    func main() {
        c1, c2, block := make(chan int), make(chan string), make(chan bool)
    
        go func() {
            for {
                select {    // 按随机顺序处理多个 case
                case message, open := <-c1:
                    if !open {    // 如果通道 c1 关闭,则跳出无限循环
                        block <- true
                        break
                    }
                    fmt.Println("A message from main by c1:", message)
    
                case message, open := <-c2:
                    if !open {    // 如果通道 c2 关闭,则跳出无限循环
                        block <- true
                        break
                    }
                    fmt.Println("A message from main by c2:", message)
                }
            }
        }()
    
        c1 <- 10
        c2 <- "hello"
        c1 <- 20
        c2 <- "world"
    
        close(c1)    // 关闭通道 c1
    
        <-block
    }
    
    输出:
    A message from main by c1: 10
    A message from main by c2: hello
    A message from main by c1: 20
    A message from main by c2: world
    
    package main
    
    import (
        "fmt"
        "time"
    )
    
    func main() {
        select {
        case <-time.After(2000000 * time.Microsecond):
            fmt.Println("2 seconds")
    
        case <-time.After(1999999 * time.Microsecond):
            fmt.Println("1.999999 seconds")
        }
    }
    
    输出:
    1.999999 seconds
    

    四、接口类型(interface):

    (1)接口代表某些方法的集合

    package main
    
    import (
        "fmt"
    )
    
    type game interface {
        Strike_of_Kings() int
        Battle_Grounds() int
    }
    type contact interface {
        Wechat()
        QQ()
    }
    type smartphone interface{    // 接口嵌套,
        game
        contact
    }
    
    type iphone struct {    
        version string
        price   float32
        user    string
    }
    
    func (iph iphone) Wechat() {    
        fmt.Println("I installed wechat on my iphone", iph.version)
    }
    func (iph *iphone) QQ() {    
        fmt.Println("I installed wechat on my iphone", iph.version)
    }
    // iphone 不满足 contact 接口
    
    func (iph iphone) Battle_Grounds() int {
        fmt.Println("There are 4 teammates at most in the Battle Grounds.")
        return 4
    }
    func (iph iphone) Strike_of_Kings() int {    
        fmt.Println("There are 5 teammates at most in the Strike of Kings.")
        return 5
    }
    // iphone 满足 game 接口
    
    func (iph *iphone) New_Version(version string) {
        iph.version = version
    }
    
    func all_round_game(game) {      // 接口作为形参
        fmt.Println("Both Strike of_Kings and Battle Grounds have installed.")
    }
    
    func main() {
        my_phone := iphone{"X", 8316, "Xiaohe"}
        my_phone.Wechat()
        fmt.Println(my_phone.Battle_Grounds())
        all_round_game(my_phone)    // my_phone 符合 game 接口,可作为该函数的实参
    
    输出:
    I installed wechat on my iphone X
    There are 4 teammates at most in the Battle Grounds.
    4
    Both Strike of_Kings and Battle Grounds have installed.
    }
    

    (2)任何类型都满足空接口;空接口interface{}作为形参可以接受任何类型的实参

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        m := make(map[int]interface{})
        m[1] = "a"
        m[2] = 2
        m[3] = false
    
        print_map(m)
    }
    
    func print_map(m map[int]interface{}) {
        for k, v := range m {
            fmt.Println(k, ":", v)
        }
    }
    
    输出:
    1 : a
    2 : 2
    3 : false
    

    []string[]interface{} 是不同的类型;
    接口是一种抽象类型,可理解为是将所有具体类型按照方法集进行再分类;
    指针方法集包含非指针方法集。

    (3)接口值(interface value)包含 类型 (接口的动态类型)和 类型值 (接口的动态值) 两个部分,仅当两者均为nil 时,接口才为nil

    The zero value for an interface has both its type and value components set to nil
    (4)反射 (reflection)
    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    type iphone struct {
        version string
        price   int
        user    string
    }
    
    func (iph iphone) Wechat() {
        fmt.Println("I installed wechat on my iphone", iph.version)
    }
    
    func main() {
        my_phone := iphone{"X", 8316, "Xiaohe"}
        Info(my_phone)
    }
    
    func Info(itf interface{}) {
        t := reflect.TypeOf(itf)
        fmt.Println("What type:", t.Name())
    
        v := reflect.ValueOf(itf)
    
        for i := 0; i < v.NumField(); i++ {
            fmt.Println(t.Field(i).Type)
            fmt.Println(v.Field(i))
        }
    }
    
    输出:
    What type: iphone
    string
    X
    int
    8316
    string
    Xiaohe
    

    五、控制流 (for if switch defer goto):

    (1)for

    for (inti statement);condition;(post statement) {
    }
    
    for i := 0; i < 10; i++ {
    } 
    for ; i < 10; {    // 去掉分号
    } 
    for i < 10{    // while 语句
    }
    for{    // 无限循环
    }
    

    (2)if

     if (init statement);condition {
    }  
    
    if (init statement);condition {
    }else {
    }
    

    (3)switch

    switch (init statement);some value {
       case 0:
       case f():
       ...
       default:
    }
    
    switch {
       case 布尔表达式1:
       case 布尔表达式2:
       ...
       default:
    }
    

    一旦符合条件自动终止,若希望继续检验下面的case,使用 fallthrough 语句。
    (4)defer

    • defer 后必须跟函数引用
    • defer 语句被检验后,延迟函数获得变量的拷贝
    func a() {
        i := 0
        defer fmt.Println(i)
        i++
        return
    }    // defer 语句打印0
    
    • defer 语句被检验后,延迟匿名函数获得变量的地址
    func b() (i int) {
        defer func() { i++ }()
        return 1    // 将1 赋给 i
    }    // 返回 2。利用 defer 语句修改外围函数的命名返回值
    
    func c() {
        for i := 0; i < 3; i++ {
          defer func() {
                fmt.Print(i)
          }()
        }
        return
    }  // 打印 333
    
    • defer 语句被检验后,延迟函数的引用被推入堆栈,当外围函数返回后,按照后进先出的顺序被调用(即使外围函数发生错误,如 panic,延迟函数仍然会被调用)
    func d() {
        for i := 0; i < 4; i++ {
            defer fmt.Print(i)
        }
    }    // 打印 3210
    

    更多细节如,panicrecover(只能用在延迟函数中) 参考 defer blog

    (5)goto

    LABEL:
        for {
            for i := 0; i < 10; i++ {
                if i > 3 {
                    break LABEL    // 跳出与LABEL同级的循环,即跳出无限循环
                } 
            }
        }
    
    LABEL:
        for i := 0; i < 10; i++ {
            for {
                continue LABEL
            }
        }
    
    LABEL:
        for {
            for i := 0; i < 10; i++ {
                if i > 3 {
                    goto LABEL    // 将再次进入无限循环
                } 
            }
        }
    

    通常标签放到 goto 的后面。

    创建工程目录:

    Go工程中共有三个部分:

    • src:存放go源码文件
    • pkg:存放编译后的包文件
    • bin:存放编译后的可执行文件

    注意:src目录需要自己创建,pkg和bin编译时会自动创建。

    步骤:

    1. 新建工程目录,my_project,并在该目录下创建 src目录;
    2. 把my_project 添加到 GOPATH,GOPATH=/home/user/...;my_project(可以同时添加多个路径目录,Linux下用冒号:隔开,window下分号;隔开);
    3. 在 src 下创建my_pkg 和 my_cmd;
    4. 包文件放入到 my_pkg 中,比如 test.go
    package my_pkg
    import "fmt"
     
    func Test(){
      fmt.Println("Hello,world!")
      fmt.Println("You used a function defined in my_package!")
    }
    

    在命令行src目录,执行 go install my_pkg 将创建 pkg 目录并声成 my_pkg.a 文件。

    1. my_cmd 中放入 package main,比如 hello_world.go
    package main
    import(
      "my_pkg"
    )
    
    func main(){
      my_pkg.Test()
    }
    

    在命令行src目录,执行 go install my_cmd 将创建 bin 目录并生成可执行文件成 hello_world.exe 文件。

    目录结构:
    src/
    \quad my_pkg/
    \qquad test.go
    \quad my_cmd/
    \qquad hello_world.go

    其他:

    • fmt.printf verbs:
      %x %b:16进制,2进制显示;%t:显示 bool 结果;%T:显示值的类型;%v:显示值;%p:显示地址;\n:换行
    • Sublime text 3
      上一个编辑处: alt+-
      下一个编辑处: alt+shift+-
    • GoSublime:
      GoSublime快捷键列表:ctrl+.+. (连击 .)
      查看声明:ctrl+.+h
      代码跳转:ctrl+shift+左键
      package control:ctrl+shift+p

    参考资料:

    [1] 无闻;Go编程基础系列视频.
    [2] Alan A.A. Donovan; Brain W. Kernighan; The Go Programming Language; 2015.

    相关文章

      网友评论

          本文标题:Golang编程基础(The Go Programming La

          本文链接:https://www.haomeiwen.com/subject/dnxxgftx.html