golang快速入门

cc 发布于2016年04月29日 ∣ golang ∣ 约5009字 · 需10分钟 阅读()

此文适合有一定语言基础的读者

go简介

语言哲学

C语言是纯过程式的,这和它产生的历史背景有关。Java语言则是激进的面向对象主义推崇者,典型表现是它不能容忍体系里存在孤立的函数。而Go语言没有去否认任何一方,而是用批判吸收的眼光,将所有编程思想做了一次梳理,融合众家之长,但时刻警惕特性复杂化,极力维持语言特性的简洁,力求小而精

Go语言反对函数和操作符重载(overload),而C++、Java和C#都允许出现同名函数或操作符,只要它们的参数列表不同。

其次,Go语言支持类、类成员方法、类的组合,但反对继承,反对虚函数(virtual function)和虚函数重载。确切地说,Go也提供了继承,只不过是采用了组合的文法来提供

1type Foo struct {
2    Base  // struct类型
3    ...
4}
5func (foo *Foo) Bar() {
6    ...
7}

Go语言也放弃了构造函数(constructor)和析构函数(destructor)

在放弃了大量的OOP特性后,Go语言送上了一份非常棒的礼物:接口(interface)。Go语言中的接口与其他语言最大的一点区别是它的非侵入性。

Go语言实现类的时候无需从接口派生,只要实现的接口定义的方法就表示实现了该接口。

以上摘自许式伟的《Go语言编程》

优点

  1. 够简单
  2. 语言级别支持并发编程
  3. 错误处理新规范
  4. 自动垃圾回收
  5. 匿名函数与闭包
  6. 类型与接口
  7. 反射
  8. 语言交互(主要与c)
  9. 丰富的内置类型

缺点

  1. 错误处理比较蛋疼,需要写很多err判断

一、hello golang!

1package main
2import "fmt"
3func main() {
4    fmt.Println("hello golang!")
5}

使用任意编辑器编写保存,命令行中:go run hello.go即可运行 可以在https://play.golang.org 上直接运行,不需要安装环境

关键字

关键字 作用
package 代码所属包
import 导入依赖包,不能导入不使用的包
main 主函数入口,无参数无返回值,命令行参数保存在os.Args中
func 函数声明
go 开启协程(并发核心)
map 字典类型, map[string]bool
delete 专用来删除字典中的元素
chan 通道,协程通信核心
select 同时读取多个chan的信息
close 用来关闭通道
make 用来创建chan或map
type 类型定义,定义任意需要的类型
struct C中的结构体,但可以定义方法以实现类功能
interface 接口类型,用来定义接口
new 新建对象, 并获得它的指针
range 类似python的range,配合for遍历列表、数组或map的元素
defer 自动关闭资源,退出代码体(一般是函数)时执行
error error接口,只要实现Error()string方法就是一个error接口
panic 抛出异常,如果不用recover捕获则会终止程序
recover 捕获panic异常,防止程序终止,与recover结合
数值定义
const 常量声明
var 变量声明
数值类型
bool 布尔
string 字符
int,int8,int16,int32,int64 int长度与平台相关
uint,uint8,uint16,uint32,uint64 uint长度与平台相关
uintptr 同指针,32位平台为4字节,64位八字节
byte 等价于uint8
rune 等价于uint32,单个unicode字符
float32,float64
complex64,complex128 复数类型, value = 3.2+12i

操作符

赋值:=,:=
数值运算:+,-,*,/,%
比较运算:>,<,==,>=,<=,!=
位运行:»,«,^,&,|与^x(取反)

特殊操作符 作用
:= 无需指定类型即可赋值,i, j := true, “hello”
_ **可以赋任意值的空对象,*_ = “string”***

二、语法快速过滤

与其他语言比较

注释:Go程序的代码注释与C++保持一致,支付块注释与行注释

1/*
2块注释
3*/
4// 行注释

无java/c中的结束分号;

package

类似java的包,一般与目录同名,只有运行目录时包名必须为main,一个目录不能出现多个包名

import

1import (
2    "fmt"
3    mr "math/rand"
4    _ "encoding/json"
5)
  1. mr 是”math/rand”的别名,可用来避免重名/长名
  2. _ 表示初始化包,但并不使用

也可以

1import "fmt"
2import mr "math/rand"
3import _ "encoding/json"

明显上一种能减少不少代码量

注意:只有大写字母开头的方法或对象可以被导出使用,类似公有私有概念

