GO大概总结

学习资料来自:
https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/preface.md


目录:

    • 先由一段简单的程序开始
    • Go基础
      • 声明单个变量
      • 声明多个变量
      • 初始化变量
      • 简化变量
      • 常量
      • 数值类型
      • 自定义类型
      • 字符串
      • 错误类型
      • 分组声明
      • iota枚举
      • 一些规则
      • array 数组
      • slice 动态数组
      • map 字典
      • make new操作
    • 流程和函数
      • if 判断
      • goto 跳转
      • for 循环
      • switch 分支
      • 函数
      • 变参
      • 传值与传指针
      • defer 延迟
      • 函数作为值 类型
      • Panic和Recover
      • main函数和init函数
      • import
    • struct类型
      • struct
      • struct的匿名字段
    • 面向对象
      • method 结构体函数
      • method继承
      • method重写
    • interface
      • interface类型
      • interface值
      • 空interface
      • interface函数参数
      • 获取interface变量存储的类型
      • 嵌入interface
      • 反射
    • 并发
      • goroutine 协程
      • channels 无缓存通道
      • Buffered Channels 有缓存通道
      • Range和Close
      • Select
      • 超时
      • runtime goroutine一些有用函数
    • 总结


先由一段简单的程序开始

package main    //声明为独立运行程序的包

import "fmt"    //引用模块,类似头文件

//函数入口,需要func,语句结尾也不需要;
func main() {   
    fmt.Printf("Hello, world or 你好,世界\n")
}

Go基础

声明单个变量

//Go
var variableName type

//C++
type variableName;

声明多个变量

//Go
var varName1,varName2,varName3 type

//C++
type varName1, varName2, varName3;

初始化变量

//Go
var variableName type = value
var varName1,varName2,varName3 type = val1, val2, val3

//C++
type variableName = value;
type varName1 = val1, varName2 = val2, varName3 = val3;

简化变量

