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类型定义,定义任意需要的类型
structC中的结构体,但可以定义方法以实现类功能
interface接口类型,用来定义接口
new新建对象, 并获得它的指针
range类似python的range,配合for遍历列表、数组或map的元素
defer自动关闭资源,退出代码体(一般是函数)时执行
errorerror接口,只要实现Error()string方法就是一个error接口
panic抛出异常,如果不用recover捕获则会终止程序
recover捕获panic异常,防止程序终止,与recover结合
数值定义
const常量声明
var变量声明
数值类型
bool布尔
string字符
int,int8,int16,int32,int64int长度与平台相关
uint,uint8,uint16,uint32,uint64uint长度与平台相关
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 = 1
if 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, 线上执行环境,可演示简单程序

    原文作者:cctse
    原文地址: https://www.jianshu.com/p/54885c8af9f3
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