golang快速入门

Posted 飞鸿影的博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了golang快速入门相关的知识,希望对你有一定的参考价值。


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

go简介

语言哲学

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

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

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

type Foo struct {
    Base  // struct类型
    ...
}
func (foo *Foo) Bar() {
    ...
}

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

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

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

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

优点

  1. 够简单

  2. 语言级别支持并发编程

  3. 错误处理新规范

  4. 自动垃圾回收

  5. 匿名函数与闭包

  6. 类型与接口

  7. 反射

  8. 语言交互(主要与c)

  9. 丰富的内置类型

缺点

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

一、hello golang!

package main
import "fmt"func main() {    fmt.Println("hello golang!") }

使用任意编辑器编写保存,命令行中: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++保持一致,支付块注释与行注释

/* 块注释 */// 行注释

无java/c中的结束分号;

package

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

import

import (
   "fmt"    mr "math/rand"    _ "encoding/json"
)
  1. mr 是”math/rand”的别名,可用来避免重名/长名

  2. _ 表示初始化包,但并不使用

也可以

import "fmt"
import mr "math/rand"
import _ "encoding/json"

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

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

const

const Pi = 3.14      // 默认类型为float64
const x,y int = 1,2  // 指定类型// 也可以集中定义
const(    Big = 1 << 100    Small = Big >> 99
)// 如果为同样的值,可省略不写

const (      a = 1      b           // b = 1    c           // c = 1    d           // d = 1
)// 可以使用iota加1,iota从0开始

const (      a = 1 + iota    b           // b = 2    c           // c = 3    d           // d = 4
)//  遇到const关键字置0

const (    e = iota    // e = 0
)

var

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

  2. 注意不能使用var i bool,j string = true, "hello"进行赋值

map

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

func

func add(x int,y int) int{   // 这里也可以写成add(x,y int)
    return x + y
}

// 函数内声明的变量必须被使用,否则会报错
func nouse(){    var no string      // 变量未使用报错    // 可以使用'_ = no'来避免报错
}

// 可以是空函数体
func empty(){ }

// 可变参数
func vary(args... int) {    fmt.Println(args) }