:=这个符号直接取代了var和type,但是只能用在函数内部,一般用var方式来定义全局变量。
_(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。

var c = 20      //自行判断类型

a := 30
_, b := 34, 35

常量

//Go 类型可写可不写
const a int = 5
const Pi = 3.1415926
const str = "string"

//C++
#define a 5                     //直接替换
const double Pi = 3.1415926;    //类型控制
const char[10] str = "string";

数值类型

这些类型的变量之间不允许互相赋值或操作!

bool            //布尔型
int             //整型 32bit
uint            //无符号整型 32bit
int8            //1bit
uint8 == byte   //字节 1bit
int16           //短整型 16bit
uint16
int32 == rune   //和int一样32bit,但不能互用
uint32
int64           //长整型 64bit
uint64          
//没有float类型,默认是float64
float32         //浮点型
float64         //类似C++的double
complex64       //短复数,(32位实数+32位虚数)
complex128      //默认长复数,(64位实数+64位虚数)

默认值:

int     0
int8    0
int32   0
int64   0
uint    0x0
rune    0 //rune的实际类型是 int32
byte    0x0 // byte的实际类型是 uint8
float32 0 //长度为 4 byte
float64 0 //长度为 8 byte
bool    false
string  ""

自定义类型

type 类似C的 typedef

type ages int

type money float32

type months map[string]int

m := months {
    "January":31,
    "February":28,
    ...
    "December":31,
}

字符串

Go中的字符串都是采用UTF-8字符集编码。

var zh string               //声明字符串
var hi string = "Hello"     //初始化字符串
no, yes, maybe := "no", "yes", "maybe"

//多行字符串
m := `hello
      world`

//可以使用 + 来连接字符串
h := "hello"
w := "world"
hw := h + w     //"hello world"

修改字符串,string字符串保存在静态数据区,数组在栈区

//yes[0] = "X"              //X,不能修改字符串常量

temp := []byte(yes)         //将yes转为[]byte类型
temp[0] = 'x'               //√,数组可以修改
xes := string(temp)         //转回string

//字符串虽不能更改,但可进行切片操作
c := "cello"
h := "h" + c[1:]    //"hello"

错误类型

Go内置有一个error类型,专门用来处理错误信息,Go的package里面还专门有一个包errors来处理错误:

err := errors.New("emit macho dwarf: elf header corrupted")
if err != nil {
    fmt.Print(err)
}

分组声明

同时声明多个常量、变量,或者导入多个包时,可采用分组的方式进行声明。

import(
    "fmt"
    "os"
)

const(
    i = 100
    pi = 3.1415
    prefix = "Go_"
)

var(
    i int
    pi float32
    prefix string
)

iota枚举

这个关键字用来声明enum的时候采用,它默认开始值是0,const中每增加一行加1:

package main

import (
    "fmt"
)

const (
    x = iota // x == 0
    y = iota // y == 1
    z = iota // z == 2
    w        // 常量声明省略值时,默认和之前一个值的字面相同。这里隐式地说w = iota,因此w == 3。其实上面y和z可同样不用"= iota"
)

const v = iota // 每遇到一个const关键字,iota就会重置,此时v == 0

const (
    h, i, j = iota, iota, iota //h=0,i=0,j=0 iota在同一行值相同
)

const (
    a       = iota //a=0
    b       = "B"
    c       = iota             //c=2
    d, e, f = iota, iota, iota //d=3,e=3,f=3
    g       = iota             //g = 4
)

func main() {
    fmt.Println(a, b, c, d, e, f, g, h, i, j, x, y, z, w, v)
}

一些规则

Go之所以会那么简洁,是因为它有一些默认的行为:

  • 大写字母开头的变量是可导出的,也就是其它包可以读取的,是公有变量;小写字母开头的就是不可导出的,是私有变量。
  • 大写字母开头的函数也是一样,相当于class中的带public关键词的公有函数;小写字母开头的就是有private关键词的私有函数。

array (数组)

[3]int与[4]int是不同的类型,数组也就不能改变长度。
数组之间是值传递,传入函数为副本;
需要使用指针使用slice。

var arr [10]int  // 声明了一个int类型的数组
arr[0] = 42      // 数组下标是从0开始的
arr[1] = 13      // 赋值操作

a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组

b := [10]int{1, 2, 3} // 声明了一个长度为10的int数组,其中前三个元素初始化为1、2、3,其它默认为0

c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度

二维数组

// 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有4个int类型的元素
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}

// 上面的声明可以简化,直接忽略内部的类型
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}

slice (动态数组)

注意slice和数组在声明时的区别:声明数组时,[num]or[…],而声明slice时,[]。

slice并不是真正意义上的动态数组,而是一个引用类型。

// 和声明array一样,只是少了长度
var fslice []int

// 声明一个含有10个元素元素类型为byte的数组
var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}

// 声明两个含有byte的slice
var a, b []byte

// a指向数组的第3个元素开始,并到第五个元素结束,
a = ar[2:5]
//现在a含有的元素: ar[2]、ar[3]和ar[4]

// b是数组ar的另一个slice
b = ar[3:5]
// b的元素是:ar[3]和ar[4]

slice有一些简便的操作:

  • slice的默认开始位置是0,ar[:n]等价于ar[0:n]
  • slice的第二个序列默认是数组的长度,ar[n:]等价于ar[n:len(ar)]
  • 如果从一个数组里面直接获取slice,可以这样ar[:],因为默认第一个序列是0,第二个是数组的长度,即等价于ar[0:len(ar)]

slice有几个有用的内置函数:

  • len 获取slice的长度
  • cap 获取slice的最大容量
  • append 向slice里面追加一个或者多个元素,然后返回一个和slice一样类型的slice
  • copy 函数copy从源slice的src中复制元素到目标dst,并且返回复制的元素的个数

注:append函数会改变slice所引用的数组的内容,从而影响到引用同一数组的其它slice。 但当slice中没有剩余空间(即(cap-len) == 0)时,此时将动态分配新的数组空间。返回的slice数组指针将指向这个空间,而原数组的内容将保持不变;其它引用此数组的slice则不受影响。

从Go1.2开始slice支持了三个参数(容量)的slice

var array [10]int
slice := array[2:4:7]   //容量最大7,无法访问array后3个元素

