美文网首页
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