// vary()       -> [] 可以为空// vary(1)      -> [1]// vary(1,2,3)  -> [1 2 3]// 混合使用固定参数与可变参数,可变参数放在最后,不能与固定参数共用参数类型
func fix_vary(fix int, args...int) {  // fix可以不使用    fmt.Println(args) }

// 多值返回, 这个函数可以直接用多重赋值实现x,y = y,x
func swap(x, y int) (int,int){
   return y, x }
   
// 返回值指定变量名
func split(sum int) (x,y int) {    x = sum / 5    y = sum % 5    return  // 当指定变量名时,返回可以略去不写

}

// 匿名函数
func(x,y int)int{
       return x+y }

// 匿名函数直接执行, 直接在函数定义后面加上参数即可
func(x,y int)int{
           return x+y }(2,3)// 传入参数2,3运行

// 匿名函数赋值
f := func(x,y int)int{
       return x+y }

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

if else, goto

// 普通用法同其他语言// 特殊用法, 可以初始化变量

if i:=j;i>0{
   // ... do sth about i

}

switch,case,select

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

switch i {
case 0:    // ...
case 1:    // ...// ...
default:    // ...

}

// switch后面的表达式不是必须的
switch{
case char == 'a':    // ...
case char == 'b':    // ...
}

// 单个case可出现多个可选结果
switch 1 {
case 1,2,3:    // ...
default: // ...

}

// 可以初始化变量,可以不做任何处理
switch os:=runtime.GOOS;os{
case "darwin":
case "linux": }

for ... range()

// 常规使用同c

// 死循环
for{    // do something

}

// 多重赋值, 实现反序
a := []int{1, 5, 3, 4, 2, 6}
for  i, j := 0, len(a) - 1; i < j; i, j = i + 1, j - 1 {    a[i], a[j] = a[j], a[i] } fmt.Println(a)  // [6 2 4 3 5 1]


// 遍历列表
for index,value := range(alist){    // ...
}

// 遍历字典
for key,value := range(amap){      // ...
}

defer

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

三、struct,另类的类

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

type Myint int

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

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

type Person struct{
    Name string
    Age int
}
func (p Person)GetName()string{
   return p.Name }

// 需要修改对象成员时用指针
func (p *Person)SetName(name string){    p.Name = name }

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

type Base struct{
    Name string
}
func (b *Base) Name()string{...} type Foo struct{    Base   //匿名实现继承,Base中的方法,Foo可以直接使用    ... } var foo Foo foo.Name() // Name()为Base中的方法

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

四、接口

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

type IFile interface {
    Read(buf []byte) (n int, err error)
    Write(buf []byte) (n int, err error)
    Seek(off int64, whence int) (pos int64, err error)
    Close() error
}
type IReader interface {
    Read(buf []byte) (n int, err error)
}
type IWriter interface {
    Write(buf []byte) (n int, err error)
}
type ICloser interface {
    Close() error
}

// 以上实现了四个接口, 下面定义一个File对象
type File struct {    // ...} func (f *File) Read(buf []byte) (n int, err error) func (f *File) Write(buf []byte) (n int, err error) func (f *File) Seek(off int64, whence int) (pos int64, err error) func (f *File) Close() error var file1 IFile = new(File) var file2 IReader = new(File) var file3 IWriter = new(File) var file4 ICloser = new(File)// 因为File实现了所有接口的方法,所有File可以赋值给四个接口变量

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

var file1 Writer = ...if file5, ok := file1.(two.IStream); ok {    // ...

}

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

type ReadWriter interface {
IReader
IWriter
}

// 完全等同于下面写法
type ReadWriter interface {    Read(p []byte) (n int, err error)    Write(p []byte) (n int, err error) }

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

{
   var v1 interface{} = 1        //  int interface{}    var v2 interface{} = "abc"    //  string interface{}    var v3 interface{} = &v2      //  *interface{} interface{}    var v4 interface{} = struct{ X int }{1}
   var v5 interface{} = &struct{ X int }{1} }

五、类型与转换

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

var v1 interface{} = ...switch v := v1.(type) {
case int:    //  v为 int
case string:    //  v为 string//...
} tes := make(map[string]interface{}) tes["a"] = "abc"
tes["b"] = 123

value, ok := tes["a"].(string)  // "abc", true
value2,ok2 := tes["a"].(int)    // "", false

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

var var1 int = 7
var2 := float64(var1) var3 := int64(var1) var4 := new(int32)  // var4为指针变量
var5 := (*int32)(var4)

六、并发

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

package main
import "fmt"

func Add(x, y int) {
   z := x + y    fmt.Println(z)
}

func main() {
   for i := 0; i < 10; i++ {
           go Add(i, i)  // 并发计算    } }

// 打印顺序如下,若要console能显示,需要加time.Sleep(),否则没来得及打印就退出了

// 4
// 6
// 8
// 10
// 2
// 14
// 12
// 16
// 0
// 18

并发通信

// c思路go实现

package main
import "fmt"
import "sync"
import "runtime"

var counter
int = 0

func Count(lock *sync.Mutex) {    lock.Lock()      counter++      fmt.Println(counter)      lock.Unlock() }

func main() {    lock := &sync.Mutex{}        for i := 0; i < 10; i++ {              go Count(lock)      }      for {        lock.Lock()              c := counter              lock.Unlock()              runtime.Gosched()              if c >= 10 {                    break              }        } }  

// go思路实现
package main
import "fmt"

func Count(ch chan int) {    ch <- 1  // 放入1    fmt.Println("Counting") }

func main() {    chs := make([]chan int 10)
   for i := 0; i < 10; i++ {        chs[i] = make(chan int)
       go Count(chs[i])    }

   for _, ch :
= range(chs) {        <-ch  //这里是读出数据,如果数据不读取会阻塞其他协程写入数据    } }

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

var ch1 chan int

var ch2 chan<- float64 // 单向channel, 只用于写float64数据

var ch3 <-chan int     // 单向channel, 只用于读取int数据

ch := make(chan int)  // 不带缓冲的channel,如果ch中没数据则读取会被阻塞,如果ch中有数据则写入会被阻塞

go func(){      ch <- 1   //  写入数据

} i := <-ch     // 取数据并使用

<- ch         // 取数据但不使用, 这里将死锁

x, ok = <- ch // ok表示有没有取到数据,与类型判断与map取值类似

// 带缓冲的channel
chbuf := make(chan int,100) // 没有数据时阻塞读取,数据塞满100个时阻塞写入

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

func single_r(ch <-chan int){    // do sth, 只能读取数据

}

func single_w(ch chan<- int){    // do sth, 只能写入数据

} var ch chan int // 定义一个双向通道,在对应的函数内只能读或写
single_r(ch) singe_w(ch)

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

LOOP:    for {  // 循环读取
        select {
           case <-chan1:            //  如果chan1成功读到数据,则进行case处理语句            case chan2 <- 1:            //  如果chan2成功读到数据,则进行case处理语句                break LOOP  // @LOOP, 退出for循环            default:            //  如果上面都没成功,则进入default处理流程            //  一般用 <-time.After(duration)替代default做读写默认超时处理        }    }

// 读取某个chan中所有数据

for i := range(ch){    // do

}

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

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

chan <- struct{}{}

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

七、C语言交互

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

package main
/* #include <stdio.h> void hello() {      printf("Hello, Cgo! -- From C world.\n"); } */

import "C"

func Hello()  {      C.hello() }func main() {      Hello() }

// 运行结果:Hello, Cgo! -- From C world.

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

C.char()
C.schar()(signed char)
C.uchar()(unsigned char)
C.short()
C.ushort()(unsigned short)
C.int()
C.uint()(unsigned int)
C.long()
C.ulong()(unsigned long)
C.longlong()(long long)
C.ulonglong()(unsigned long long)
C.float()
C.double()

八、异常处理

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

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

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

defer func(){
   if err := recover(); err !=nil{        // 处理错误    } }// ...

panic("something error")  // 这个panic将被上面的defer捕获,阻止程序退出

九、学习资源

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




以上是关于golang快速入门的主要内容,如果未能解决你的问题,请参考以下文章

golang 快速入门让Golang kafka驱动程序发布到“测试”主题,这些主题是从快速入门指南创建的http://kafka.apache.org/docum

Golang Module快速入门

golang goroutine例子[golang并发代码片段]

cgo快速入门之golang调用C语言

[易学易懂系列|golang语言|零基础|快速入门|]

Golang Gin 实战| 快速安装入门