map (字典)

map也就是Python中字典(哈希表)的概念,它的格式为
map [keyType] valueType

map和slice类似,只是slice的index只能是int类型,map可以是很多类型。

// 声明一个key是字符串,值为int的字典,这种方式的声明需要在使用之前使用make初始化
var numbers1 map[string]int
numbers1 = make(map[string]int)

// 另一种map的声明方式
numbers := make(map[string]int)
numbers["one"] = 1  //赋值
numbers["ten"] = 10 //赋值
numbers["three"] = 3

fmt.Println("第三个数字是: ", numbers["three"]) // 读取数据
// 打印出来如:第三个数字是: 3

使用map过程中需要注意的几点:

  • map是无序的,每次打印出来的map都会不一样,它不能通过index获取,而必须通过key获取
  • map的长度是不固定的,也就是和slice一样,也是一种引用类型
  • 内置的len函数同样适用于map,返回map拥有的key的数量
  • map的值可以很方便的修改,通过numbers[“one”]=11可以很容易的把key为one的字典值改为11
  • map和其他基本型别不同,它不是thread-safe,在多个go-routine存取时,必须使用mutex lock机制

通过delete删除map的元素:

// 初始化一个字典
rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 }
// map有两个返回值, 第一个返回值为值
//第二个返回值,如果不存在key,那么ok为false,如果存在ok为true
csharpRating, ok := rating["C#"]
if ok {
    fmt.Println("C# is in the map and its rating is ", csharpRating)
} else {
    fmt.Println("We have no rating associated with C# in the map")
}

delete(rating, "C")  // 删除key为C的元素

map也是一种引用类型,如果两个map同时指向一个底层,那么一个改变,另一个也相应的改变:

m := make(map[string]string)
m["Hello"] = "Bonjour"
m1 := m
m1["Hello"] = "Salut"  // 现在m["hello"]的值已经是Salut了

make、 new操作

make用于内建类型(map、slice 和channel)的内存分配。
new用于各种类型的内存分配。

new(T)分配了零值填充的T类型的内存空间,并且返回其地址,即一个*T类型的值。(类似C++的new操作符)

make只能创建slice、map和channel,并且返回一个有初始值(非零)的T类型,而不是*T。


流程和函数

if 判断

Go里面if条件判断语句中不需要括号

if x > 10 {
    fmt.Println("x is greater than 10")
} else {
    fmt.Println("x is less than 10")
}

Go的if还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了,如下所示

// 计算获取值x,然后根据x返回的大小,判断是否大于10。
if x := computedValue(); x > 10 {
    fmt.Println("x is greater than 10")
} else {
    fmt.Println("x is less than 10")
}

//这个地方如果这样调用就编译出错了,因为x是条件里面的变量
fmt.Println(x)

goto 跳转

标签名是大小写敏感的。

func myFunc() {
    i := 0
Here:   //这行的第一个词,以冒号结束作为标签
    println(i)
    i++
    goto Here   //跳转到Here去
}

for 循环

Go是没有while循环的,所以它又可以当作while来使用。
语法:

for expression1; expression2; expression3 {
    //...
}

例子:

package main

import "fmt"

func main(){
    sum := 0;
    for index:=0; index < 10 ; index++ {
        sum += index
    }
    fmt.Println("sum is equal to ", sum)
}
// 输出:sum is equal to 45

有些时候如果我们忽略expression1和expression3:

sum := 1
for ; sum < 1000;  {
    sum += sum
}

也可以忽略; 就变成while了:

sum := 1
for sum < 1000 {
    sum += sum
}

在循环里面有两个关键操作break和continue:

  • break操作是跳出当前循环
  • continue是跳过本次循环

for配合range可以用于读取slice和map的数据:

for k,v:=range map {
    fmt.Println("map's key:",k)
    fmt.Println("map's val:",v)
}

由于 Go 支持 “多值返回”, 而对于“声明而未被调用”的变量, 编译器会报错, 在这种情况下, 可以使用_来丢弃不需要的返回值 例如:

for _, v := range map{
    fmt.Println("map's val:", v)
}

switch 分支