const

 1const Pi = 3.14      // 默认类型为float64
 2const x,y int = 1,2  // 指定类型
 3// 也可以集中定义
 4const(
 5    Big = 1 << 100
 6    Small = Big >> 99
 7)
 8// 如果为同样的值,可省略不写
 9const (   
10    a = 1   
11    b           // b = 1
12    c           // c = 1
13    d           // d = 1
14)
15// 可以使用iota加1,iota从0开始
16const (   
17    a = 1 + iota
18    b           // b = 2
19    c           // c = 3
20    d           // d = 4
21)
22//  遇到const关键字置0
23const (
24    e = iota    // e = 0
25)

var

 1var i int
 2var x,y,z int            // 声明多个变量
 3var x,y,z int = 1,2,3    // 声明多个变量并初始化,int可以省略
 4var i,j = true, "hello"  // 多变量类型同时赋值
 5var(  // 集中声明,与导包方式一样
 6    v1 int
 7    v2 string
 8)
 9v3 := "no var init"      // 只能在函数体内使用此方法
10x,y = y,x                // 多重赋值 
11_,i,_ = x,y,z            // 使用'_'屏蔽不需要的值
  1. 注意go变量名放在类型之前,这个与c,java相反,对同类型变量可以只留下最后一个类型声名
  2. 注意不能使用var i bool,j string = true, "hello"进行赋值

map

1var myMap map[string]string // 字典声名,声明的map是空值,需要用make()创建
2myMap = make(map[string]string)

func

 1func add(x int,y int) int{   // 这里也可以写成add(x,y int)
 2    return x + y
 3}
 4
 5// 函数内声明的变量必须被使用,否则会报错
 6func nouse(){
 7    var no string  
 8    // 变量未使用报错
 9    // 可以使用'_ = no'来避免报错
10}
11
12// 可以是空函数体
13func empty(){
14}
15
16// 可变参数
17func vary(args... int) {
18    fmt.Println(args)
19}
20// vary()       -> [] 可以为空
21// vary(1)      -> [1]
22// vary(1,2,3)  -> [1 2 3]
23
24// 混合使用固定参数与可变参数,可变参数放在最后,不能与固定参数共用参数类型
25func fix_vary(fix int, args...int) {  // fix可以不使用
26    fmt.Println(args)
27}
28
29// 多值返回, 这个函数可以直接用多重赋值实现x,y = y,x
30func swap(x, y int) (int,int){
31    return y, x
32}
33
34// 返回值指定变量名
35func split(sum int) (x,y int) {
36    x = sum / 5
37    y = sum % 5
38    return  // 当指定变量名时,返回可以略去不写
39}
40
41// 匿名函数
42func(x,y int)int{
43    return x+y
44}
45// 匿名函数直接执行, 直接在函数定义后面加上参数即可
46func(x,y int)int{
47    return x+y
48}(2,3)  // 传入参数2,3运行
49// 匿名函数赋值
50f := func(x,y int)int{
51    return x+y
52}

流程控制中的条件表达式,不需要小括号

if else, goto

1// 普通用法同其他语言
2// 特殊用法, 可以初始化变量
3if i:=j;i>0{
4	// ... do sth about i
5}
6

switch,case,select

switch不需要用break来退出一个case,默认退出

 1switch i {
 2case 0:
 3    // ...
 4case 1:
 5    // ...
 6// ...
 7default:
 8    // ...
 9}
10// switch后面的表达式不是必须的
11switch{
12case char == 'a':
13    // ...
14case char == 'b':
15    // ...
16}
17// 单个case可出现多个可选结果
18switch 1 {
19case 1,2,3:
20    // ...
21default:   
22    // ...
23}
24// 可以初始化变量,可以不做任何处理
25switch os:=runtime.GOOS;os{
26case "darwin":
27case "linux":
28}

for … range()

 1// 常规使用同c
 2// 死循环
 3for{
 4    // do something
 5}
 6// 多重赋值, 实现反序
 7a := []int{1, 5, 3, 4, 2, 6}
 8for  i, j := 0, len(a) - 1; i < j; i, j = i + 1, j - 1 {
 9    a[i], a[j] = a[j], a[i]
10}
11fmt.Println(a)  // [6 2 4 3 5 1]
12
13// 遍历列表
14for index,value := range(alist){ 
15    // ...
16}
17// 遍历字典
18for key,value := range(amap){  
19    // ...
20}

defer

1f,err := os.Open(filename)
2if err != nil{
3    log.Println("")
4    return
5}
6defer f.Close() // 程序处理完后续代码退出或异常退出时执行
7// ... 其他处理

三、struct,另类的类