Go里面switch默认相当于每个case最后带有break,所以不需要我们写,也可以使用fallthrough强制执行后面的case代码。

语法:

switch sExpr {
case expr1:
    some instructions
case expr2:
    some other instructions
case expr3:
    some other instructions
default:
    other code
}

例子:

i := 10
switch i {
case 1:
    fmt.Println("i is equal to 1")
case 2, 3, 4:
    fmt.Println("i is equal to 2, 3 or 4")
case 10:
    fmt.Println("i is equal to 10")
default:
    fmt.Println("All I know is that i is an integer")
}

函数

它通过关键字func来声明,它的格式如下:

func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
    //这里是处理逻辑代码
    //返回多个值
    return value1, value2
}

上面的代码我们看出:

  • 关键字func用来声明一个函数funcName
  • 函数可以有一个或者多个参数,每个参数后面带有类型,通过,分隔
  • 函数可以返回多个值
  • 上面返回值声明了两个变量output1和output2,如果你不想声明也可以,直接就两个类型
  • 如果只有一个返回值且不声明返回值变量,那么你可以省略 包括返回值 的括号
  • 如果没有返回值,那么就直接省略最后的返回信息
  • 如果有返回值, 那么必须在函数的内层添加return语句

变参

Go函数支持变参。接受变参的函数是有着不定数量的参数的。为了做到这点,首先需要定义函数使其接受变参:

func myfunc(arg ...int) {}

arg …int告诉Go这个函数接受不定数量的参数。注意,这些参数的类型全部是int。在函数体中,变量arg是一个int的slice:

for _, n := range arg {
    fmt.Printf("And the number is: %d\n", n)
}

传值与传指针

当我们传一个参数值到被调用函数里面时,实际上是传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中相应实参不会发生任何变化,因为数值变化只作用在copy上。

可以使用 & 来取地址

Go语言中channel,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)

defer 延迟

函数结束时调用defer的语句,类似类的析构函数,可以在函数中添加多个defer语句,如果有多个defer,则按照后进先出(栈)依次执行 。

func ReadWrite() bool {
    file.Open("file")
    defer file.Close()

    if failureX {
        return false
    }
    if failureY {
        return false
    }
    return true
}

函数作为值、 类型

在Go中函数也是一种变量,我们可以通过type来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种类型。

package main

import "fmt"

type testInt func(int) bool // 声明了一个函数类型

func isOdd(integer int) bool {
    if integer%2 == 0 {
        return false
    }
    return true
}

func isEven(integer int) bool {
    if integer%2 == 0 {
        return true
    }
    return false
}

// 声明的函数类型在这个地方当做了一个参数

func filter(slice []int, f testInt) []int {
    var result []int
    for _, value := range slice {
        if f(value) {
            result = append(result, value)
        }
    }
    return result
}

func main(){
    slice := []int {1, 2, 3, 4, 5, 7}
    fmt.Println("slice = ", slice)
    odd := filter(slice, isOdd)    // 函数当做值来传递了
    fmt.Println("Odd elements of slice are: ", odd)
    even := filter(slice, isEven)  // 函数当做值来传递了
    fmt.Println("Even elements of slice are: ", even)
}

Panic和Recover

Go没有像Java那样的异常机制,它不能抛出异常,而是使用了panic和recover机制。一定要记住,你应当把它作为最后的手段来使用,也就是说,你的代码中应当没有,或者很少有panic的东西。这是个强大的工具,请明智地使用它。

Panic

是一个内建函数,可以中断原有的控制流程,进入一个令人恐慌的流程中。当函数F调用panic,函数F的执行被中断,但是F中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,F的行为就像调用了panic。这一过程继续向上,直到发生panic的goroutine中所有调用的函数返回,此时程序退出。恐慌可以直接调用panic产生。也可以由运行时错误产生,例如访问越界的数组。

Recover

是一个内建的函数,可以让进入令人恐慌的流程中的goroutine恢复过来。recover仅在延迟函数中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何效果。如果当前的goroutine陷入恐慌,调用recover可以捕获到panic的输入值,并且恢复正常的执行。

下面这个函数演示了如何在过程中使用panic

var user = os.Getenv("USER")

func init() {
    if user == "" {
        panic("no value for $USER")
    }
}

面这个函数检查作为其参数的函数在执行时是否会产生panic:

unc throwsPanic(f func()) (b bool) {
    defer func() {
        if x := recover(); x != nil {
            b = true
        }
    }()
    f() //执行函数f,如果f中出现了panic,那么就可以恢复回来
    return
}

main函数和init函数

  1. Go里面有两个保留的函数:init函数(能够应用于所有的package)和main函数(只能应用于package main)。
  2. 这两个函数在定义时不能有任何的参数和返回值。
  3. 强烈建议用户在一个package中每个文件只写一个init函数。
  4. Go程序会自动调用init()和main(),所以你不需要在任何地方调用这两个函数。每个package中的init函数都是可选的,但package main就必须包含一个main函数。

GO大概总结_第1张图片

import

导入包文件
1.相对路径

import “./model” //当前文件同一目录的model目录,但是不建议这种方式来import

2.绝对路径

import “shorturl/model” //加载gopath/src/shorturl/model模块

import(
    "fmt"
)

fmt.Println("hello world")

import常用的几种方式:
1.点操作 (省略前缀的包名)

 import(
     . "fmt"
 )

 Println("hello world")

2.别名操作

import(
    f "fmt"
)

f.Println("hello world")

3._操作
_操作其实是引入该包,而不直接使用包里面的函数,而是调用了该包里面的init函数。

import (
        "database/sql"
        _ "github.com/ziutek/mymysql/godrv"
    )

struct类型

struct

和C或者其他语言一样,我们可以声明新的类型

type person struct {
    name string
    age int
}

var P person  // P现在就是person类型的变量了

//1.一般声明
P.name = "Astaxie"  // 赋值"Astaxie"给P的name属性.
P.age = 25  // 赋值"25"给变量P的age属性

//2.按照顺序提供初始化值
P := person{"Tom", 25}

//3.通过field:value的方式初始化,这样可以任意顺序
P := person{age:24, name:"Tom"}

//4.当然也可以通过new函数分配一个指针,此处P的类型为*person
P := new(person)

fmt.Printf("The person's name is %s", P.name)  // 访问P的name属性.

struct的匿名字段

struct里面可以定义所有类型,包括内置

package main

import "fmt"

type Human struct {
    name string
    age int
    weight int
}

type Student struct {
    Human  // 匿名字段,那么默认Student就包含了Human的所有字段
    speciality string
}

func main() {
    // 我们初始化一个学生
    mark := Student{Human{"Mark", 25, 120}, "Computer Science"}

    // 我们访问相应的字段
    fmt.Println("His name is ", mark.name)
    fmt.Println("His age is ", mark.age)
    fmt.Println("His weight is ", mark.weight)
    fmt.Println("His speciality is ", mark.speciality)
    // 修改对应的备注信息
    mark.speciality = "AI"
    fmt.Println("Mark changed his speciality")
    fmt.Println("His speciality is ", mark.speciality)
    // 修改他的年龄信息
    fmt.Println("Mark become old")
    mark.age = 46
    fmt.Println("His age is", mark.age)
    // 修改他的体重信息
    fmt.Println("Mark is not an athlet anymore")
    mark.weight += 60
    fmt.Println("His weight is", mark.weight)
}

面向对象

method 结构体函数

给函数指定作用域,变成外界不可访问的该作用域的函数

类似C++类函数,只能当前类使用

package main

import (
    "fmt"
    "math"
)

type Rectangle struct {
    width, height float64
}

type Circle struct {
    radius float64
}

func (r Rectangle) area() float64 {
    return r.width*r.height
}

func (c Circle) area() float64 {
    return c.radius * c.radius * math.Pi
}


func main() {
    r1 := Rectangle{12, 2}
    r2 := Rectangle{9, 4}
    c1 := Circle{10}
    c2 := Circle{25}

    fmt.Println("Area of r1 is: ", r1.area())
    fmt.Println("Area of r2 is: ", r2.area())
    fmt.Println("Area of c1 is: ", c1.area())
    fmt.Println("Area of c2 is: ", c2.area())
}