给类型加上方法便是对象,用type定义自己的类型,然后加上方法

1type Myint int
2func (m Myint)Less(b Myint) bool{
3  return m < b
4}
5var a Myint = 1
6if a.Less(2){    // 对天显示的类型2会自动处理成相应类型
7    // do something
8}

因为一般对象有不少属性,所以一般用struct来定义对象

 1type Person struct{
 2    Name string
 3    Age int
 4}
 5func (p Person)GetName()string{
 6    return p.Name
 7}
 8// 需要修改对象成员时用指针
 9func (p *Person)SetName(name string){
10    p.Name = name
11}

对象继承,c中的struct匿名组合

 1type Base struct{
 2    Name string
 3}
 4func (b *Base) Name()string{...}
 5type Foo struct{
 6    Base   //匿名实现继承,Base中的方法,Foo可以直接使用
 7    ...
 8}
 9var foo Foo
10foo.Name() // Name()为Base中的方法

可见性,没有private,protected,public,只有大写开头的成员才能导出包外被使用

四、接口

用interface定义,接口不需要被继承,任何对象只要实现了接口中的方法就实现了此接口

 1type IFile interface {
 2    Read(buf []byte) (n int, err error)
 3    Write(buf []byte) (n int, err error)
 4    Seek(off int64, whence int) (pos int64, err error)
 5    Close() error
 6}
 7type IReader interface {
 8    Read(buf []byte) (n int, err error)
 9}
10type IWriter interface {
11    Write(buf []byte) (n int, err error)
12}
13type ICloser interface {
14    Close() error
15}
16// 以上实现了四个接口, 下面定义一个File对象
17type File struct {
18    // ...
19}
20func (f *File) Read(buf []byte) (n int, err error)
21func (f *File) Write(buf []byte) (n int, err error)
22func (f *File) Seek(off int64, whence int) (pos int64, err error) 
23func (f *File) Close() error
24
25var file1 IFile = new(File)
26var file2 IReader = new(File)
27var file3 IWriter = new(File)
28var file4 ICloser = new(File)
29// 因为File实现了所有接口的方法,所有File可以赋值给四个接口变量

接口查询,查看对象是否实现接口 (使用类型判断方法)

1var file1 Writer = ...
2if file5, ok := file1.(two.IStream); ok {
3    // ...
4}

接口组合,可以像struct一样组合成一个新接口

1type ReadWriter interface {
2IReader
3IWriter
4}
5// 完全等同于下面写法
6type ReadWriter interface {
7    Read(p []byte) (n int, err error)
8    Write(p []byte) (n int, err error)
9}

空接口interface{},可以接受任何类型

1{
2    var v1 interface{} = 1        //  int interface{}
3    var v2 interface{} = "abc"    //  string interface{}
4    var v3 interface{} = &v2      //  *interface{} interface{}
5    var v4 interface{} = struct{ X int }{1}
6    var v5 interface{} = &struct{ X int }{1}
7}

五、类型与转换

类型查询,使用.(target type)来判断类型

 1var v1 interface{} = ...
 2switch v := v1.(type) {
 3case int:
 4    //  v为 int
 5case string: 
 6    //  v为 string
 7//...
 8}
 9
10tes := make(map[string]interface{})
11tes["a"] = "abc"
12tes["b"] = 123
13
14value, ok := tes["a"].(string)  // "abc", true
15value2,ok2 := tes["a"].(int)    // "", false

类型强转,只有兼容的对象可以进行转换

1var var1 int = 7
2var2 := float64(var1)
3var3 := int64(var1)
4
5var4 := new(int32)  // var4为指针变量
6var5 := (*int32)(var4)

六、并发

关键字go简单暴力实现并发,不同于线程/进程,更轻量级的协程

 1package main
 2import "fmt"
 3func Add(x, y int) {z := x + y
 4    fmt.Println(z) }
 5func main() {
 6    for i := 0; i < 10; i++ {
 7        go Add(i, i)  // 并发计算
 8    }
 9}
10// 打印顺序如下,若要console能显示,需要加time.Sleep(),否则没来得及打印就退出了
11// 4
12// 6
13// 8
14// 10
15// 2
16// 14
17// 12
18// 16
19// 0
20// 18