func main() {
    r1 := Rectangle{12, 2}
    r2 := Rectangle{9, 4}
    fmt.Println("Area of r1 is: ", area(r1))
    fmt.Println("Area of r2 is: ", area(r2))
}

method的两个技巧:

  • 如果一个method的receiver是*T,你可以在一个T类型的实例变量V上面调用这个method,而不需要&V去调用这个method
  • 如果一个method的receiver是T,你可以在一个*T类型的变量P上面调用这个method,而不需要 *P去调用这个method

method继承

就是C++类有个成员变量(类),这个类可以直接调用成员变量的函数

package main

import "fmt"

type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //匿名字段
    school string
}

type Employee struct {
    Human //匿名字段
    company string
}

//在human上面定义了一个method
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

func main() {
    mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
    sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}

    mark.SayHi()
    sam.SayHi()
}

method重写

就是C++类函数和成员变量(类)的函数重名,优先调用自己的函数。

package main

import "fmt"

type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //匿名字段
    school string
}

type Employee struct {
    Human //匿名字段
    company string
}

//Human定义method
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

//Employee的method重写Human的method
func (e *Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone) //Yes you can split into 2 lines here.
}

func main() {
    mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
    sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}

    mark.SayHi()
    sam.SayHi()
}

interface

interface类型

interface是一组method的集合,它把所有的具有共性的方法定义在一起。

package main

import (
    "fmt"
)

//电话基本功能打电话
type Phone interface {
    call()
}

type NokiaPhone struct {
}

//诺基亚能打电话
func (nokiaPhone NokiaPhone) call() {
    fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {
}

//苹果能打电话
func (iPhone IPhone) call() {
    fmt.Println("I am iPhone, I can call you!")
}

func main() {
    var phone Phone

    phone = new(NokiaPhone)
    phone.call()

    phone = new(IPhone)
    phone.call()
}

interface值

interface变量能保存具有interface方法的变量,如上面的例子:
phone = NokiaPhone
phone = IPhone

空interface

空interface可以存储任意类型,如果一个函数返回interface{},那么也就可以返回任意类型的值,它有点类似于C语言的void*类型。

// 定义a为空接口
var a interface{}
var i int = 5
s := "Hello world"
// a可以存储任意类型的数值
a = i
a = s

interface函数参数

interface的变量可以持有任意实现该interface类型的对象。

举个例子:fmt.Println是我们常用的一个函数,但是你是否注意到它可以接受任意类型的数据。打开fmt的源码文件,你会看到这样一个定义:

type Stringer interface {
     String() string
}

//fmt.Println实现类似如下
func (f fmt) Println(s string, str Stringer ) string {
    ...
}

也就是说,任何实现了String方法的类型都能作为参数被fmt.Println调用,让我们来试一试

package main
import (
    "fmt"
    "strconv"
)

type Human struct {
    name string
    age int
    phone string
}

// 通过这个方法 Human 实现了 fmt.Stringer
func (h Human) String() string {
    return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years -  ✆ " +h.phone+"❱"
}

func main() {
    Bob := Human{"Bob", 39, "000-7777-XXX"}
    fmt.Println("This Human is : ", Bob)
}

获取interface变量存储的类型

目前常用的有两种方法:

1.Comma-ok断言
value, ok = element.(T),这里value就是变量的值,ok是一个bool类型,有则true,element是interface变量,T是断言的类型。

2.switch测试
switch使用不需要获取value和ok

switch value := element.(type) {
                case int:
                case string:
                case Person:
                default:

嵌入interface

interface可以嵌入interface

反射

反射就是能检查程序在运行时的状态,反射的字段必须是可修改的。
一般用到的包是reflect包,使用reflect一般分成三步:

1). 首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)。

t := reflect.TypeOf(i)    //得到类型的元数据,通过t我们能获取类型定义里面的所有元素
v := reflect.ValueOf(i)   //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值

2).将reflect对象转化成相应的值

tag := t.Elem().Field(0).Tag  //获取定义在struct里面的标签
name := v.Elem().Field(0).String()  //获取存储在第一个字段里面的值

3).获取反射值能返回相应的类型和数值

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())

并发

goroutine 协程

它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。

goroutine通过go关键字实现:

package main

import (
    "fmt"
    "runtime"
)

func say(s string) {
    for i := 0; i < 5; i++ {
        //协程是在同一线程内,所以需要让给别人运行一段时间再切回来
        runtime.Gosched()
        fmt.Println(s)
    }
}

func main() {
    go say("world") //开一个新的Goroutines执行
    say("hello") //当前Goroutines执行
}

channels 无缓存通道

Go提供了一个很好的通信机制channel,接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不需要显式的lock,如果读取的channel没有数据会阻塞直到数据到来。channel可以与Unix shell 中的双向管道做类比。

channels 通过chan关键字实现,并且必须使用make创建:

package main

import "fmt"

func sum(a []int, c chan int) {
    total := 0
    for _, v := range a {
        total += v
    }
    c <- total  // send total to c
}

func main() {
    a := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(a[:len(a)/2], c)
    go sum(a[len(a)/2:], c)
    x, y := <-c, <-c  // receive from c

    fmt.Println(x, y, x + y)
}

Buffered Channels 有缓存通道

Buffered Channels就是channel可以存储多个元素。
ch := make(chan type, value)
当 value = 0 时,channel 是无缓冲阻塞读写的,当value > 0 时,channel 有缓冲、是非阻塞的,直到写满 value 个元素才阻塞写入。

package main

import "fmt"

func main() {
    c := make(chan int, 2)//修改2为1就报错,修改2为3可以正常运行
    c <- 1
    c <- 2
    fmt.Println(<-c)
    fmt.Println(<-c)
}
        //修改为1报如下的错误:
        //fatal error: all goroutines are asleep - deadlock!

Range和Close

记住应该在生产者的地方关闭channel,而不是消费的地方去关闭它,这样容易引起panic

package main

import (
    "fmt"
)

func fibonacci(n int, c chan int) {
    x, y := 1, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x + y
    }
    close(c)
}

func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}

另外记住一点的就是channel不像文件之类的,不需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的

Select

通过select可以监听多个channel上的数据流动。
select默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。

package main

import "fmt"

func fibonacci(c, quit chan int) {
    x, y := 1, 1
    for {
        select {
        case c <- x:
            x, y = y, x + y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}

在select里面还有default语法,select其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。

select {
case i := <-c:
    // use i
default:
    // 当c阻塞的时候执行这里
}

超时

利用select来设置超时来避免整个程序进入阻塞。

func main() {
    c := make(chan int)
    o := make(chan bool)
    go func() {
        for {
            select {
                case v := <- c:
                    println(v)
                case <- time.After(5 * time.Second):
                    println("timeout")
                    o <- true
                    break
            }
        }
    }()
    <- o    //超时退出程序
}

runtime goroutine一些有用函数

  • Goexit
    退出当前执行的goroutine,但是defer函数还会继续调用

  • Gosched
    让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。

  • NumCPU
    返回 CPU 核数量

  • NumGoroutine
    返回正在执行和排队的任务总数

  • GOMAXPROCS
    用来设置可以并行计算的CPU核数的最大值,并返回之前的值。


总结

二十五个关键字,记熟基础基本差不多了。

break    default      func    interface    select
case     defer        go      map          struct
chan     else         goto    package      switch
const    fallthrough  if      range        type
continue for          import  return       var
  • var和const参考 Go基础里面的变量和常量声明
  • package和import已经有过短暂的接触
  • func 用于定义函数和方法
  • return 用于从函数返回
  • defer 用于类似析构函数
  • go 用于并发
  • select 用于选择不同类型的通讯
  • interface 用于定义接口,参考 interface
  • struct 用于定义抽象数据类型,参考 structural
  • break、case、continue、for、fallthrough、else、if、switch、goto、default这些参考 流程和函数
  • chan用于channel通讯
  • type用于声明自定义类型
  • map用于声明map类型数据
  • range用于读取slice、map、channel数据

你可能感兴趣的:(Go,go)