并发通信

 1// c思路go实现
 2package main
 3import "fmt" 
 4import "sync" 
 5import "runtime"
 6
 7var counter int = 0
 8func Count(lock *sync.Mutex) { 
 9    lock.Lock()   
10    counter++   
11    fmt.Println(counter)   
12    lock.Unlock()
13}
14func main() {
15    lock := &sync.Mutex{}    
16    for i := 0; i < 10; i++ {       
17        go Count(lock)   
18    }   
19    for { 
20        lock.Lock()      
21        c := counter      
22        lock.Unlock()      
23        runtime.Gosched()       
24        if c >= 10 {         
25            break      
26        }    
27    }
28}  
29
30// go思路实现
31package main
32import "fmt"
33func Count(ch chan int) {
34    ch <- 1  // 放入1
35    fmt.Println("Counting") 
36}
37func main() {
38    chs := make([]chan int 10)
39    for i := 0; i < 10; i++ {
40        chs[i] = make(chan int)
41        go Count(chs[i])
42    }
43    for _, ch := range(chs) {
44        <-ch  //这里是读出数据,如果数据不读取会阻塞其他协程写入数据
45    }
46}

channel,chan与map类似,没有make时是不能使用的,所以声明与make最好一起,

 1var ch1 chan int
 2var ch2 chan<- float64 // 单向channel, 只用于写float64数据
 3var ch3 <-chan int     // 单向channel, 只用于读取int数据
 4ch := make(chan int)  // 不带缓冲的channel,如果ch中没数据则读取会被阻塞,如果ch中有数据则写入会被阻塞
 5go func(){   
 6    ch <- 1   //  写入数据
 7}
 8i := <-ch     // 取数据并使用
 9<- ch         // 取数据但不使用, 这里将死锁
10x, ok = <- ch // ok表示有没有取到数据,与类型判断与map取值类似
11// 带缓冲的channel
12chbuf := make(chan int,100) // 没有数据时阻塞读取,数据塞满100个时阻塞写入

单向通道一般用于函数参数定义

1func single_r(ch <-chan int){
2    // do sth, 只能读取数据
3}
4func single_w(ch chan<- int){
5    // do sth, 只能写入数据
6}
7var ch chan int // 定义一个双向通道,在对应的函数内只能读或写
8single_r(ch)
9singe_w(ch)

select,与switch类似,但是是专用多通道读取

 1LOOP:
 2    for {  // 循环读取
 3        select {
 4            case <-chan1:
 5            //  如果chan1成功读到数据,则进行case处理语句
 6            case chan2 <- 1:
 7            //  如果chan2成功读到数据,则进行case处理语句
 8                break LOOP  // @LOOP, 退出for循环
 9            default:
10            //  如果上面都没成功,则进入default处理流程
11            //  一般用 <-time.After(duration)替代default做读写默认超时处理
12        }
13    }
14
15// 读取某个chan中所有数据
16for i := range(ch){
17    // do 
18}

因为select也有break语句,所以需要使用类似goto的标记来标识退出地方

通道经常使用的特殊数据:

1chan <- struct{}{}

语义为将一个空数据传递给channel.因为struct{}{}占用的内存非常小,而且我们对数据内容也不关心,通常用来做信号量来处理

七、C语言交互

将c代码用/*,*/包含起来,紧挨着写import “C"即可, 不需要特别编译处理即可直接执行

 1package main
 2/*
 3#include <stdio.h>
 4void hello() {   
 5    printf("Hello, Cgo! -- From C world.\n");
 6}
 7*/
 8import "C"
 9
10func Hello()  {   
11    C.hello()
12}
13func main() {   
14    Hello()
15}
16
17// 运行结果:Hello, Cgo! -- From C world.

需要传入参数到C函数时,需要用C的类型转换后再传进去

 1C.char()
 2C.schar()(signed char 3C.uchar()(unsigned char 4C.short()
 5C.ushort()(unsigned short 6C.int()
 7C.uint()(unsigned int 8C.long()
 9C.ulong()(unsigned long10C.longlong()(long long11C.ulonglong()(unsigned long long12C.float()
13C.double()

八、异常处理

defer, panic(), recover()异常处理,不再有烦琐的try…exception

当在一个函数执行过程中调用panic()函数时,正常的函数执行流程将立终止,但函数中之前使用defer关键字延迟执行的语句将正常展开执行,然后返回到调用函数,逐层向上执行panic,直到所属的goroutine中所有正在执行的函数被终止

recover()函数用来终止panic流程,放在defer关键词后面的函数中执行

1defer func(){
2    if err := recover(); err !=nil{
3        // 处理错误
4    }
5}
6
7// ...
8
9panic("something error")  // 这个panic将被上面的defer捕获,阻止程序退出

九、学习资源

https://gobyexample.com, 这里有大量使用常规使用案例
https://play.golang.org, 线上执行环境,可演示简单程序